220 lines
6.4 KiB
PHP
Executable File
220 lines
6.4 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* 域名绑定中间件
|
|
* 用于检查请求域名是否在允许的列表中
|
|
*/
|
|
|
|
namespace app\admin\middleware;
|
|
|
|
use think\facade\Config;
|
|
use think\exception\HttpResponseException;
|
|
use think\Response;
|
|
|
|
|
|
class DomainBind
|
|
{
|
|
/**
|
|
* 处理请求
|
|
*
|
|
* @param \think\Request $request
|
|
* @param \Closure $next
|
|
* @return \think\Response
|
|
*/
|
|
public function handle($request, \Closure $next)
|
|
{
|
|
// 获取当前请求域名
|
|
$domain = $request->host();
|
|
|
|
// 获取当前请求路径
|
|
$path = $request->pathinfo();
|
|
|
|
// 白名单路径检查 - 不需要验证域名的路径
|
|
$whitelistPaths = $this->getWhitelistPaths();
|
|
foreach ($whitelistPaths as $whitePath) {
|
|
// 支持简单的路径匹配,如 'notify/*' 匹配所有通知路径
|
|
if ($this->pathMatch($whitePath, $path)) {
|
|
// 白名单路径,跳过域名检查
|
|
\think\facade\Log::info('白名单路径访问: ' . $path . ', 域名: ' . $domain);
|
|
return $next($request);
|
|
}
|
|
}
|
|
|
|
// 检查域名是否允许
|
|
if (!$this->checkDomain($domain)) {
|
|
// 记录被拒绝的请求
|
|
\think\facade\Log::warning('域名访问被拒绝: ' . $domain . ', 路径: ' . $path);
|
|
return $this->error('未授权,无法访问', 403);
|
|
}
|
|
|
|
// 域名允许,记录请求信息(可选,仅用于调试)
|
|
// \think\facade\Log::info('域名访问通过: ' . $domain . ', 路径: ' . $path);
|
|
|
|
// 域名允许,继续处理请求
|
|
return $next($request);
|
|
}
|
|
|
|
/**
|
|
* 检查域名是否在允许列表中
|
|
*
|
|
* @param string $domain 请求域名
|
|
* @return bool 是否允许
|
|
*/
|
|
protected function checkDomain($domain)
|
|
{
|
|
// 获取所有允许的域名
|
|
$allowedDomains = $this->getAllowedDomains();
|
|
|
|
// 如果没有设置允许的域名,默认放行所有域名
|
|
if (empty($allowedDomains)) {
|
|
return true;
|
|
}
|
|
|
|
// 检查域名是否在允许列表中
|
|
foreach ($allowedDomains as $allowedDomain) {
|
|
// 支持通配符匹配,例如 *.example.com
|
|
if ($this->domainMatch($allowedDomain, $domain)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// 域名不在允许列表中
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 获取所有允许的域名
|
|
*
|
|
* @return array 允许的域名列表
|
|
*/
|
|
protected function getAllowedDomains()
|
|
{
|
|
$allowedDomains = [];
|
|
|
|
// 1. 从配置文件中获取额外允许的域名
|
|
try {
|
|
$configDomains = Config::get('api.admin_domains', []);
|
|
if (!empty($configDomains) && is_array($configDomains)) {
|
|
foreach ($configDomains as $d) {
|
|
$d = trim($d);
|
|
if (!empty($d) && !in_array($d, $allowedDomains)) {
|
|
$allowedDomains[] = $d;
|
|
}
|
|
}
|
|
}
|
|
} catch (\Exception $e) {
|
|
// 配置获取失败,记录日志
|
|
\think\facade\Log::error('获取API域名配置失败: ' . $e->getMessage());
|
|
}
|
|
|
|
// 3. 记录可用域名到日志,用于调试
|
|
// \think\facade\Log::info('允许的域名列表: ' . json_encode($allowedDomains));
|
|
|
|
return $allowedDomains;
|
|
}
|
|
|
|
|
|
/**
|
|
* 检查域名是否匹配
|
|
*
|
|
* @param string $pattern 配置中的域名模式
|
|
* @param string $domain 当前请求的域名
|
|
* @return bool 是否匹配
|
|
*/
|
|
protected function domainMatch($pattern, $domain)
|
|
{
|
|
// 完全匹配
|
|
if (strtolower($pattern) === strtolower($domain)) {
|
|
return true;
|
|
}
|
|
|
|
// 通配符匹配 (例如: *.example.com)
|
|
if (strpos($pattern, '*') !== false) {
|
|
$pattern = str_replace('*', '.*', $pattern);
|
|
$pattern = '/^' . str_replace('.', '\.', $pattern) . '$/i';
|
|
return preg_match($pattern, $domain);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 返回错误信息
|
|
*
|
|
* @param string $msg 错误信息
|
|
* @param int $code 错误码
|
|
* @return \think\Response
|
|
*/
|
|
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', // 调试接口
|
|
];
|
|
|
|
// 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;
|
|
}
|
|
}
|