12 防范测试正常、上线失败
1. 为什么安全 Header 特别容易出现环境差异
开发环境通常是:
http://localhost:5173 -> http://localhost:8080
生产环境可能增加:
- HTTPS 和不同子域名;
- Nginx、CDN、WAF;
- Cookie 的
Secure、SameSite和 Domain; - 更严格的 Helmet/CSP;
- OAuth、支付、对象存储、字体等第三方来源;
- 代理缓存和多实例部署。
因此“本地 Fetch 成功”只覆盖很小一部分链路。
2. 不要在生产一次性打开所有严格策略
尤其是 CSP,应先使用 Report-Only:
Content-Security-Policy-Report-Only: default-src 'self'; connect-src 'self' https://api.example.com
它记录违规但暂不阻止资源。收集真实页面依赖后,再逐步切换为强制 CSP。报告也可能包含噪声或被扩展污染,必须结合页面回归判断。
CORP、COEP、COOP 也要围绕图片、下载、iframe、OAuth 弹窗和第三方资源逐项验证,而不是看到 Helmet 推荐就全部开启最严格值。
3. 使用环境变量维护 Origin 白名单
示例环境值:
CORS_ALLOWED_ORIGINS=https://www.example.com,https://admin.example.com
服务启动时应:
- 按逗号拆分并去除空白;
- 使用
new URL()验证; - 只保留
url.origin,拒绝带用户名密码等异常配置; - 生产环境若列表为空则启动失败,而不是自动回退到
*; - 日志记录启用的来源数量和环境,不记录敏感凭据。
4. 建立上线前响应矩阵
对每个生产入口验证:
| 维度 | 至少覆盖 |
|---|---|
| Origin | 允许、拒绝、无 Origin |
| 方法 | GET、JSON POST、PATCH/DELETE |
| 响应 | 200、业务失败、401、403、404、500 |
| 预检 | OPTIONS + Authorization/Content-Type |
| 内容 | JSON、下载、图片、HTML 预览 |
| 链路 | 直连 Express、经 Nginx、公网/CDN |
不要只检查 Header 是否“存在”,还要检查值是否唯一、是否与请求 Origin 匹配。
5. 自动化冒烟检查
正式请求:
curl -isk https://api.example.com/health \
-H "Origin: https://www.example.com"
预检:
curl -isk -X OPTIONS https://api.example.com/api/users \
-H "Origin: https://www.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: authorization,content-type"
拒绝来源:
curl -isk https://api.example.com/api/users \
-H "Origin: https://attacker.example"
-i 显示响应头,-s 关闭进度信息,-k 跳过证书校验。生产证书验收时不应使用 -k,否则会掩盖证书链和域名错误。
curl 不执行浏览器 CORS/CSP 策略,所以它适合验证服务端 Header,但最终必须用真实浏览器测试 Console 和 Network。
6. 高频生产故障
实际 Origin 与配置不同
https://example.com、https://www.example.com、http://example.com 是不同 Origin。重定向前后的响应也可能缺少 CORS Header。
Cookie 本地正常,线上未发送
检查:
- Fetch/Axios 是否开启凭据;
Allow-Credentials和精确 Origin;- Cookie
Secure、SameSite、Domain、Path; - 浏览器第三方 Cookie 策略;
- CSRF Token 是否随请求发送。
预检被认证或代理拦截
检查 OPTIONS 是否到达 Express、是否被 Nginx返回 404/405、是否先经过 JWT 中间件。
CSP 造成生产白屏
查看 Console 里的具体 directive,而不是直接关闭整个 CSP。常见遗漏有 API connect-src、CDN script-src、字体 font-src 和图片 img-src。
缓存串用 Origin
检查 Vary: Origin、CDN cache key、Nginx 缓存,以及策略更新后旧预检的 Max-Age。
重复 Header
公网响应出现两个 Access-Control-Allow-Origin 时,对比直连 Express 与经过 Nginx 的响应,确定唯一配置层。
7. 发布与回滚建议
- 在与生产拓扑相似的预发布环境验证;
- 先灰度严格 Header;
- 保留旧配置并准备快速回滚;
- 同时观察浏览器错误、Nginx 4xx/5xx、Express 错误率和 OPTIONS 比例;
- 安全策略变更与业务代码一样进入代码审查;
- 在 README 或部署文档记录每个 Header 的所有者。
8. 最终检查清单
- [ ] 生产 Origin 使用精确协议、域名和端口。
- [ ] CORS 没有被当成认证或 CSRF 防护。
- [ ] OPTIONS 位于认证之前并能正确返回。
- [ ] 成功、错误和 Nginx 代理错误都已测试。
- [ ] Cookie 模式没有使用
Allow-Origin: *。 - [ ] 动态 Origin 响应包含正确的
Vary。 - [ ] CSP
connect-src包含实际 API/WebSocket 地址。 - [ ] 下载所需 Header 已通过
Expose-Headers暴露。 - [ ] Nginx 与 Express 没有重复设置 CORS。
- [ ] 已在真实浏览器验证,而非只用 curl/Postman。