1.重新设置Token验证接口

This commit is contained in:
2026-05-06 09:49:39 +08:00
parent ccdb58ea1d
commit 29ab883f4e
5 changed files with 124 additions and 66 deletions

View File

@@ -34,8 +34,8 @@ AGENT_AUTH_JWT_SECRET=
PLAYX_SESSION_EXPIRE_SECONDS=3600
# verifyToken 是否仅本地联调false=走对方远程校验)
PLAYX_VERIFY_TOKEN_LOCAL_ONLY=false
# verifyToken 对方接口路径
PLAYX_TOKEN_VERIFY_URL=/api/v1/auth/verify-token
# verifyToken:填完整 https URL 时仅发 request_id+token填相对路径时拼 PLAYX_ANGPOW_IMPORT_BASE_URL 并发商户签名体
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
# verifyToken 仅本地联调时的默认用户名

View File

@@ -411,56 +411,85 @@ class Playx extends Api
return $this->error(__('Token expiration'), null, 0, ['statusCode' => 401]);
}
$baseUrl = config('playx.angpow_import.base_url', '');
$verifyUrl = config('playx.api.token_verify_url', '/api/v1/auth/verify-token');
$verifyPath = ltrim(strval($verifyUrl), '/');
if ($verifyPath === '') {
return $this->error(__('PlayX API not configured'));
}
if ($baseUrl === '') {
return $this->error(__('PlayX API not configured'));
$baseUrl = strval(config('playx.angpow_import.base_url', ''));
$verifyUrlRaw = strval(config('playx.api.token_verify_url', '/api/v1/auth/verify-token'));
$verifyUrlTrimmed = trim($verifyUrlRaw);
$isAbsoluteVerifyUrl = str_starts_with($verifyUrlTrimmed, 'http://')
|| str_starts_with($verifyUrlTrimmed, 'https://');
if ($isAbsoluteVerifyUrl) {
$targetVerifyUrl = $verifyUrlTrimmed;
} else {
$verifyPath = ltrim($verifyUrlTrimmed, '/');
if ($verifyPath === '') {
return $this->error(__('PlayX API not configured'));
}
if ($baseUrl === '') {
return $this->error(__('PlayX API not configured'));
}
$targetVerifyUrl = rtrim($baseUrl, '/') . '/' . $verifyPath;
}
try {
$merchantCode = strval(config('playx.angpow_import.merchant_code', ''));
$authKey = strval(config('playx.angpow_import.auth_key', ''));
if ($merchantCode === '' || $authKey === '') {
return $this->error(__('PlayX API not configured'));
}
$requestId = 'mall_' . uniqid();
$requestDate = strval(time());
$signatureInput = 'merchant_code=' . $merchantCode
. '&request_date=' . $requestDate
. '&request_id=' . $requestId
. '&token=' . $token;
$signature = $this->buildPlayxTokenVerifySignature($signatureInput, $authKey);
if ($signature === null) {
return $this->error(__('Invalid signature'), null, 0, ['statusCode' => 500]);
}
$client = new \GuzzleHttp\Client([
'base_uri' => rtrim($baseUrl, '/') . '/',
'timeout' => 10,
$clientOptions = [
'timeout' => 10,
'http_errors' => false,
]);
$headers = [
'Content-Type' => 'application/json',
'X-Request-Signature' => $signature,
'X-Signature' => $signature,
'X-Request-Date' => $requestDate,
'X-Request-ID' => $requestId,
];
$payload = [
'merchant_code' => $merchantCode,
'request_date' => $requestDate,
'request_id' => $requestId,
'token' => $token,
];
$res = $client->post($verifyPath, [
'headers' => $headers,
'json' => $payload,
]);
if (!$isAbsoluteVerifyUrl) {
$clientOptions['base_uri'] = rtrim($baseUrl, '/') . '/';
}
$client = new \GuzzleHttp\Client($clientOptions);
if ($isAbsoluteVerifyUrl) {
$headers = [
'Content-Type' => 'application/json',
];
$payload = [
'request_id' => $requestId,
'token' => $token,
];
$res = $client->post($targetVerifyUrl, [
'headers' => $headers,
'json' => $payload,
]);
} else {
$merchantCode = strval(config('playx.angpow_import.merchant_code', ''));
$authKey = strval(config('playx.angpow_import.auth_key', ''));
if ($merchantCode === '' || $authKey === '') {
return $this->error(__('PlayX API not configured'));
}
$requestDate = strval(time());
$signatureInput = 'merchant_code=' . $merchantCode
. '&request_date=' . $requestDate
. '&request_id=' . $requestId
. '&token=' . $token;
$signature = $this->buildPlayxTokenVerifySignature($signatureInput, $authKey);
if ($signature === null) {
return $this->error(__('Invalid signature'), null, 0, ['statusCode' => 500]);
}
$headers = [
'Content-Type' => 'application/json',
'X-Request-Signature' => $signature,
'X-Signature' => $signature,
'X-Request-Date' => $requestDate,
'X-Request-ID' => $requestId,
];
$payload = [
'merchant_code' => $merchantCode,
'request_date' => $requestDate,
'request_id' => $requestId,
'token' => $token,
];
$verifyPath = ltrim($verifyUrlTrimmed, '/');
$res = $client->post($verifyPath, [
'headers' => $headers,
'json' => $payload,
]);
}
$data = json_decode(strval($res->getBody()), true);
$code = $res->getStatusCode();
if ($code !== 200 || empty($data['user_id'])) {
@@ -477,6 +506,9 @@ class Playx extends Api
$remoteMsg = mb_substr($bodyText, 0, 300);
}
}
if ($remoteMsg === '' || str_contains(strtolower($remoteMsg), '<html')) {
$remoteMsg = 'PlayX verify-token failed: HTTP ' . strval($code) . ' at ' . $targetVerifyUrl;
}
$msg = $remoteMsg !== '' ? $remoteMsg : __('Token expiration');
return $this->error($msg, null, 0, ['statusCode' => 401]);

View File

@@ -32,6 +32,7 @@ return [
'api' => [
'base_url' => strval(env('PLAYX_API_BASE_URL', '')),
'secret_key' => strval(env('PLAYX_API_SECRET_KEY', '')),
// 完整 https URL回调校验Body 仅 request_id + token相对路径拼 angpow_import.base_url + 商户签名字段
'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',

View File

@@ -71,15 +71,18 @@ flowchart LR
1. 用户在 playX 内打开积分商城入口iframe
2. playX 前端通过 postMessage 将 **playX 下发的 token**(及必要上下文)传给商城 H5。
3. 商城 H5 调用商城后端 **`POST /api/v1/mall/verifyToken`**(前端只传 `token`),由商城后端向 playX 的 **Token Verification API**`PLAYX_ANGPOW_IMPORT_BASE_URL` + `PLAYX_TOKEN_VERIFY_URL`发起校验。
4. **前提**:配置 **`playX.verify_token_local_only = false`**,且 **`PLAYX_ANGPOW_IMPORT_BASE_URL`** 已配置为可访问的 playX 基地址。
3. 商城 H5 调用商城后端 **`POST /api/v1/mall/verifyToken`**(前端只传 `token`),由商城后端向 **Token Verification API** 发起校验。
4. **前提**:配置 **`playX.verify_token_local_only = false`**,且 **`PLAYX_TOKEN_VERIFY_URL`** 已配置:
- **完整 `https://...` URL**(如回调:`https://callback-mallsys.superior3.net/callback/api/mallsys/plx/auth/verify-token`):商城对该地址 `POST`Body 仅 **`request_id` + `token`**,无需 `PLAYX_ANGPOW_IMPORT_BASE_URL`
- **相对路径**(如 `/api/v1/auth/verify-token`):需同时配置 **`PLAYX_ANGPOW_IMPORT_BASE_URL`**,并按商户约定附带 **`merchant_code` / `request_date` / `X-Request-Signature`** 等。
5. playX 返回 **`user_id``username`**(及可选会话过期时间等)。
6. 商城写入 **`mall_playx_session`**`session_id` + 上述 `user_id`/`username` + 过期时间),后续 H5 可用 **`session_id`** 或 **`token`(商城临时 token见模式 B** 调用资产/领取等接口。
幂等与安全:
- H5 **不要**把 playX 的 `user_id` 当作唯一可信凭据直传下单;**以 token 换 session** 或由商城签发 token 的流程为准。
- playX 侧 Token Verification API 的鉴权/签名由商城后端完成(`merchant_code/request_date/request_id` + `X-Request-Signature`H5 不参与签名计算
- `PLAYX_TOKEN_VERIFY_URL` 为**完整 https URL**:对端按回调文档校验,商城后端只组 **`request_id` + `token`**H5 不参与。
- 若为**相对路径 + 基地址**商户网关:鉴权/签名由商城后端完成(`merchant_code/request_date/request_id` + `X-Request-Signature`H5 不参与签名计算。
#### 4.1.3 模式 B本地 / 无 playX 环境(商城自校验,不请求 playX
@@ -211,26 +214,46 @@ flowchart LR
### 5.2 Mall → playXToken Verification API
- **目的**:商城后端校验 token/session,获取可信 `user_id``username`
- **目的**:商城后端校验 token获取可信 `user_id``username`
- **Method**`POST`
- **Base URL**`PLAYX_ANGPOW_IMPORT_BASE_URL`(当前联调:`https://plx-uat2.ttwd3.com/en-my/mall`
- **Path**`/api/v1/auth/verify-token`
- **完整 URL**`{PLAYX_ANGPOW_IMPORT_BASE_URL}/api/v1/auth/verify-token`
- **签名 Header**`X-Request-Signature``Base64(HMAC-SHA1(canonical, key))`
- **签名明文 canonical**`merchant_code={merchant_code}&request_date={request_date}&request_id={request_id}&token={token}`
- **request_date 格式**Unix 时间戳字符串(秒)
- **Content-Type**`application/json`
请求字段说明(当前实现):
#### 5.2.1 回调网关(推荐当前联调)
- **完整 URL**:由环境变量 **`PLAYX_TOKEN_VERIFY_URL`** 填完整地址,例如:
`https://callback-mallsys.superior3.net/callback/api/mallsys/plx/auth/verify-token`
- **请求 Body**
| 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `merchant_code` | String | 是 | 商户编码,默认 `plx`。 |
| `request_date` | String | 是 | 请求时间戳(秒)。 |
| `request_id` | String | 是 | 商城系统生成的唯一请求流水号。 |
| `token` | String | 是 | 从带有商城的 Iframe `postMessage` 接收到的用户登录凭证。 |
| `request_id` | String | 是 | 商城生成的请求追踪号。 |
| `token` | String | 是 | 前端传入的 playX 临时凭证。 |
请求示例:
```json
{
"request_id": "mall_20260319_9f1b6d",
"token": "eyJhbGciOi..."
}
```
#### 5.2.2 商户网关(相对路径 + HMAC
- **Base URL**`PLAYX_ANGPOW_IMPORT_BASE_URL`
- **Path**`PLAYX_TOKEN_VERIFY_URL`(相对路径,如 `/api/v1/auth/verify-token`
- **完整 URL**`{PLAYX_ANGPOW_IMPORT_BASE_URL}{PLAYX_TOKEN_VERIFY_URL}`
- **签名 Header**`X-Request-Signature``Base64(HMAC-SHA1(canonical, key))`
- **签名明文 canonical**`merchant_code={merchant_code}&request_date={request_date}&request_id={request_id}&token={token}`
- **request_date**Unix 时间戳字符串(秒)
| 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `merchant_code` | String | 是 | 商户编码。 |
| `request_date` | String | 是 | 请求时间戳(秒)。 |
| `request_id` | String | 是 | 商城系统生成的唯一请求流水号。 |
| `token` | String | 是 | 用户登录凭证。 |
```json
{
"merchant_code": "plx",

View File

@@ -299,8 +299,10 @@ curl -X POST 'https://{商城域名}/api/v1/mall/dailyPush' \
**前端入参约定(重要)**
- 前端/客户端调用本接口时,**只需要传 `token`**(或兼容传 `session`)。
- `merchant_code``request_date``request_id``X-Request-Signature` 由商城后端生成并带给 playX前端无需参与签名。
-仅传 `token` 仍校验失败,通常是 token 本身无效/过期或对方网关路径未放通,并非前端少传字段。
- 商城后端调用 playX 校验时:
- **`PLAYX_TOKEN_VERIFY_URL` 为完整 `https://` URL**(回调网关):后端只向对方发送 **`request_id` + `token`**(与对端文档一致),无需前端传商户字段。
- 若为**相对路径**:后端会拼 **`PLAYX_ANGPOW_IMPORT_BASE_URL`**,并生成 **`merchant_code` / `request_date` / `request_id` / `X-Request-Signature`**,前端仍不参与签名。
- 若仅传 `token` 仍校验失败,多为 token 无效/过期或上游 URL 未放通,并非前端少传字段。
**请求示例:**
```json
@@ -630,10 +632,10 @@ 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` | 远程 `verify-token` 基础地址(当前联调可含站点前缀路径) |
| `PLAYX_TOKEN_VERIFY_URL` | 远程 `verify-token` 路径(默认 `/api/v1/auth/verify-token` |
| `PLAYX_ANGPOW_MERCHANT_CODE` | `verify-token` 请求体 `merchant_code` |
| `PLAYX_ANGPOW_IMPORT_AUTH_KEY` | `verify-token` 签名密钥HMAC-SHA1Base64 输出) |
| `PLAYX_ANGPOW_IMPORT_BASE_URL` | Angpow 推送等接口基地址;**相对路径** `verify-token` 时亦作其基地址 |
| `PLAYX_TOKEN_VERIFY_URL` | **完整 `https://` URL**回调校验Body 仅 `request_id`+`token`**相对路径**:拼基地址 + 商户签名体 |
| `PLAYX_ANGPOW_MERCHANT_CODE` | 仅相对路径 `verify-token` 时使用 |
| `PLAYX_ANGPOW_IMPORT_AUTH_KEY` | 仅相对路径 `verify-token` 时 HMAC 密钥 |
---