Nginx反向代理下Access-Control-Allow-Origin多值CORS错误修复咨询
问题背景
我的架构配置如下:
- React应用部署在测试域名amplifyapp.com
- Keycloak授权服务器
- 部署在Nginx反向代理后的Spring Gateway
Nginx用作反向代理卸载SSL,配置如下:
server { server_name api1.domain.org; index index.html index.htm; access_log /var/log/nginx/bmiapp.log; error_log /var/log/nginx/bmiapp-error.log error; set $cors_credentials ''; set $cors_content_type ''; set $cors_content_length ''; if ($http_origin ~ '.+') { set $cors_credentials 'true'; } if ($request_method = OPTIONS) { set $cors_content_type 'text/plain'; set $cors_content_length '0'; } # empty header will not be added add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials $cors_credentials always; add_header Access-Control-Allow-Methods $http_access_control_request_method always; add_header Access-Control-Allow-Headers $http_access_control_request_headers always; add_header Content-Type $cors_content_type; add_header Content-Length $cors_content_length; if ($request_method = OPTIONS) { return 204; } location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_pass http://ip:8134; proxy_redirect off; } listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/api1.domain.org/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/api1.domain.org/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } server { server_name api2.domain.org; index index.html index.htm; access_log /var/log/nginx/bmiapp.log; error_log /var/log/nginx/bmiapp-error.log error; set $cors_credentials ''; set $cors_content_type ''; set $cors_content_length ''; if ($http_origin ~ '.+') { set $cors_credentials 'true'; } if ($request_method = OPTIONS) { set $cors_content_type 'text/plain'; set $cors_content_length '0'; } # empty header will not be added add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials $cors_credentials always; add_header Access-Control-Allow-Methods $http_access_control_request_method always; add_header Access-Control-Allow-Headers $http_access_control_request_headers always; add_header Content-Type $cors_content_type; add_header Content-Length $cors_content_length; if ($request_method = OPTIONS) { return 204; } location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_pass http://ip:8138; proxy_redirect off; } listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/api2.domain.org/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/api2.domain.org/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } server { if ($host = api1.domain.org) { return 301 https://$host$request_uri; } # managed by Certbot server_name api1.domain.org; listen 80; return 404; # managed by Certbot } server { if ($host = api2.domain.org) { return 301 https://$host$request_uri; } # managed by Certbot server_name api2.domain.org; listen 80; return 404; # managed by Certbot }
调用后端接口时出现以下跨域错误:
Access to XMLHttpRequest at 'https://api2.domain.org/api/microservice/dashboard/test' from origin 'http://localhost:3000' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:3000, http://localhost:3000', but only one is allowed.
问题原因
错误核心是Nginx和后端Spring Gateway(或下游微服务)同时设置了Access-Control-Allow-Origin响应头,导致浏览器接收到两个相同的Origin值,触发CORS规则拦截。
解决方案
只需选择以下一种方案执行即可,不要同时启用两端的CORS配置。
方案一:保留Nginx的CORS配置,关闭后端CORS设置
- 检查Spring Gateway或下游微服务中的CORS配置,包括:
- 代码中的
@CrossOrigin注解 - 全局CORS配置类(如
CorsConfig) application.yml/application.properties中的Spring Gateway CORS配置
将上述配置全部禁用或删除。
- 代码中的
- 确保Nginx配置中的
always参数保留,该参数能保证错误响应也会带上CORS头,避免跨域问题。
方案二:关闭Nginx的CORS配置,由后端统一处理
- 修改Nginx的
api1.domain.org和api2.domain.org两个server块,删除以下所有CORS相关代码:set $cors_credentials ''; set $cors_content_type ''; set $cors_content_length ''; if ($http_origin ~ '.+') { set $cors_credentials 'true'; } if ($request_method = OPTIONS) { set $cors_content_type 'text/plain'; set $cors_content_length '0'; } # empty header will not be added add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials $cors_credentials always; add_header Access-Control-Allow-Methods $http_access_control_request_method always; add_header Access-Control-Allow-Headers $http_access_control_request_headers always; add_header Content-Type $cors_content_type; add_header Content-Length $cors_content_length; if ($request_method = OPTIONS) { return 204; } - 在Spring Gateway中配置全局CORS规则,示例如下:
代码配置方式
@Configuration public class CorsConfig { @Bean public CorsWebFilter corsWebFilter() { CorsConfiguration config = new CorsConfiguration(); // 允许携带Cookie config.setAllowCredentials(true); // 指定允许的源,生产环境建议替换为具体域名 config.addAllowedOrigin("http://localhost:3000"); config.addAllowedOrigin("https://xxx.amplifyapp.com"); // 允许所有请求方法 config.addAllowedMethod("*"); // 允许所有请求头 config.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); } }YAML配置方式
spring: cloud: gateway: globalcors: cors-configurations: '[/**]': allowed-origins: - "http://localhost:3000" - "https://xxx.amplifyapp.com" allowed-methods: "*" allowed-headers: "*" allow-credentials: true
注意事项
- 生产环境禁止使用
*作为允许的Origin,必须指定具体的业务域名,避免安全风险。 - 修改Nginx配置后,执行
sudo nginx -s reload命令使配置生效。 - 测试时建议清空浏览器缓存,避免旧的CORS缓存影响结果。
内容的提问来源于stack exchange,提问作者Peter Penzov
相关产品推荐
相关产品推荐

