From 0a1109c1098fd7aa6169f7e1a2b421cf7250392e Mon Sep 17 00:00:00 2001 From: zhenhui <1276357500@qq.com> Date: Thu, 23 Apr 2026 09:26:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=AA=8C=E8=AF=81token?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3-=E4=B8=B4=E6=97=B6=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/controller/v1/Playx.php | 80 +++++++++++++++++++++++++++++++++ config/playx.php | 13 ++++++ 2 files changed, 93 insertions(+) diff --git a/app/api/controller/v1/Playx.php b/app/api/controller/v1/Playx.php index 88ad193..7320448 100644 --- a/app/api/controller/v1/Playx.php +++ b/app/api/controller/v1/Playx.php @@ -469,11 +469,91 @@ class Playx extends Api } } + /** + * 联调:合作方 JWT payload 的 base64url 解码(不验签),仅用于 dev_verify_token_exact 命中后的 session 字段。 + * + * @return array{sub?: string, user_fullname?: string, exp?: int}|null + */ + private function parsePartnerJwtPayloadForDev(string $jwt): ?array + { + $parts = explode('.', $jwt); + if (count($parts) < 2) { + return null; + } + $payload = $parts[1]; + $b64 = strtr($payload, '-_', '+/'); + $pad = strlen($b64) % 4; + if ($pad > 0) { + $b64 .= str_repeat('=', 4 - $pad); + } + $raw = base64_decode($b64, true); + if ($raw === false || $raw === '') { + return null; + } + $decoded = json_decode($raw, true); + if (!is_array($decoded)) { + return null; + } + + return $decoded; + } + /** * 本地校验 temLogin 等写入的商城 token(类型 muser),写入 mall_session */ private function verifyTokenLocal(string $token): Response { + $devExact = strval(config('playx.dev_verify_token_exact', '')); + if ($devExact !== '' && hash_equals($devExact, $token)) { + $payload = $this->parsePartnerJwtPayloadForDev($token); + if ($payload === null) { + return $this->error(__('Token expiration'), null, 0, ['statusCode' => 401]); + } + if (isset($payload['exp'])) { + $exp = intval($payload['exp']); + if ($exp > 0 && $exp <= time()) { + return $this->error(__('Token expiration'), null, 0, ['statusCode' => 401]); + } + } + + $overrideUserId = strval(config('playx.dev_verify_session_user_id', '')); + $playxUserId = $overrideUserId; + if ($playxUserId === '' && isset($payload['sub']) && is_string($payload['sub'])) { + $playxUserId = $payload['sub']; + } + if ($playxUserId === '') { + return $this->error(__('Token expiration'), null, 0, ['statusCode' => 401]); + } + + $overrideUsername = strval(config('playx.dev_verify_session_username', '')); + $username = $overrideUsername; + if ($username === '') { + if (isset($payload['user_fullname']) && is_string($payload['user_fullname']) && $payload['user_fullname'] !== '') { + $username = $payload['user_fullname']; + } elseif (isset($payload['sub']) && is_string($payload['sub'])) { + $username = $payload['sub']; + } + } + + $expireAt = time() + intval(config('playx.session_expire_seconds', 3600)); + $sessionId = bin2hex(random_bytes(16)); + MallSession::create([ + 'session_id' => $sessionId, + 'user_id' => $playxUserId, + 'username' => $username, + 'expire_time' => $expireAt, + 'create_time' => time(), + 'update_time' => time(), + ]); + + return $this->success('', [ + 'session_id' => $sessionId, + 'user_id' => $playxUserId, + 'username' => $username, + 'token_expire_at' => date('c', $expireAt), + ]); + } + $tokenData = Token::get($token); if (empty($tokenData) || (isset($tokenData['expire_time']) && intval($tokenData['expire_time']) <= time())) { return $this->error(__('Token expiration'), null, 0, ['statusCode' => 401]); diff --git a/config/playx.php b/config/playx.php index 9d918d6..4fee1ab 100644 --- a/config/playx.php +++ b/config/playx.php @@ -24,6 +24,19 @@ return [ * 联调/无 PlayX 环境可开;上线对接 PlayX 后请设为 false 并配置 api.base_url。 */ 'verify_token_local_only' => filter_var(env('PLAYX_VERIFY_TOKEN_LOCAL_ONLY', '1'), FILTER_VALIDATE_BOOLEAN), + /** + * 联调占位(待对方提供 token 校验接口后删除或清空):verify_token_local_only 为 true 时, + * 若请求中的 token 与此字符串完全一致则视为有效合作方 JWT,并写入 mall_session。 + * 生产环境务必置空或通过环境变量覆盖为空。 + */ + 'dev_verify_token_exact' => strval(env( + 'PLAYX_DEV_VERIFY_TOKEN_EXACT', + 'eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0bXlyIiwiYXV0aCI6IlJPTEVfTUVNQkVSIiwibWVyY2hhbnQiOiJwbHgiLCJ1c2VyX2Z1bGxuYW1lIjoiQW5uYSIsImN1cnJlbmN5IjoiTVlSIiwibGFuZ3VhZ2UiOiJ6aC1DTiIsInZpcCI6ZmFsc2UsImV4cCI6MTc3NjkzODI4N30.XVvNcnBcAEqdxqoNRmygkl826bLUfwH2xyBE8wiSTOSeJjv99DHKJOAgsE4ukJ-M_t1hPbraz9GO4qvOszOeDg' + )), + /** 命中 dev_verify_token_exact 时写入 session 的展示用户名(空则取 JWT user_fullname 或 sub) */ + 'dev_verify_session_username' => strval(env('PLAYX_DEV_VERIFY_SESSION_USERNAME', 'yangyang123')), + /** 命中 dev_verify_token_exact 时写入 session 的 user_id(空则取 JWT sub) */ + 'dev_verify_session_user_id' => strval(env('PLAYX_DEV_VERIFY_SESSION_USER_ID', '')), // PlayX API 配置(商城调用 PlayX 时使用) 'api' => [ 'base_url' => strval(env('PLAYX_API_BASE_URL', '')),