这个顺序基本合理,但建议先固定信任边界,再看每层解析结果。关键是区分“TCP 直连来源”和“代理头解析后的客户端”:任何转发头都不能自行成为信任依据。
1. 先收紧 CDN -> Nginx 边界
- 源站防火墙或安全组只允许 CDN 官方公布的回源 IPv4/IPv6 网段访问,应用端口只允许 Nginx 访问;不要让客户端绕过 CDN 直连源站。
- CDN 边缘应删除客户端自带的真实 IP 头和
X-Forwarded-Proto,再由 CDN 覆盖写入。确认供应商规定的真实 IP 头名称、值的语义,以及它对 X-Forwarded-For 是覆盖还是追加。
set_real_ip_from 只放 CDN 官方出口网段,并建立网段更新、nginx -t 和 reload 流程;不要配置成任意来源。优先使用 CDN 的单值真实 IP 头。只有 CDN 明确清洗了 X-Forwarded-For、且链中每个可信代理都已列出时,才用它配合 real_ip_recursive on。
可临时增加不含 Cookie/Authorization 的诊断日志:
log_format trust_chain '$request_id peer=$realip_remote_addr client=$remote_addr '
'xff_in="$http_x_forwarded_for" proto_in="$http_x_forwarded_proto"';
access_log /var/log/nginx/trust-chain.log trust_chain;
启用 realip 后,逐请求核对:$realip_remote_addr 应是实际连接 Nginx 的 CDN 节点且落在官方网段内;$remote_addr 应是经可信 CDN 头恢复出的客户端地址。两者不能只看一个。若使用供应商单值头,配置骨架是:
set_real_ip_from <CDN_OFFICIAL_CIDR>;
real_ip_header <CDN_CANONICAL_REAL_IP_HEADER>;
real_ip_recursive off;
2. 再规范 Nginx -> 应用的头
Nginx 已恢复客户端地址后,若应用只需要最终客户端 IP,最清晰的做法是由 Nginx覆盖输出:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $trusted_external_proto;
这里不直接用 $proxy_add_x_forwarded_for,因为入站链可能已含客户端地址,而 realip 又把 $remote_addr 改成了客户端地址,继续追加容易得到重复值。若确实要保留完整审计链,应先定义每一段的生成者和清洗规则,再显式重建;CDN 节点地址可保留在 Nginx 的 $realip_remote_addr 日志中,不必交给应用自行猜链。
X-Forwarded-Proto 必须来自可信 CDN:
- 外部入口只提供 HTTPS 时,可由 Nginx固定写成
https。
- 同时支持 HTTP/HTTPS 时,只接受 CDN 从外部连接生成的
http 或 https,拒绝其他值,再规范化写给应用。
- CDN 到 Nginx 若是 HTTP,
$scheme 只表示这一段回源协议,会错误地得到 http;此时不能用 $scheme 覆盖外部协议。
- 上述传递成立的前提仍是实际连接来源已由源站网络边界确认为 CDN。不要从任意来源原样透传客户端提交的
X-Forwarded-Proto。
3. 应用只信任直接代理
应用或框架的 trusted proxy 应限定为 Nginx 的实际私网地址或最小 CIDR,并确保应用端口不能被其他来源访问。优先按明确地址信任,不要使用全局信任,也不要仅凭固定“代理层数”处理一个可能存在绕行路径的入口。应用侧同时记录:socket peer、框架解析出的客户端 IP、解析后的 scheme、Host 以及收到的规范化代理头。通过标准应是:socket peer 始终为 Nginx,客户端 IP 与 Nginx 的 $remote_addr 一致,scheme 与用户外部访问协议一致。
4. 最后按 Secure -> SameSite -> Domain -> Path 查 Cookie
先用浏览器 Network/Application(或同等工具)确认登录响应是否收到 Set-Cookie、浏览器是否拒收,以及下一跳请求是否实际带出 Cookie:
Secure:外部为 HTTPS 且应用已正确识别 HTTPS 后再核对;若 scheme 仍解析成 HTTP,框架可能不发安全 Cookie或生成错误跳转。
SameSite:普通同站登录优先核对 Lax 是否足够;确有跨站回调或嵌入需求才使用 None,并同时设置 Secure。
Domain:优先省略以得到 host-only Cookie;若显式设置,必须与实际访问主机匹配,不能带协议或端口,并检查登录前后是否发生了主机名跳转。
Path:通常应为 /;确认后续接口路径落在其覆盖范围内,并排除同名 Cookie 在不同 Domain/Path 下互相干扰。
5. 做三组可重复核对
# 正常经 CDN 请求,使用同一个请求 ID 对齐 CDN、Nginx、应用日志
curl -sS -D - -o /dev/null https://PUBLIC_HOST/check
# 经 CDN 注入伪造值;最终客户端 IP/协议不应受这两个值控制
curl -sS -D - -o /dev/null https://PUBLIC_HOST/check \
-H 'X-Forwarded-For: 198.51.100.77' \
-H 'X-Forwarded-Proto: http'
# 尝试绕过 CDN 直达源站;网络边界应拒绝该路径
curl -sS -D - -o /dev/null --resolve PUBLIC_HOST:443:ORIGIN_IP https://PUBLIC_HOST/check
不要预设这些测试会通过;以三层日志和浏览器 Cookie 拒收原因逐项确认。最终边界应是:CDN 清洗客户端头,Nginx 只接受官方 CDN 来源并覆盖发往应用的头,应用只信任直接连接它的 Nginx。这样即使客户端主动提交转发头,也无法跨过任一层的信任边界。