91 lines
3.2 KiB
PHP
91 lines
3.2 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
namespace app\api\controller;
|
||
|
||
use support\Request;
|
||
use support\Response;
|
||
use Tinywan\Jwt\JwtToken;
|
||
use plugin\saiadmin\basic\OpenController;
|
||
use app\api\util\ReturnCode;
|
||
use app\api\cache\AuthTokenCache;
|
||
|
||
/**
|
||
* API 鉴权 Token 接口
|
||
* 仅支持 GET,必传参数:signature、secret、device、time,签名规则:signature = md5(device . secret . time)
|
||
* 后续所有 /api 接口调用均需在请求头携带此接口返回的 auth-token
|
||
*/
|
||
class AuthTokenController extends OpenController
|
||
{
|
||
/**
|
||
* 获取 auth-token
|
||
* GET /api/authToken
|
||
* 参数:signature(签名)、secret(密钥)、device(设备标识)、time(时间戳,秒),四者均为必传且非空
|
||
*/
|
||
public function index(Request $request): Response
|
||
{
|
||
if (strtoupper($request->method()) !== 'GET') {
|
||
return $this->fail('仅支持 GET 请求', ReturnCode::PARAMS_ERROR);
|
||
}
|
||
|
||
$param = $request->get();
|
||
$signature = trim((string) ($param['signature'] ?? ''));
|
||
$secret = trim((string) ($param['secret'] ?? ''));
|
||
$device = trim((string) ($param['device'] ?? ''));
|
||
$time = trim((string) ($param['time'] ?? ''));
|
||
|
||
if ($signature === '' || $secret === '' || $device === '' || $time === '') {
|
||
return $this->fail('signature、secret、device、time 均为必传且不能为空', ReturnCode::PARAMS_ERROR);
|
||
}
|
||
|
||
$serverSecret = trim((string) config('api.auth_token_secret', ''));
|
||
if ($serverSecret === '') {
|
||
return $this->fail('服务未配置 API_AUTH_TOKEN_SECRET', ReturnCode::PARAMS_ERROR);
|
||
}
|
||
if ($secret !== $serverSecret) {
|
||
return $this->fail('密钥错误', ReturnCode::FORBIDDEN);
|
||
}
|
||
|
||
$tolerance = (int) config('api.auth_token_time_tolerance', 300);
|
||
$now = time();
|
||
$ts = is_numeric($time) ? (int) $time : 0;
|
||
if ($ts <= 0 || abs($now - $ts) > $tolerance) {
|
||
return $this->fail('时间戳无效或已过期', ReturnCode::PARAMS_ERROR);
|
||
}
|
||
|
||
$sign = $this->getAuthToken($device, $serverSecret, $time);
|
||
if ($sign !== $signature) {
|
||
return $this->fail('签名验证失败', ReturnCode::FORBIDDEN);
|
||
}
|
||
|
||
$exp = (int) config('api.auth_token_exp', 86400);
|
||
$tokenResult = JwtToken::generateToken([
|
||
'id' => 0,
|
||
'plat' => 'api',
|
||
'device' => $device,
|
||
'access_exp' => $exp,
|
||
]);
|
||
|
||
// 同一设备只保留最新 token,覆盖后旧 token 失效
|
||
AuthTokenCache::setDeviceToken($device, $tokenResult['access_token'], $exp);
|
||
|
||
return $this->success([
|
||
'auth-token' => $tokenResult['access_token'],
|
||
'expires_in' => $tokenResult['expires_in'],
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 生成签名:signature = md5(device . secret . time)
|
||
*
|
||
* @param string $device 设备标识
|
||
* @param string $secret 密钥(来自配置)
|
||
* @param string $time 时间戳
|
||
* @return string
|
||
*/
|
||
private function getAuthToken(string $device, string $secret, string $time): string
|
||
{
|
||
return md5($device . $secret . $time);
|
||
}
|
||
}
|