diff --git a/server/app/api/cache/UserCache.php b/server/app/api/cache/UserCache.php index 362324a..7c62190 100644 --- a/server/app/api/cache/UserCache.php +++ b/server/app/api/cache/UserCache.php @@ -131,4 +131,51 @@ class UserCache $val = Cache::get($key); return $val !== null && $val !== ''; } + + /** 当前有效 user-token 按用户存储的 key 前缀(重新登录/注册后覆盖,保证单用户单 token) */ + private static function currentTokenPrefix(): string + { + return config('api.user_token_current_prefix', 'api:user:current_token:'); + } + + private static function userTokenExpire(): int + { + return (int) config('api.user_token_exp', 604800); + } + + /** + * 设置该用户当前唯一有效的 user-token(登录/注册时调用,会覆盖该用户之前的 token) + * @param int $userId 用户 ID + * @param string $token 完整 user-token 字符串 + */ + public static function setCurrentUserToken(int $userId, string $token): bool + { + if ($userId <= 0 || $token === '') { + return false; + } + $key = self::currentTokenPrefix() . $userId; + return Cache::set($key, $token, self::userTokenExpire()); + } + + /** + * 获取该用户当前在服务端登记的有效 user-token,不存在或已过期返回 null + */ + public static function getCurrentUserToken(int $userId): ?string + { + if ($userId <= 0) { + return null; + } + $key = self::currentTokenPrefix() . $userId; + $value = Cache::get($key); + return $value !== null && $value !== '' ? (string) $value : null; + } + + /** + * 校验请求中的 token 是否为该用户当前唯一有效 token + */ + public static function isCurrentUserToken(int $userId, string $token): bool + { + $current = self::getCurrentUserToken($userId); + return $current !== null && $current === $token; + } } diff --git a/server/app/api/logic/UserLogic.php b/server/app/api/logic/UserLogic.php index 9f0bd39..3246c8f 100644 --- a/server/app/api/logic/UserLogic.php +++ b/server/app/api/logic/UserLogic.php @@ -56,6 +56,8 @@ class UserLogic UserCache::setUser((int) $user->id, $userArr); $userToken = $this->generateUserToken((int) $user->id); + // 同一用户只保留最新一次登录的 token,旧 token 自动失效 + UserCache::setCurrentUserToken((int) $user->id, $userToken); return [ 'user' => $userArr, 'user-token' => $userToken, @@ -94,6 +96,8 @@ class UserLogic UserCache::setUser((int) $user->id, $userArr); $userToken = $this->generateUserToken((int) $user->id); + // 同一用户只保留最新一次登录的 token,旧 token 自动失效 + UserCache::setCurrentUserToken((int) $user->id, $userToken); return [ 'user' => $userArr, 'user-token' => $userToken, @@ -174,7 +178,15 @@ class UserLogic return null; } $id = $extend['id'] ?? null; - return $id !== null ? (int) $id : null; + if ($id === null) { + return null; + } + $userId = (int) $id; + // 同一用户只允许当前登记的 token 生效,重新登录/注册后旧 token 失效 + if (!UserCache::isCurrentUserToken($userId, $userToken)) { + return null; + } + return $userId; } catch (\Throwable $e) { return null; } diff --git a/server/config/api.php b/server/config/api.php index 2792834..a5e86e0 100644 --- a/server/config/api.php +++ b/server/config/api.php @@ -13,6 +13,8 @@ return [ 'auth_token_device_prefix' => env('API_AUTH_TOKEN_DEVICE_PREFIX', 'api:auth_token:'), // user-token 有效期(秒),默认 7 天 'user_token_exp' => (int) env('API_USER_TOKEN_EXP', 604800), + // 按用户存储当前有效 user-token 的 Redis key 前缀(同一用户仅保留最新一次登录的 token) + 'user_token_current_prefix' => env('API_USER_TOKEN_CURRENT_PREFIX', 'api:user:current_token:'), // 用户信息 Redis 缓存过期时间(秒),默认 7 天 'user_cache_expire' => (int) env('API_USER_CACHE_EXPIRE', 604800), // 用户缓存 Redis key 前缀