header('Authorization', ''); if (! is_string($header) || ! str_starts_with($header, 'Bearer ')) { throw new PlayerAuthenticationException('缺少或非法 Authorization', 8001); } // 标准:`Authorization: Bearer `,此处去掉前缀 7 字节 "Bearer " $token = trim(substr($header, 7)); if ($token === '') { throw new PlayerAuthenticationException('Token 为空', 8001); } // 本地 dev: 优先于 JWT,避免未配密钥时仍能测需登录接口 if ($this->devBypassAllowed() && str_starts_with($token, 'dev:')) { return $this->resolveDevToken($token); } // 与 .env 中 MAIN_SITE_SSO_JWT_SECRET 一致,用于 firebase/php-jwt 验签 $secret = config('lottery.main_site.sso_jwt_secret'); if (! is_string($secret) || $secret === '') { throw new PlayerAuthenticationException('SSO 未配置(MAIN_SITE_SSO_JWT_SECRET)', 8004, 503); } return $this->resolveJwt($token, $secret); } private function devBypassAllowed(): bool { return (bool) config('lottery.player_auth.dev_bypass') && app()->environment(['local', 'testing']); } private function resolveDevToken(string $token): Player { if (! preg_match('/^dev:(\d+)$/', $token, $m)) { throw new PlayerAuthenticationException('开发 Token 格式应为 dev:{玩家ID}', 8002); } $player = Player::query()->find((int) $m[1]); if ($player === null) { throw new PlayerAuthenticationException('玩家不存在', 8003); } return $player; } private function resolveJwt(string $jwt, string $secret): Player { $alg = (string) config('lottery.player_auth.jwt.algorithm', 'HS256'); try { /** @var object $claims */ $claims = JWT::decode($jwt, new Key($secret, $alg)); } catch (\Throwable $e) { // 签名错误、exp 过期、格式损坏等均归 8002 throw new PlayerAuthenticationException('Token 无效或已过期', 8002); } // 与主站约定 JWT 里字段名;若主站用 sub/iss 等可改 env LOTTERY_JWT_CLAIM_* $siteKey = (string) config('lottery.player_auth.jwt.claim_site_code', 'site_code'); $pidKey = (string) config('lottery.player_auth.jwt.claim_site_player_id', 'site_player_id'); $siteCode = data_get($claims, $siteKey); $sitePlayerId = data_get($claims, $pidKey); if (! is_string($siteCode) || $siteCode === '' || ! is_string($sitePlayerId) || $sitePlayerId === '') { throw new PlayerAuthenticationException('JWT 缺少站点或玩家标识', 8002); } // 首期:库中必须先有该行;若需「首次进入自动建档」可在此处 firstOrCreate $player = Player::query() ->where('site_code', $siteCode) ->where('site_player_id', $sitePlayerId) ->first(); if ($player === null) { throw new PlayerAuthenticationException('玩家未建档', 8003); } return $player; } }