简介
本技术文档围绕 DouPHP 的“代理信任中间件”展开,系统性解释其工作原理与代理信任机制,包括:
- IP 地址验证与可信代理判定
- 协议检查(HTTPS 检测)与请求转发安全
- 标准代理头部字段的解析与验证(X-Forwarded-For、X-Forwarded-Proto 等)
- 如何配置可信代理列表与 Host 白名单
- HTTPS 重定向与协议检测的实现细节
- 完整配置示例与安全最佳实践
- 面向初学者的概念说明与面向高级开发者的自定义扩展指南
项目结构
DouPHP 将代理信任能力以“中间件 + 请求对象 + 初始化阶段注入配置”的方式实现:
- 中间件层:TrustProxyMiddleware 负责在管道最前置读取配置并设置 Request 的可信代理名单。
- 请求层:Request 提供 ip()、isSecure()、host() 等方法,结合可信代理名单决定是否采信 X-Forwarded-* 等头。
- 初始化层:InitTrait 在构造审计服务之前加载 security.php 并设置可信代理与 Host 白名单,确保早期调用不会误信伪造头。
- 路由层:各端 Resolver 将 trust_proxy 别名映射到 TrustProxyMiddleware,并在默认中间件栈中启用。
graph TB
A["客户端请求"] --> B["Nginx/负载均衡(反向代理)"]
B --> C["DouPHP 入口"]
C --> D["InitTrait::loadSecurityConfig()<br/>加载 security.php<br/>设置 Request 可信代理/Host"]
D --> E["中间件管道: SecurityHeaders -> TrustProxy -> Throttle -> ..."]
E --> F["控制器/业务逻辑"]
F --> G["Response 输出"]
核心组件
- TrustProxyMiddleware:从配置读取 trusted_proxies,调用 Request::setTrustedProxies(),幂等保证即使 Init 顺序异常也能生效。
- Request:封装客户端 IP、协议、主机名的获取逻辑;仅在 REMOTE_ADDR 命中可信代理时,才采信 X-Forwarded-For / X-Forwarded-Proto。
- InitTrait:在最早阶段加载 security.php 并设置可信代理与 Host 白名单,避免在审计日志或鉴权前被伪造头污染。
- 安全响应头中间件基类:根据 security.headers 下发基线安全头,HSTS 仅在 HTTPS 且开启时下发。
架构总览
下图展示了请求进入后,如何通过初始化阶段和中间件链完成代理信任配置与后续处理。
sequenceDiagram
participant Client as "客户端"
participant Proxy as "反向代理/负载均衡"
participant App as "DouPHP 应用"
participant Init as "InitTrait"
participant MW as "中间件管道"
participant Req as "Request"
Client->>Proxy : HTTP/HTTPS 请求
Proxy->>App : 转发请求(可能携带 X-Forwarded-*)
App->>Init : loadSecurityConfig()
Init-->>Req : setTrustedProxies()/setTrustedHosts()
App->>MW : 执行中间件链
MW->>Req : TrustProxyMiddleware 再次断言 trusted_proxies
Note over Req,Proxy : 仅当 REMOTE_ADDR 命中可信代理时,才采信 X-Forwarded-*
MW-->>App : 继续处理业务
App-->>Client : 返回响应(可带安全头/HSTS)
详细组件分析
TrustProxyMiddleware:代理信任中间件
- 职责:在中间件管道最前置读取配置中的 trusted_proxies,并写入 Request。
- 设计要点:
- 幂等性:即便 Init 阶段已设置过,中间件再次断言,确保语义集中可读。
- 安全性:默认不信任任何代理(trusted_proxies 为空),必须显式配置可信代理出口 IP/CIDR。
- 影响范围:决定后续 Request::ip() 与 Request::isSecure() 是否采信 X-Forwarded-* 头。
flowchart TD
Start(["进入 TrustProxyMiddleware"]) --> ReadCfg["读取 config/security.php<br/>security.trusted_proxies"]
ReadCfg --> SetReq["调用 Request::setTrustedProxies()"]
SetReq --> Next["调用下一个中间件"]
Next --> End(["结束"])
Request:IP、协议与主机名可信判定
- IP 获取(ip()):
- 候选来源:X-Forwarded-For(逗号分隔,左到右)、HTTP_CLIENT_IP、REMOTE_ADDR。
- 可信判定:仅当 REMOTE_ADDR 命中 trusted_proxies(精确 IP 或 CIDR)时,才展开 X-Forwarded-*;否则仅使用 REMOTE_ADDR。
- IPv4/IPv6 归一化:优先返回 IPv4(含 ::ffff:x.x.x.x 映射),其次 IPv6,兜底首段原始字符串。
- 协议检测(isSecure()):
- 优先判断 SERVER_PORT=443 或 HTTPS 非 off。
- 若来自可信代理且存在 X-Forwarded-Proto,取最左侧值(多级代理场景),为 https 则视为 HTTPS。
- 主机名(host()):
- 支持 trusted_hosts 白名单校验,未命中时回落到白名单首项,防止 Host 头注入污染对外 URL。
flowchart TD
S(["Request::ip()"]) --> Collect["收集候选 IP<br/>X-Forwarded-For / HTTP_CLIENT_IP / REMOTE_ADDR"]
Collect --> Trusted{"REMOTE_ADDR 命中可信代理?"}
Trusted -- 否 --> UseRemote["仅使用 REMOTE_ADDR"]
Trusted -- 是 --> Expand["展开 X-Forwarded-For 序列"]
Expand --> Normalize["归一化 IPv4/IPv6"]
Normalize --> ReturnIP["返回首个有效 IP"]
UseRemote --> ReturnIP
初始化阶段:安全配置注入
- 在构造 AuditService($request->ip()) 之前,先加载 security.php 并设置 Request 的可信代理与 Host 白名单,避免伪造头污染审计日志或鉴权流程。
- 中间件层再次幂等断言,确保即使 Init 顺序异常也不致漏设。
安全响应头与 HSTS
- AbstractSecurityHeadersMiddleware 根据 security.headers 下发基线安全头(X-Content-Type-Options、X-Frame-Options、Referrer-Policy、Permissions-Policy)。
- HSTS 仅在 HTTPS 且配置开启时下发,避免在非 HTTPS 环境错误启用。
依赖关系分析
- 中间件注册:
- 后台端:AdminResolver 将 trust_proxy 别名映射到 TrustProxyMiddleware。
- 前台端:FrontResolver 默认中间件栈包含 security_headers、trust_proxy、throttle,可选 user_auth、csrf。
- 配置依赖:
- security.php 提供 trusted_proxies、trusted_hosts、headers、session 等安全相关配置。
- 运行时依赖:
- Request 在初始化阶段被注入可信代理与 Host 白名单,后续所有 ip()/isSecure()/host() 均受其影响。
graph LR
Sec["security.php"] --> Init["InitTrait::loadSecurityConfig()"]
Init --> Req["Request::setTrustedProxies()/setTrustedHosts()"]
Admin["AdminResolver"] --> MW["中间件别名映射"]
Front["FrontResolver"] --> MW
MW --> TP["TrustProxyMiddleware"]
TP --> Req
性能考量
- 可信代理匹配采用 CIDR 计算,复杂度与规则数量线性相关;建议合理划分网段,避免过多细粒度规则。
- X-Forwarded-For 解析为简单字符串分割与 trim,开销极低。
- 中间件链顺序:TrustProxy 置于限流之前,确保限流键基于可信 IP,减少误判与资源浪费。
故障排查指南
- 症状:业务侧通过 request()->ip() 获取到的不是真实客户端 IP。
- 排查点:确认 REMOTE_ADDR 是否为反向代理出口 IP;确认 security.trusted_proxies 是否包含该 IP/CIDR。
- 参考:Request::collectClientIpCandidates() 与 isFromTrustedProxy()。
- 症状:HTTPS 页面仍被识别为 http。
- 排查点:确认 Nginx/负载均衡是否正确设置 X-Forwarded-Proto=https;确认 trusted_proxies 已包含代理出口 IP。
- 参考:Request::isSecure()。
- 症状:生成链接域名不正确或被篡改。
- 排查点:配置 trusted_hosts 白名单,避免未命中的 Host 头污染;必要时回落至白名单首项。
- 参考:Request::host() 与 trustedHosts()。
- 症状:审计日志记录的攻击源 IP 不准确。
- 排查点:确保 InitTrait::loadSecurityConfig() 在审计服务构造前执行;中间件层再次断言 trusted_proxies。
- 参考:InitTrait::instantiateCommonFactories()。
结论
DouPHP 的代理信任机制通过“初始化阶段注入 + 中间件幂等断言 + 请求层严格校验”的组合,确保在反向代理环境下正确识别真实客户端 IP、协议与主机名,同时防范伪造 X-Forwarded-* 头的风险。默认“不信任任何代理”的安全基线,要求部署方明确配置可信代理出口,从而在可用性与安全性之间取得平衡。
附录:配置示例与安全最佳实践
配置可信代理与 Host 白名单
- 在 config/security.php 中设置:
- trusted_proxies:填写反向代理出口 IP 或网段(CIDR),例如内网网段。
- trusted_hosts:填写站点规范域名与子域通配,防止 Host 头注入。
- 三端中间件链已默认包含 trust_proxy,无需额外注册。
代理头部字段处理逻辑
- X-Forwarded-For:
- 仅在 REMOTE_ADDR 命中 trusted_proxies 时才会展开;按逗号拆分,从左到右取有效 IP。
- 用于 ip() 方法,最终选择首个 IPv4(含映射)或首个 IPv6。
- X-Forwarded-Proto:
- 仅在 REMOTE_ADDR 命中 trusted_proxies 时才会采信;取最左侧值,为 https 则 isSecure() 返回 true。
- HTTP_CLIENT_IP:
- 作为备选来源之一,优先级低于 X-Forwarded-For,但仍需满足可信代理条件。
HTTPS 重定向与协议检测
- 协议检测:
- 优先依据 SERVER_PORT=443 或 HTTPS 非 off;若来自可信代理且 X-Forwarded-Proto=https,则视为 HTTPS。
- HSTS:
- 由安全响应头中间件在 HTTPS 且配置开启时下发 Strict-Transport-Security。
- 重定向:
- 业务侧可通过 redirect() 进行跳转;如需强制 HTTPS,可在网关/Nginx 层统一处理,或在应用层根据 isSecure() 做策略控制。
安全最佳实践
- 最小信任原则:
- 仅将反向代理出口 IP/CIDR 加入 trusted_proxies,避免宽泛网段导致信任面过大。
- 白名单 Host:
- 配置 trusted_hosts,防止 Host 头注入污染外链、邮件、缓存等。
- 网关层防护:
- 在 Nginx/负载均衡层清理不可信的头,只允许可信代理注入 X-Forwarded-*。
- 审计与监控:
- 关注审计日志中的 IP 来源,确保与 trusted_proxies 一致;对异常 IP 进行告警。
- 渐进启用 HSTS:
- 先在测试环境验证 HTTPS 链路,再在生产环境启用 HSTS,避免回滚困难。
初学者概念说明
- 什么是代理信任?
- 当网站部署在反向代理之后,服务器收到的直接连接来自代理,而非真实客户端。为了获得真实客户端信息,代理会添加 X-Forwarded-* 头。但这类头可能被恶意伪造,因此需要“信任名单”来限定哪些代理可以注入这些头。
- 为什么重要?
- 错误的 IP/协议/主机名会导致访问统计失真、限流失效、外链生成错误、会话劫持等安全问题。通过可信代理机制,可以在保障功能的同时降低安全风险。
高级开发者扩展指南
- 自定义代理验证逻辑:
- 可在 Request 中扩展 isFromTrustedProxy() 的判断逻辑,增加更多维度(如来源端口、TLS 指纹、证书校验等)。
- 或通过中间件链追加自定义校验步骤,在 TrustProxy 之后进一步过滤可疑请求。
- 多环境差异化配置:
- 在不同环境(开发/测试/生产)维护不同的 security.php,严格控制 trusted_proxies 与 trusted_hosts。
- 与限流/认证联动:
- 确保 TrustProxy 位于限流之前,使限流键基于可信 IP;认证模块可结合 host() 与 scheme() 做更精细的策略控制。