CORS、转发头和安全响应头解决的是三个不同问题:CORS 决定浏览器是否允许前端读取跨源响应;Forwarded Headers 决定应用是否相信代理报告的客户端 IP 与协议;安全响应头约束浏览器如何解释响应。它们都在 Host 组合根配置,业务模块不应各自复制一套策略。
请求进入 Host 的顺序
API 的关键顺序是 UseForwardedHeaders → ForwardedPortHostMiddleware → 异常处理与关联标识 → UseCors → Scalar CSP(文档面开启时)→ SecurityHeadersMiddleware → 认证。Forwarded Headers 必须在依赖 RemoteIpAddress 或 IsHttps 的组件之前运行;端口回写必须紧跟可信代理处理;CORS 必须位于认证之前。
SLB / CDN / OpenResty │ X-Forwarded-For / X-Forwarded-Proto / X-Forwarded-Host / X-Forwarded-Port ▼可信代理校验 → 公开端口回写 → CORS 预检 → 安全响应头 → Authentication → TenantGateway 也执行可信代理解析和安全头注入。中间件采用“已有同名头则不覆盖”的规则,因此边缘层可以给出更严格策略,API 仍为受控直连提供纵深防御。
配置生产 CORS
生产环境应列出完整 Origin。AllowedMethods 和 AllowedHeaders 不配置时允许任意方法或头;PreflightMaxAgeHours 最小为一小时;AllowCredentials 只有显式开启才生效。
{ "Cors": { "AllowedOrigins": [ "https://console.example.com", "https://portal.example.com" ], "AllowedMethods": [ "GET", "QUERY", "POST", "PUT", "PATCH", "DELETE", "OPTIONS" ], "AllowedHeaders": [ "Authorization", "Content-Type", "X-Client-Platform", "traceparent", "correlationId", "Idempotency-Key" ], "AllowCredentials": true, "PreflightMaxAgeHours": 1 }}Origin 包含协议、主机和端口,不包含路径。未配置任何 Origin 时策略默认拒绝跨域,但不会阻止 Host 启动。Development 在未显式配置 AllowAnyOrigin 时默认开放任意来源;生产不会自动开放。
QUERY 不在 CORS safelisted methods 中,分页、列表和复杂只读请求会先发 OPTIONS 预检。方法白名单漏掉 QUERY 时,@bitz/platform-sdk 会在浏览器报告网络错误后尝试 POST /_query;这能维持业务可用性,但会掩盖边缘层的配置缺口。生产验收仍须证明原生 QUERY 能通过真实 CDN、WAF、Ingress 与 Gateway。两种传输的差异见 QUERY 请求协议。
配置可信代理拓扑
API 处理 X-Forwarded-For、X-Forwarded-Proto 与 X-Forwarded-Host,紧随其后的端口中间件在可信条件成立时处理 X-Forwarded-Port。这些值共同决定 HTTPS、Cookie Origin 和 Scalar 调试 Server 使用的 Scheme/Host。ForwardedHeaders:ForwardLimit 控制采纳跳数:仓库 appsettings 默认常为 1;OpenResty → Gateway → API 两跳预览应设为 2。已知代理与网络会先清空再由配置重建,避免框架默认信任范围被意外保留。
{ "ForwardedHeaders": { "ForwardLimit": 2, "KnownProxies": ["10.20.0.10", "127.0.0.1"], "KnownNetworks": ["10.30.0.0/16"], "DirectExposure": false }}无效 IP 或 CIDR 会在配置绑定时抛出异常。Production 与 Staging 还要求至少配置一个 KnownProxy/KnownNetwork,或明确声明 DirectExposure=true;没有拓扑声明时 Runtime Configuration Guard 拒绝启动。
直接相信公网传入的 X-Forwarded-* 会让攻击者伪造客户端地址、Host 或端口,进而影响 IP 限流、封禁、审计、登录 Cookie Origin 与文档调试地址。配置值必须描述“紧邻应用的可信上一跳”,而不是把全部互联网网段加入清单。浏览器 refresh-cookie 校验只使用 UseForwardedHeaders 与端口中间件校正后的 Scheme://Host,不会把未经验证的原始头当作可信 Origin。
OpenResty/Nginx 应使用 $http_host 填充 Host 与 X-Forwarded-Host,并单独发送 X-Forwarded-Port $server_port。$host 可能丢失 8088 一类非默认端口,使 Scalar 把调用发往 80/443。具体配置与回退语义见 OpenAPI 与 Scalar 文档面。
配置安全响应头
SecurityHeadersMiddleware 使用可热更新的 Security:Headers。默认开启 HSTS、nosniff、禁止 iframe、严格 referrer、同源 CSP,并禁用位置、麦克风和摄像头。
{ "Security": { "Headers": { "Enabled": true, "EnableHsts": true, "HstsMaxAgeSeconds": 63072000, "HstsIncludeSubDomains": true, "HstsPreload": false, "ContentTypeOptions": "nosniff", "FrameOptions": "DENY", "ReferrerPolicy": "strict-origin-when-cross-origin", "ContentSecurityPolicy": "default-src 'self'; script-src 'self'; connect-src 'self' https://api.example.com", "PermissionsPolicy": "geolocation=(), microphone=(), camera=()" } }}HSTS 只有在请求经 Forwarded Headers 校正后被识别为 HTTPS、EnableHsts=true 且 max-age 大于零时才注入。启用 includeSubDomains 或 preload 前,必须证明所有子域都永久支持 HTTPS;预加载是浏览器厂商级承诺,不能只靠一次配置回滚撤销。
CSP 的落地方法
先以实际前端依赖建立最小清单,再按 script-src、style-src、img-src、font-src、connect-src 分类开放。默认策略为兼容 SPA 在 style 中保留 'unsafe-inline',这是一项兼容性取舍,不应被描述成最高强度基线。
生产变更应先在非阻断环境收集违规报告,再收紧策略。不要用 default-src *、长期开放 'unsafe-eval',也不要因为一个第三方组件报错就扩大所有资源类型。
# 验证实际响应头;使用真实入口域名才能覆盖 CDN/SLB 与 Gateway。curl -sS -D - -o /dev/null https://api.example.com/health/live
# 模拟合法 Origin 的预检请求,核对 allow-origin/method/header。curl -i -X OPTIONS https://api.example.com/api/tickets \ -H 'Origin: https://console.example.com' \ -H 'Access-Control-Request-Method: QUERY' \ -H 'Access-Control-Request-Headers: authorization,content-type,x-client-platform'自动化契约测试
测试至少覆盖合法 Origin、未知 Origin、带凭据策略、HTTPS/HSTS 和已存在响应头不被覆盖。下面的断言骨架表达期望边界,测试夹具应使用仓库统一的 API factory。
// 使用未知 Origin 发出预检,服务可以响应,但不能授予该 Origin。request.Headers.Add("Origin", "https://evil.example");request.Headers.Add("Access-Control-Request-Method", "QUERY");var response = await client.SendAsync(request);
// 拒绝证据是缺少授权响应头,不能只断言状态码。response.Headers.TryGetValues("Access-Control-Allow-Origin", out _) .ShouldBeFalse();故障定位
浏览器提示 CORS 时先看 OPTIONS 是否到达正确 Host,再核对 Origin 字符串、方法和请求头。出现重定向循环或 HSTS 缺失时,检查可信代理是否正确回写 Request.Scheme。资源被 CSP 阻断则查看浏览器控制台中的具体 directive,和 CORS 分开定位。
常见误判是只用 Postman 验证 CORS、在 API 内信任任意转发头、或把 Gateway 写出的头当作 API 自身已配置。发布证据必须从公网入口和受控直连入口各采集一次。
发布验收
- Production/Staging 的可信代理拓扑通过启动门禁;
- 合法与非法 Origin 均有预检契约测试;
- 公网入口确认 HSTS、CSP、frame、MIME、referrer 和 permissions 策略;
- IP 限流与审计读取的是经可信代理校验后的地址;
- CSP 例外有业务用途、负责人和移除条件;
- Gateway 与 API 没有冲突或意外弱化的重复响应头。