必须放弃通配符*,改用动态白名单策略:对带凭证请求需指定确切源,用map指令匹配可信域名;预检请求用if拦截并返回204;暴露头需显式声明且避开敏感字段。

直接在 Nginx 中硬写 Access-Control-Allow-Origin: * 看似简单,但只要涉及用户凭证(如 Cookie、Authorization 头)、多前端域名或精细化权限控制,就必须放弃通配符,改用动态、可验证、有边界的策略。Nginx 本身不校验请求来源,它只负责“如实声明”,而声明错了,浏览器就会静默拦截响应——前端报错却看不到后端日志里的成功返回,这是最典型的排查陷阱。
明确区分是否携带凭证
这是所有配置的起点。一旦前端请求设置了 credentials: true(例如 fetch 带了 { credentials: 'include' }),后端响应中的 Access-Control-Allow-Origin 就绝不能是 *,否则浏览器直接拒绝暴露响应体。
- 允许带 Cookie 的跨域:必须指定确切源,比如
https://app.company.com或https://dashboard.company.net - 不带凭证的公开接口(如公共数据查询):可用
*,但需确认业务上确实无需身份上下文 - 如果前后端同属一个主域(如
api.example.com和www.example.com),可考虑使用Access-Control-Allow-Origin: $http_origin配合白名单校验,更安全
用 map 指令支持多个可信前端域名
Nginx 原生不支持在 add_header 中写多个值,但可通过 map 指令实现运行时匹配,避免重复 server 块或硬编码判断。
在 http 块顶部添加:
map $http_origin $cors_origin {
default "";
"~^https?://(app|admin|staging)\.company\.com$" "$http_origin";
"~^https?://localhost:3000$" "$http_origin";
}
然后在对应 location 中使用:
add_header 'Access-Control-Allow-Origin' $cors_origin;-
add_header 'Access-Control-Allow-Credentials' 'true';(仅当 $cors_origin 非空时才生效) - 若
$cors_origin为空(即来源不在白名单),Nginx 不会添加该头,浏览器自然拒绝跨域
正确处理 OPTIONS 预检请求
浏览器对非简单请求(如含自定义 header、PUT/DELETE 方法、Content-Type 为 application/json)会先发一次 OPTIONS 请求。Nginx 必须快速响应,且不能把预检请求转发给后端。
- 用
if ($request_method = 'OPTIONS') { return 204; }终止流程,避免穿透到应用层 - 在 return 前补全必要响应头:
Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Max-Age - 不要在预检响应里加
Access-Control-Allow-Credentials—— 它只用于实际请求的响应头
暴露关键响应头并限制暴露范围
前端 JS 默认只能读取少数基础响应头(如 Content-Type)。如果业务需要访问 X-Request-ID、Retry-After 或分页字段 X-Total-Count,必须显式声明:
add_header 'Access-Control-Expose-Headers' 'X-Request-ID, X-Total-Count, Retry-After';- 避免暴露敏感头,如
Set-Cookie、Authorization、Server—— 这些不应出现在Expose-Headers中 - 若后端已设置
Set-Cookie,且前端需接收,确保Access-Control-Allow-Credentials true与精确Allow-Origin同时存在


















