From d44ce36286209d9d833fcb15c8a88d46bb843fcb Mon Sep 17 00:00:00 2001 From: zhenhui <1276357500@qq.com> Date: Wed, 6 May 2026 10:59:49 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B5=8B=E8=AF=95verifyToken?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env-example | 2 +- app/api/controller/v1/Playx.php | 5 ++++- config/playx.php | 2 +- docs/PlayX-对接文档(积分商城).md | 10 ++++++---- docs/PlayX-调用积分商城接口说明.md | 4 ++-- scripts/playx-verify-token-callback-test.php | 8 +++++++- 6 files changed, 21 insertions(+), 10 deletions(-) diff --git a/.env-example b/.env-example index aadd528..83f2685 100644 --- a/.env-example +++ b/.env-example @@ -34,7 +34,7 @@ AGENT_AUTH_JWT_SECRET= PLAYX_SESSION_EXPIRE_SECONDS=3600 # verifyToken 是否仅本地联调(false=走对方远程校验) PLAYX_VERIFY_TOKEN_LOCAL_ONLY=false -# verifyToken:完整 https URL 时 Body merchant_code+request_id+token、签名明文同序;相对路径时拼基址+商户四字段 +# verifyToken:完整 https URL 时 Body merchant_code+request_date+request_id+token、签名明文同序;相对路径时拼基址+商户四字段 PLAYX_TOKEN_VERIFY_URL=https://callback-mallsys.superior3.net/callback/api/mallsys/plx/auth/verify-token # verifyToken 仅本地联调时的默认用户ID PLAYX_VERIFY_TOKEN_LOCAL_DEFAULT_USER_ID=testmyr diff --git a/app/api/controller/v1/Playx.php b/app/api/controller/v1/Playx.php index a42a994..215f68d 100644 --- a/app/api/controller/v1/Playx.php +++ b/app/api/controller/v1/Playx.php @@ -449,8 +449,10 @@ class Playx extends Api return $this->error(__('PlayX API not configured')); } - // 回调网关要求 Body 含 merchant_code;签名字符串与参与签名的字段顺序与 angpow 风格一致(HMAC-SHA1→Base64、密钥解析同 angpow)。 + // 回调网关要求 Body 含 merchant_code、request_date(Unix 秒);签名字符串与 Body 参与签名字段一致(HMAC 同 angpow)。 + $requestDate = strval(time()); $signatureInput = 'merchant_code=' . $merchantCode + . '&request_date=' . $requestDate . '&request_id=' . $requestId . '&token=' . $token; $signature = $this->buildPlayxTokenVerifySignature($signatureInput, $authKey); @@ -464,6 +466,7 @@ class Playx extends Api ]; $payload = [ 'merchant_code' => $merchantCode, + 'request_date' => $requestDate, 'request_id' => $requestId, 'token' => $token, ]; diff --git a/config/playx.php b/config/playx.php index f4c41b3..b75ba26 100644 --- a/config/playx.php +++ b/config/playx.php @@ -32,7 +32,7 @@ return [ 'api' => [ 'base_url' => strval(env('PLAYX_API_BASE_URL', '')), 'secret_key' => strval(env('PLAYX_API_SECRET_KEY', '')), - // 完整 https URL:回调 Body merchant_code+request_id+token,签名同字段拼接(HMAC 同 angpow);相对路径:拼基址 + 商户体 + // 完整 https URL:回调 Body merchant_code+request_date+request_id+token,签名同序(HMAC 同 angpow);相对路径:拼基址 + 商户体 'token_verify_url' => strval(env('PLAYX_TOKEN_VERIFY_URL', '/api/v1/auth/verify-token')), 'bonus_grant_url' => '/api/v1/bonus/grant', 'balance_credit_url' => '/api/v1/balance/credit', diff --git a/docs/PlayX-对接文档(积分商城).md b/docs/PlayX-对接文档(积分商城).md index 5b1bb77..861764d 100644 --- a/docs/PlayX-对接文档(积分商城).md +++ b/docs/PlayX-对接文档(积分商城).md @@ -73,7 +73,7 @@ flowchart LR 2. playX 前端通过 postMessage 将 **playX 下发的 token**(及必要上下文)传给商城 H5。 3. 商城 H5 调用商城后端 **`POST /api/v1/mall/verifyToken`**(前端只传 `token`),由商城后端向 **Token Verification API** 发起校验。 4. **前提**:配置 **`playX.verify_token_local_only = false`**,且 **`PLAYX_TOKEN_VERIFY_URL`** 已配置: - - **完整 `https://...` URL**(如回调):商城 `POST` 该地址;JSON Body 含 **`merchant_code`**(`PLAYX_ANGPOW_MERCHANT_CODE`)、**`request_id`、`token`**;**`X-Request-Signature`** 与 angpow-imports **同源算法**(密钥 **`PLAYX_ANGPOW_IMPORT_AUTH_KEY`**),签名字符串为 **`merchant_code=...&request_id=...&token=...`**;无需 `PLAYX_ANGPOW_IMPORT_BASE_URL`。 + - **完整 `https://...` URL**(如回调):商城 `POST` 该地址;JSON Body 含 **`merchant_code`、`request_date`(Unix 秒)、`request_id`、`token`**;**`X-Request-Signature`** 与 angpow-imports **同源算法**(密钥 **`PLAYX_ANGPOW_IMPORT_AUTH_KEY`**),签名字符串为 **`merchant_code=...&request_date=...&request_id=...&token=...`**;无需 `PLAYX_ANGPOW_IMPORT_BASE_URL`。 - **相对路径**(如 `/api/v1/auth/verify-token`):需同时配置 **`PLAYX_ANGPOW_IMPORT_BASE_URL`**,Body 使用 **`request_date`**,并按商户约定附带签名等。 5. playX 返回 **`user_id`、`username`**(及可选会话过期时间等)。 6. 商城写入 **`mall_playx_session`**(`session_id` + 上述 `user_id`/`username` + 过期时间),后续 H5 可用 **`session_id`** 或 **`token`(商城临时 token,见模式 B)** 调用资产/领取等接口。 @@ -81,7 +81,7 @@ flowchart LR 幂等与安全: - H5 **不要**把 playX 的 `user_id` 当作唯一可信凭据直传下单;**以 token 换 session** 或由商城签发 token 的流程为准。 -- 若 `PLAYX_TOKEN_VERIFY_URL` 为**完整 https URL**:Body 含 **`merchant_code`、`request_id`、`token`**(回调网关必填 `merchant_code`);`X-Request-Signature` 为 angpow 同源 HMAC,明文 **`merchant_code=...&request_id=...&token=...`**;H5 不参与签名。 +- 若 `PLAYX_TOKEN_VERIFY_URL` 为**完整 https URL**:Body 含 **`merchant_code`、`request_date`、`request_id`、`token`**;`X-Request-Signature` 为 angpow 同源 HMAC,明文 **`merchant_code=...&request_date=...&request_id=...&token=...`**;H5 不参与签名。 - 若为**相对路径 + 基地址**商户网关:鉴权/签名由商城后端完成(`merchant_code/request_date/request_id` + `X-Request-Signature` 等);H5 不参与签名计算。 #### 4.1.3 模式 B:本地 / 无 playX 环境(商城自校验,不请求 playX) @@ -223,12 +223,13 @@ flowchart LR - **完整 URL**:由环境变量 **`PLAYX_TOKEN_VERIFY_URL`** 填完整地址,例如: `https://callback-mallsys.superior3.net/callback/api/mallsys/plx/auth/verify-token` - **请求 Header**:与 **angpow-imports** 相同风格,仅 **`Content-Type: application/json`**、**`X-Request-Signature`**(`Base64(HMAC-SHA1(canonical, PLAYX_ANGPOW_IMPORT_AUTH_KEY))`,密钥解析与 angpow 一致:hex / base64 / 明文)。 -- **签名明文 canonical**:`merchant_code={merchant_code}&request_id={request_id}&token={token}`(与 JSON Body 参与签名字段一致)。 -- **请求 Body**(回调网关实际校验;在 PlayX 文档 `request_id`+`token` 基础上增加对端要求的 **`merchant_code`**): +- **签名明文 canonical**:`merchant_code={merchant_code}&request_date={request_date}&request_id={request_id}&token={token}`(`request_date` 为 Unix 秒字符串,与 Body 一致)。 +- **请求 Body**(回调网关实际校验;在文档 `request_id`+`token` 基础上增加对端要求的 **`merchant_code`、`request_date`**): | 字段名 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `merchant_code` | String | 是 | 与 `PLAYX_ANGPOW_MERCHANT_CODE` 一致。 | +| `request_date` | String | 是 | Unix 秒时间戳字符串,须与参与签名一致。 | | `request_id` | String | 是 | 商城生成的请求追踪号。 | | `token` | String | 是 | 前端传入的 playX 临时凭证。 | @@ -237,6 +238,7 @@ flowchart LR ```json { "merchant_code": "plx", + "request_date": "1700000000", "request_id": "mall_20260319_9f1b6d", "token": "eyJhbGciOi..." } diff --git a/docs/PlayX-调用积分商城接口说明.md b/docs/PlayX-调用积分商城接口说明.md index 0d33fc9..bb63da5 100644 --- a/docs/PlayX-调用积分商城接口说明.md +++ b/docs/PlayX-调用积分商城接口说明.md @@ -304,7 +304,7 @@ curl -X POST 'https://{商城域名}/api/v1/mall/dailyPush' \ **前端入参约定(重要)**: - 调用本接口时 **仅需 `token`**(或兼容 `session`);**不要**传 `request_id` / `request_date` / 商户字段(均由商城后端生成后请求上游)。 - 建议 **`Content-Type: application/json`**,Body:`{"token":"..."}`。若使用 **form-data**(如图二),请同样只传 `token`(及可选 `lang`);避免缺少 `token` 键名。 -- 商城请求上游回调地址时:Body 含 **`merchant_code`、`request_id`、`token`**(对端必填 `merchant_code`);`X-Request-Signature` 与 angpow-imports **同源算法**,canonical 为 **`merchant_code=...&request_id=...&token=...`**。 +- 商城请求上游回调地址时:Body 含 **`merchant_code`、`request_date`(秒)、`request_id`、`token`**;`X-Request-Signature` 与 angpow-imports **同源算法**,canonical 为 **`merchant_code=...&request_date=...&request_id=...&token=...`**。 **请求示例:** ```json @@ -644,7 +644,7 @@ GET /api/v1/mall/addressList?session_id=fc7f3e3f0d0f4cb29f66e4c8fbab4f66 | `PLAYX_DAILY_PUSH_SECRET` | 非空则 Daily Push 必须带合法 HMAC 头 | | `PLAYX_VERIFY_TOKEN_LOCAL_ONLY` | 为 true 时 verifyToken 不请求 playX 远程 | | `PLAYX_ANGPOW_IMPORT_BASE_URL` | Angpow 推送等接口基地址;**相对路径** `verify-token` 时亦作其基地址 | -| `PLAYX_TOKEN_VERIFY_URL` | **完整 `https://` URL**:回调 Body `merchant_code`+`request_id`+`token`,签名明文同序;**相对路径**:拼基地址 + 商户四字段 | +| `PLAYX_TOKEN_VERIFY_URL` | **完整 `https://` URL**:回调 Body `merchant_code`+`request_date`+`request_id`+`token`,签名明文同序;**相对路径**:拼基地址 + 商户四字段 | | `PLAYX_ANGPOW_MERCHANT_CODE` | **回调与相对路径** `verify-token` 均需 | | `PLAYX_ANGPOW_IMPORT_AUTH_KEY` | 回调与相对路径的 HMAC 密钥(与 angpow-imports 相同) | diff --git a/scripts/playx-verify-token-callback-test.php b/scripts/playx-verify-token-callback-test.php index 92bf4c0..958b8b6 100644 --- a/scripts/playx-verify-token-callback-test.php +++ b/scripts/playx-verify-token-callback-test.php @@ -88,11 +88,16 @@ if ($authKey === '') { } $requestId = 'mall_cli_' . uniqid(); -$canonical = 'merchant_code=' . $merchantCode . '&request_id=' . $requestId . '&token=' . $token; +$requestDate = strval(time()); +$canonical = 'merchant_code=' . $merchantCode + . '&request_date=' . $requestDate + . '&request_id=' . $requestId + . '&token=' . $token; $signature = buildSignature($canonical, $authKey); $payload = json_encode([ 'merchant_code' => $merchantCode, + 'request_date' => $requestDate, 'request_id' => $requestId, 'token' => $token, ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); @@ -109,6 +114,7 @@ echo " -d '" . $payload . "'\n\n"; echo "--- 本机直接请求(PHP stream)---\n"; echo "request_id={$requestId}\n"; +echo "request_date={$requestDate}\n"; echo "canonical={$canonical}\n\n"; $body = $payload;