diff --git a/API对接文档.md b/API对接文档.md index a3f1501..11629fc 100644 --- a/API对接文档.md +++ b/API对接文档.md @@ -49,10 +49,10 @@ ## 2. 鉴权与对接流程(平台侧 /api/v1) -平台侧接口分两步: +平台侧接口需统一携带请求头 **`api-key`**(与服务端 `.env` 中 `API_KEY` 一致),业务接口另需 **`auth-token`**。 -1. **获取 `auth-token`** -2. **携带 `auth-token` 调用 `/api/v1/*` 业务接口** +1. **获取 `auth-token`**(同时携带 `api-key`) +2. **携带 `api-key` + `auth-token` 调用 `/api/v1/*` 业务接口** ### 2.1 获取 auth-token @@ -100,11 +100,22 @@ signature = md5(agent_id + secret + time) - 密钥错误/签名错误/时间戳无效:`code=403` - 服务端未配置密钥或生成失败:`code=500` -### 2.2 调用 v1 业务接口(携带 auth-token) +### 2.2 平台 api-key(所有 /api/v1/* 必填) -除 `/api/v1/authToken` 外,其余 `/api/v1/*` 接口需要在请求头携带: +- **取值**:与服务端环境变量 `API_KEY` 完全一致(部署在 `server/.env`) +- **适用范围**:所有 `/api/v1/*` 接口(含 `/api/v1/authToken` 与业务接口) +- **携带方式**(任选其一,按优先级读取,先命中即采用): + 1. 请求头 `api-key: `(**推荐**) + 2. URL 查询参数 `api_key=`(或 `api-key=`) + 3. body 表单/JSON 字段 `api_key`(或 `api-key`) +- **未携带或错误**:`401` / `403` -- `auth-token: ` +### 2.3 调用 v1 业务接口(携带 auth-token) + +除 `/api/v1/authToken` 外,其余 `/api/v1/*` 接口需要携带: + +- `api-key: <与 API_KEY 一致>`(请求头 / query / body 任选其一,参见 2.2) +- `auth-token: `(仅支持请求头) 当 `auth-token` 过期或失效,返回 `code=402`,需要重新调用 `/api/v1/authToken` 获取新 token。 diff --git a/server/.env.example b/server/.env.example index 6c79a09..c0f404c 100644 --- a/server/.env.example +++ b/server/.env.example @@ -4,7 +4,7 @@ DB_HOST=127.0.0.1 DB_PORT=3306 DB_NAME=dafuweng-v3 DB_USER=dafuweng-v3 -DB_PASSWORD=tA6rciKLKxpFNGAm +DB_PASSWORD=123456 DB_PREFIX= DB_POOL_MAX=32 DB_POOL_MIN=4 @@ -28,14 +28,16 @@ WEBMAN_CHANNEL_LISTEN_HOST=0.0.0.0 GAME_URL=dice-v3-game.h55555game.top # API 鉴权与用户(可选,不填则用默认值) +# 平台对接 /api/v1/* 请求头 api-key(必填,与对接方约定) +API_KEY= # authToken 签名密钥(必填,与客户端约定,用于 signature 校验) -API_AUTH_TOKEN_SECRET=xF75oK91TQj13s0UmNIr1NBWMWGfflNO +API_AUTH_TOKEN_SECRET= # authToken 时间戳允许误差秒数,防重放,默认 300 API_AUTH_TOKEN_TIME_TOLERANCE=300 API_AUTH_TOKEN_EXP=86400 # API_USER_TOKEN_EXP=604800 API_USER_CACHE_EXPIRE=86400 -API_USER_ENCRYPT_KEY=Wj818SK8dhKBKNOY3PUTmZfhQDMCXEZi +API_USER_ENCRYPT_KEY= # 验证码配置,支持cache|session CAPTCHA_MODE=cache diff --git a/server/app/api/lang/legacy_en.php b/server/app/api/lang/legacy_en.php index c10649a..1139c88 100644 --- a/server/app/api/lang/legacy_en.php +++ b/server/app/api/lang/legacy_en.php @@ -48,6 +48,9 @@ return [ '没有原因' => 'Unknown reason', '缺少参数:agent_id、secret、time、signature 不能为空' => 'Missing parameters: agent_id, secret, time, signature are required', '服务端未配置 API_AUTH_TOKEN_SECRET' => 'API_AUTH_TOKEN_SECRET is not configured', + '服务端未配置 API_KEY' => 'API_KEY is not configured', + '请携带 api-key' => 'Please provide api-key', + 'api-key 无效' => 'Invalid api-key', '密钥错误' => 'Invalid secret', '时间戳已过期或无效,请同步时间' => 'Timestamp expired or invalid, please sync time', '签名验证失败' => 'Signature verification failed', diff --git a/server/app/api/middleware/ApiAccessLogMiddleware.php b/server/app/api/middleware/ApiAccessLogMiddleware.php index c2fdd36..5913af6 100644 --- a/server/app/api/middleware/ApiAccessLogMiddleware.php +++ b/server/app/api/middleware/ApiAccessLogMiddleware.php @@ -20,6 +20,7 @@ class ApiAccessLogMiddleware implements MiddlewareInterface /** 请求头名称(小写) */ private const SENSITIVE_HEADER_NAMES = [ + 'api-key', 'auth-token', 'token', 'authorization', @@ -32,6 +33,8 @@ class ApiAccessLogMiddleware implements MiddlewareInterface 'secret', 'signature', 'token', + 'api-key', + 'api_key', 'auth-token', 'auth_token', 'old_token', diff --git a/server/app/api/middleware/ApiKeyMiddleware.php b/server/app/api/middleware/ApiKeyMiddleware.php new file mode 100644 index 0000000..ef18992 --- /dev/null +++ b/server/app/api/middleware/ApiKeyMiddleware.php @@ -0,0 +1,64 @@ +resolveApiKey($request); + if ($apiKey === '') { + throw new ApiException('Please provide api-key', ReturnCode::UNAUTHORIZED); + } + if (!hash_equals($expected, $apiKey)) { + throw new ApiException('Invalid api-key', ReturnCode::FORBIDDEN); + } + + return $handler($request); + } + + private function resolveApiKey(Request $request): string + { + $headerValue = $request->header('api-key'); + if ($headerValue !== null && trim((string) $headerValue) !== '') { + return trim((string) $headerValue); + } + + foreach (['api_key', 'api-key'] as $key) { + $val = $request->get($key); + if ($val !== null && trim((string) $val) !== '') { + return trim((string) $val); + } + } + + foreach (['api_key', 'api-key'] as $key) { + $val = $request->post($key); + if ($val !== null && trim((string) $val) !== '') { + return trim((string) $val); + } + } + + return ''; + } +} diff --git a/server/config/api.php b/server/config/api.php index 9370386..4f131ac 100644 --- a/server/config/api.php +++ b/server/config/api.php @@ -11,6 +11,8 @@ return [ 'session_username_prefix' => env('API_SESSION_USERNAME_PREFIX', 'api:user:session:'), // 登录会话过期时间(秒),默认 7 天 'session_expire' => (int) env('API_SESSION_EXPIRE', 604800), + // 平台对接请求头 api-key(/api/v1/* 必填,与客户端请求头 api-key 一致) + 'platform_api_key' => env('API_KEY', ''), // auth-token 签名密钥(与客户端约定,用于 /api/authToken 的 signature 校验,必填) 'auth_token_secret' => env('API_AUTH_TOKEN_SECRET', ''), // auth-token 时间戳允许误差(秒),防重放,默认 300 秒 diff --git a/server/config/route.php b/server/config/route.php index 540188b..5fe9d2e 100644 --- a/server/config/route.php +++ b/server/config/route.php @@ -14,6 +14,7 @@ use Webman\Route; use app\api\middleware\ApiAccessLogMiddleware; +use app\api\middleware\ApiKeyMiddleware; use app\api\middleware\AuthTokenMiddleware; use app\api\middleware\TokenMiddleware; @@ -22,9 +23,10 @@ Route::group('/api/v1', function () { Route::any('/authToken', [app\api\controller\v1\AuthTokenController::class, 'index']); })->middleware([ ApiAccessLogMiddleware::class, + ApiKeyMiddleware::class, ]); -// 平台 v1 接口:需在请求头携带 auth-token +// 平台 v1 接口:需在请求头携带 api-key、auth-token Route::group('/api/v1', function () { Route::any('/getGameList', [app\api\controller\v1\GameController::class, 'getGameList']); Route::any('/getGameHall', [app\api\controller\v1\GameController::class, 'getGameHall']); @@ -36,6 +38,7 @@ Route::group('/api/v1', function () { Route::any('/setPlayerWallet', [app\api\controller\v1\GameController::class, 'setPlayerWallet']); })->middleware([ ApiAccessLogMiddleware::class, + ApiKeyMiddleware::class, AuthTokenMiddleware::class, ]); diff --git a/server/resource/translations/api/en.php b/server/resource/translations/api/en.php index 29bd54d..a908d95 100644 --- a/server/resource/translations/api/en.php +++ b/server/resource/translations/api/en.php @@ -4,6 +4,9 @@ declare(strict_types=1); return [ 'ACCOUNT_DISABLED' => 'Account is disabled and cannot log in', 'API_AUTH_TOKEN_SECRET is not configured' => 'API_AUTH_TOKEN_SECRET is not configured', + 'API_KEY is not configured' => 'API_KEY is not configured', + 'Please provide api-key' => 'Please provide api-key', + 'Invalid api-key' => 'Invalid api-key', 'AUTH_TOKEN_EXPIRED' => 'auth-token expired', 'AUTH_TOKEN_FORMAT_INVALID' => 'auth-token format invalid', 'AUTH_TOKEN_INVALID' => 'auth-token invalid', diff --git a/server/resource/translations/api/zh.php b/server/resource/translations/api/zh.php index fc19a10..e293276 100644 --- a/server/resource/translations/api/zh.php +++ b/server/resource/translations/api/zh.php @@ -4,6 +4,9 @@ declare(strict_types=1); return [ 'ACCOUNT_DISABLED' => '账号已被禁用,无法登录', 'API_AUTH_TOKEN_SECRET is not configured' => '服务端未配置 API_AUTH_TOKEN_SECRET', + 'API_KEY is not configured' => '服务端未配置 API_KEY', + 'Please provide api-key' => '请携带 api-key', + 'Invalid api-key' => 'api-key 无效', 'AUTH_TOKEN_EXPIRED' => 'auth-token 已过期', 'AUTH_TOKEN_FORMAT_INVALID' => 'auth-token 格式无效', 'AUTH_TOKEN_INVALID' => 'auth-token 无效', diff --git a/server/scripts/gen_auth_token_signature.php b/server/scripts/gen_auth_token_signature.php index 47328a1..bc0859f 100644 --- a/server/scripts/gen_auth_token_signature.php +++ b/server/scripts/gen_auth_token_signature.php @@ -11,7 +11,7 @@ declare(strict_types=1); $options = getopt('', ['agent_id:', 'secret:', 'time::']); -$agentId = $options['agent_id'] ?? '5ef059938ba799aaa845e1c2e8a762bd'; +$agentId = $options['agent_id'] ?? '202cb962ac59075b964b07152d234b70'; $secret = $options['secret'] ?? 'xF75oK91TQj13s0UmNIr1NBWMWGfflNO'; $time = $options['time'] ?? (string) time();