第一次在项目中遇到跨域报错时,那个红色错误提示让我记忆犹新。浏览器控制台赫然显示着"Access-Control-Allow-Origin"的错误,前端同事说接口调不通,后端同事坚称接口没问题。这就是典型的跨域问题现场。
现代Web开发中,前后端分离架构已成为主流。前端可能运行在http://localhost:3000,而后端API部署在https://api.example.com。当浏览器尝试从前端向不同域名、端口或协议的后端发起请求时,就会触发同源策略的限制。这个安全机制本意是好的,但却给开发带来了实际困扰。
要让Nginx支持跨域,主要靠这几个响应头:
nginx复制add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
这里有个坑我踩过:add_header指令默认只在200、204、206等成功状态码下生效。如果接口返回404或500错误,这些头就不会被添加。解决方法是在server块顶部添加:
nginx复制add_header 'Access-Control-Allow-Origin' '*' always;
对于复杂请求(如Content-Type为application/json的POST请求),浏览器会先发送OPTIONS预检请求。Nginx需要特殊处理:
nginx复制location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}
Access-Control-Max-Age控制预检结果缓存时间(秒),合理设置能减少不必要的OPTIONS请求。
开发环境可以用*通配符,但生产环境必须指定具体域名:
nginx复制map $http_origin $cors_origin {
default "";
"~^https://example\.com$" $http_origin;
"~^https://admin\.example\.com$" $http_origin;
}
server {
add_header 'Access-Control-Allow-Origin' $cors_origin;
}
这个配置使用map指令实现动态域名匹配,比写死域名更灵活。注意正则表达式要严格匹配,避免出现example.com.hacker.com这种子域名欺骗。
如果需要传递Cookie或Authorization头,必须做额外配置:
nginx复制add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Origin' $http_origin;
这里有个重要限制:当启用Allow-Credentials时,Allow-Origin不能使用*通配符,必须明确指定域名。
nginx -s reloadbash复制curl -I https://api.example.com/some/path
WebSocket跨域需要单独配置:
nginx复制location /socket/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
add_header 'Access-Control-Allow-Origin' $http_origin;
}
字体文件跨域需要额外MIME类型支持:
nginx复制location ~* \.(eot|ttf|woff|woff2)$ {
add_header 'Access-Control-Allow-Origin' '*';
}
对于多个API路径,可以使用include指令:
nginx复制# cors.conf
add_header 'Access-Control-Allow-Origin' $cors_origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
# nginx.conf
location /api/ {
include cors.conf;
proxy_pass http://backend;
}
适当增大Access-Control-Max-Age值(默认5秒):
nginx复制add_header 'Access-Control-Max-Age' 86400; # 24小时
但要注意:如果跨域配置需要频繁变更,这个值不宜设置过大。
nginx复制map $http_origin $cors_origin {
default "";
"~^https://(www\.)?example\.com$" $http_origin;
"~^https://staging\.example\.com$" $http_origin;
}
server {
listen 443 ssl;
server_name api.example.com;
# SSL配置省略...
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With';
add_header 'Access-Control-Max-Age' 86400;
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' $cors_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Expose-Headers' 'X-Total-Count';
proxy_pass http://backend;
proxy_set_header Host $host;
}
}
在Kubernetes Ingress中的注解方式:
yaml复制nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "https://example.com"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-headers: "DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization"
Access-Control-*系列头bash复制# 测试简单请求
curl -H "Origin: https://example.com" -I https://api.example.com/data
# 测试预检请求
curl -X OPTIONS -H "Origin: https://example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
-I https://api.example.com/data
Access-Control-Allow-Origin: *和Access-Control-Allow-Credentials: true的组合Vary: Origin头避免缓存污染:nginx复制add_header 'Vary' 'Origin';
如果需要支持大量动态域名,可以使用正则匹配:
nginx复制map $http_origin $cors_origin {
default "";
"~^https://([a-z0-9-]+\.)?example\.com$" $http_origin;
"~^https://partner-domain\.com$" $http_origin;
}
正确处理CORS头与缓存头的交互:
nginx复制location /static/ {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Cache-Control' 'public, max-age=31536000';
expires 1y;
}
nginx复制add_header 'Content-Security-Policy' "default-src 'self'; script-src 'self' https://cdn.example.com";
nginx复制add_header 'X-Frame-Options' 'SAMEORIGIN';
add_header 'X-Content-Type-Options' 'nosniff';
add_header 'Referrer-Policy' 'strict-origin-when-cross-origin';
这些安全头与CORS配置共同构成了完整的Web应用安全防线。