manghe/app/api/middleware/Allow.php
2025-04-12 18:35:05 +08:00

191 lines
5.5 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
*
* User: anker
* Date: 4/22/22
* Email: <13408046898@163.com>
**/
namespace app\api\middleware;
use think\facade\Config;
use think\exception\HttpResponseException;
use think\Response;
class Allow
{
public function handle($request, \Closure $next)
{
// 处理跨域
header('Access-Control-Allow-Origin: *');
header('Access-Control-Max-Age: 1800');
header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE');
header('Access-Control-Allow-Headers: Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With,access-token,token,adid,clickid,client');
// 第一次预请求不管
if (strtoupper($request->method()) == "OPTIONS") {
exit;
}
// 对GET请求进行签名验证
if (strtoupper($request->method()) == "GET") {
$this->verifySignature($request);
}
// 处理跨域
// 后置中间件
$response = $next($request);
return $response;
}
/**
* 验证请求签名
* @param \think\Request $request
* @return void
*/
protected function verifySignature($request)
{
// 获取所有GET参数
$params = $request->get();
// 获取当前请求路径
$path = $request->pathinfo();
// 白名单路径检查 - 不需要验证域名的路径
$whitelistPaths = $this->getWhitelistPaths();
foreach ($whitelistPaths as $whitePath) {
// 支持简单的路径匹配,如 'notify/*' 匹配所有通知路径
if ($this->pathMatch($whitePath, $path)) {
// 白名单路径,跳过域名检查
// \think\facade\Log::info('白名单路径访问: ' . $path . ', 域名: ' . $);
return;
}
}
// 如果请求中携带is_test=true参数则跳过签名验证
if (isset($params['is_test']) && $params['is_test'] === 'true') {
return;
}
// 检查是否有必要的签名参数
if (!isset($params['timestamp']) || !isset($params['sign'])) {
$this->error('缺少必要的签名参数');
}
// 检查请求时间戳是否在合理范围内例如5分钟内
if (time() - intval($params['timestamp']) > 300) {
$this->error('请求已过期');
}
// 从请求中获取签名
$requestSign = $params['sign'];
//移除url
unset($params['s']);
// 从参数中移除签名,用于生成本地签名
unset($params['sign']);
// 按照键名对参数进行排序
ksort($params);
// 组合参数为字符串
$signStr = '';
foreach ($params as $key => $value) {
$signStr .= $key . '=' . $value . '&';
}
// 获取当前请求的域名和时间戳,组合为密钥
$host = $request->host();
$timestamp = $params['timestamp'];
$appSecret = $host . $timestamp;
// 添加密钥
$signStr = rtrim($signStr, '&') . $appSecret;
// 生成本地签名使用MD5签名算法
$localSign = md5($signStr);
// 比对签名
if ($requestSign !== $localSign) {
$this->error('签名验证失败');
}
}
/**
* 返回错误信息
* @param string $msg 错误信息
* @param int $code 错误码
* @return void
*/
protected function error($msg, $code = 0)
{
$result = [
'status' => $code,
'msg' => $msg,
'data' => null
];
$response = Response::create($result, 'json', $code);
throw new HttpResponseException($response);
}
/**
* 中间件结束调度
* @param \think\Response $response
* @return void
*/
public function end(\think\Response $response)
{
}
/**
* 获取路径白名单
*
* @return array 白名单路径列表
*/
protected function getWhitelistPaths()
{
// 1. 默认白名单路径(如支付回调通知等)
$defaultWhitelist = [
'notify/*', // 支付回调等通知
'health', // 健康检查
'debug', // 调试接口
'generate_urllinks',
];
// 2. 从配置文件中获取白名单路径
try {
$configWhitelist = Config::get('api.whitelist_paths', []);
if (!empty($configWhitelist) && is_array($configWhitelist)) {
return array_merge($defaultWhitelist, $configWhitelist);
}
} catch (\Exception $e) {
\think\facade\Log::error('获取API白名单路径配置失败: ' . $e->getMessage());
}
return $defaultWhitelist;
}
/**
* 路径匹配检查
*
* @param string $pattern 白名单路径模式
* @param string $path 请求路径
* @return bool 是否匹配
*/
protected function pathMatch($pattern, $path)
{
// 完全匹配
if ($pattern === $path) {
return true;
}
// 通配符匹配 (例如: 'notify/*')
if (strpos($pattern, '*') !== false) {
$pattern = str_replace('*', '.*', $pattern);
$pattern = '/^' . str_replace('/', '\/', $pattern) . '$/i';
return preg_match($pattern, $path);
}
return false;
}
}