必须绕过默认$_server取值逻辑,结合可信代理、host解析、ua/referer校验、自定义头验证及source_tag日志组合实现多维请求来源识别与防护。

如果您在 ThinkPHP 应用中需要识别并校验请求的真实来源,防止伪造 Referer、恶意 UA 或非法子域名调用,则必须绕过默认的 $_SERVER 取值逻辑,结合反向代理环境与可信头字段进行多维验证。以下是实现请求来源验证的具体配置与操作步骤:
一、配置可信代理并获取真实客户端 IP
直接读取 $_SERVER['REMOTE_ADDR'] 在 CDN、Nginx 反向代理或容器部署下会返回网关地址而非真实用户 IP,必须通过 X-Real-IP 或 X-Forwarded-For 头配合可信代理白名单提取。ThinkPHP 的 request()->ip() 方法已内置代理处理逻辑,但默认仅信任 127.0.0.1 和 ::1,线上需显式声明内网段。
1、打开 config/app.php 文件,在数组中添加或修改 trusted_proxies 配置项;
2、将实际部署环境中的负载均衡器、CDN 回源网段填入,例如:'trusted_proxies' => ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'];
3、确保 Nginx 配置中已设置 proxy_set_header X-Real-IP $remote_addr;,且未允许前端透传该头;
4、在业务代码中统一使用 $this->request->ip() 获取真实 IP,禁用 $_SERVER['REMOTE_ADDR']。
二、通过 Host 与子域名识别业务来源
域名是区分租户、站点或调用方身份的关键标识,但直接解析 HTTP_HOST 易受伪造和代理干扰。ThinkPHP 提供 request()->host() 和 request()->subDomain() 方法,其可靠性依赖于 url_domain_deploy 配置及 Host 头透传完整性。
1、启用子域名解析:在 config/app.php 中设置 'url_domain_deploy' => true;
2、若需手动提取主域或二级域(如 api.example.com → example.com),调用 $this->request->host() 后用 PHP 字符串函数切分,避免正则误判;
3、本地开发时,浏览器不支持 sub.localhost,须在 hosts 文件中绑定真实测试域名(如 127.0.0.1 api.test.com);
4、禁止在控制器中使用 $_SERVER['HTTP_HOST'] 拼接协议生成跳转 URL,应改用 $this->request->domain()。
三、提取并校验 User-Agent 与 Referer 头
User-Agent 可识别调用终端类型(小程序、APP、浏览器、curl),Referer 则用于判断是否来自可信页面跳转。二者均属客户端可控字段,不可作为唯一认证依据,但可用于辅助标记与灰度分流。
1、在中间件中调用 $this->request->header('user-agent') 获取原始 UA 字符串;
2、使用 strpos() 或正则匹配关键词,例如检测微信小程序:stripos($ua, 'miniProgram') !== false;
3、对 Referer 进行白名单校验:先检查 $this->request->header('referer') 是否非空,再比对是否以预设域名开头(如 https://admin.example.com);
4、禁止将 Referer 直接写入日志或响应体,防止反射型 XSS,需先 htmlspecialchars($referer, ENT_QUOTES, 'UTF-8') 转义。
四、注入自定义来源标识头并强制校验
第三方系统调用 API 时,应在请求头中携带唯一标识(如 X-App-ID、X-Client-Type),服务端通过中间件统一提取、格式校验与白名单比对,拒绝无标识或非法标识的请求。
1、新建中间件 app/middleware/SourceHeaderCheck.php;
2、在 handle() 方法中调用 $this->request->header('x-app-id') 获取应用 ID;
3、校验其是否为 UUID 格式:filter_var($appId, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i']]);
4、查询数据库或配置文件确认该 App ID 是否启用且归属有效租户,失败则抛出 HttpException(403);
5、将校验结果存入请求属性:$request->withAttr('source_app_id', $appId),供后续控制器使用。
五、组合生成 source_tag 并写入访问日志
单一维度不足以准确刻画请求来源,应将 IP、UA 类型、Referer 域名、自定义头等字段组合为结构化标签(source_tag),统一记录至日志,支撑审计与溯源。
1、在 SourceHeaderCheck 或独立追踪中间件中构造标签字符串,例如:sprintf('%s-%s@%s', $clientType, $appVersion, $realIp);
2、对公网 IP 执行脱敏处理:preg_replace('/\.\d+$/','.*', $realIp);
3、截断过长 UA 字符串,限制长度不超过 255 字符:mb_strimwidth($ua, 0, 255, '...', 'UTF-8');
4、将 source_tag 写入日志上下文:$this->log->record(['source_tag' => $tag]);
5、确保日志配置中已关闭敏感头字段(如 Authorization、X-API-Key)的自动记录,防止密钥泄露。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











