first(); if ($aiModel) { return self::encode([ 'uid' => $aiModel->uid, 'id' => 0, 'typ' => self::AI_MODEL_TOKEN_TYPE, 'ver' => (int) $aiModel->token_version, ], self::AI_MODEL_TOKEN_TTL); } $user = UserApi::getByUuid($userUid); if (! $user || ! isset($user['sn'])) { // 查无此人时 UserApi 返回的是 id=0 的占位结构,不能拿它签 token return null; } return self::encode([ 'uid' => $user['id'], 'id' => $user['sn'], ], self::USER_TOKEN_TTL); } public static function getJwtKey() { return config('mint.app.jwt_secrets_key'); } public static function getToken(Request $request) { $token = $request->bearerToken(); return $token; } public static function current(Request $request) { $token = $request->bearerToken(); if ($token) { try { $jwt = JWT::decode($token, new Key(self::getJwtKey(), 'HS512')); } catch (\Exception $e) { return false; } if ($jwt->exp < time()) { // 过期 return false; } if (! self::modelTokenIsValid($jwt)) { return false; } // 有效的token return ['user_uid' => $jwt->uid, 'user_id' => $jwt->id]; } // 这里曾经有一条 cookie 回落:拿不到 bearer 就读 $_COOKIE['user_uid'] 并 // 直接当成已登录用户返回。那是 v1(api-v8)时代的遗留,而且是个认证绕过—— // 它不验签名、不验过期、不查库,浏览器里手工种一个 user_uid 就能冒充任何人。 // api-v13 与两个前端都已不再种这个 cookie(只种 timezone 和 language), // 所以这条分支是只读不写的孤儿路径,2026-09-24 删除。 // // 仍有少数 v2 控制器绕开本方法直接读 $_COOKIE,删它们是各自独立的工作: // SentenceController@index(view=fulltext) // ProgressChapterController@index // LikeController@delete // UserDictController@index(view=user)与 @delete return false; } /** * 校验模型身份 token 是否已被撤销。 * * 撤销即把 ai_models.token_version 自增,旧 token 里的 ver 随即对不上。 * 模型被删除同样视为失效。人类 token 直接放行,不查库;唯一的例外是 * root 管理员(自增主键 id 恰好是 0),见方法内 id === 0 分支的说明。 */ private static function modelTokenIsValid(object $jwt): bool { if (isset($jwt->typ) && $jwt->typ === self::AI_MODEL_TOKEN_TYPE) { $version = AiModel::where('uid', $jwt->uid)->value('token_version'); return $version !== null && (int) $version === (int) ($jwt->ver ?? 0); } // 人类 token(自增主键 id > 0)直接放行,不查库。 if ((int) ($jwt->id ?? 0) !== 0) { return true; } // id === 0 的 token 有两种来源,不能再只凭 id 判死: // 1. root 管理员的人类 token —— 其自增主键 id 恰好是 0(历史种子数据); // 2. 引入版本号之前签出的旧模型 token(typ 缺失、id 恒为 0)—— 无法撤销。 // 旧模型 token 的 uid 一定落在 ai_models 表,root 的 uid 不在其中, // 故用「uid 是否命中 ai_models」区分:命中 → 旧模型 token,作废;未命中 → 人类,放行。 // ai_models.uid 是 uuid 列,uid 不是合法 UUID 时查库会报类型错误,先挡掉。 if (! Str::isUuid((string) $jwt->uid)) { return false; } return ! AiModel::where('uid', $jwt->uid)->exists(); } /** * @param array $claims */ private static function encode(array $claims, int $ttl): string { return JWT::encode(array_merge([ 'nbf' => time(), 'exp' => time() + $ttl, ], $claims), self::getJwtKey(), 'HS512'); } }