// +---------------------------------------------------------------------- declare (strict_types=1); namespace app\common\middleware; use app\common\model\Config; use Closure; use think\facade\Cache; use think\exception\HttpException; class ApiAuth { protected $apps; public function handle($request, Closure $next) { $this->apps = Config::whereIn('name', ['appKey', 'appSecret'])->column('value', 'name'); $params = $request->param(); $appKey = $request->header('X-Api-AppKey'); $timestamp = $request->header('X-Api-Timestamp'); $nonce = $request->header('X-Api-Nonce'); $signature = $request->header('X-Api-Signature'); // 1. 基础检查 if (!$signature || !$appKey || !$timestamp || !$nonce) { throw new HttpException(401, '缺少必要的签名参数'); } // 2. 检查 AppKey 是否合法 if ($this->apps['appKey'] != $appKey) { throw new HttpException(401, '无效的 AppKey'); } // 3. 检查时间戳(防重放攻击,允许 60 秒误差) if (abs(time() - $timestamp) > 60) { // throw new HttpException(401, '请求已超时'); } // 4. 检查随机字符串 $cacheKey = "api_nonce:" . $nonce; if (Cache::has($cacheKey)) { // throw new HttpException(401, '重复请求'); } Cache::setex($cacheKey, 60, 1); // 5. 验证签名 if (!$this->checkSignature($params, $timestamp, $nonce, $signature)) { throw new HttpException(401, '签名验证失败'); } return $next($request); } protected function checkSignature($params, $timestamp, $nonce, $signature) { $appSecret = $this->apps['appSecret']; unset($params['/']); ksort($params); $stringToBeSigned = http_build_query($params) . '&app_secret=' . $appSecret . '×tamp=' . $timestamp . '&nonce=' . $nonce; $serverSig = hash('sha256', $stringToBeSigned); return hash_equals($serverSig, $signature); } }