You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot+AWS环境下CORS跨域问题排查求助

问题:CORS错误排查建议

部署架构与问题现象

  • ReactJS应用部署在AWS Amplify,访问地址为https://example.com
  • Spring Boot应用部署在EC2实例,通过NGINX以api.example.com对外提供服务
  • ReactJS通过https://api.example.com/...与后端通信
  • 禁用Chrome安全设置(含CORS)时可正常获取数据,但正常Chrome/Firefox环境下出现CORS错误

已配置内容

Spring Boot CORS配置

@Bean
public CorsFilter corsFilter() throws Exception{
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOriginPatterns(List.of("*")); // Allow all origins
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    config.setAllowCredentials(true);

    config.addExposedHeader("code");
    config.addExposedHeader("reason");            

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

    source.registerCorsConfiguration("/**", config);
    log.info(new ObjectMapper().writeValueAsString(config));
    return new CorsFilter(source);
}

NGINX配置(/etc/nginx/sites-available/api.example.com)

upstream backend-service-api.example.com {
    server localhost:8080;
    #server search - service - 3: 8082;
}


server {
    server_name api.example.com;

    location /{
        proxy_pass https://example.com;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
    }

    location ~* "^/api/backapp-service(/|$)(.*)" {
    rewrite "(?i)/api/backapp-service(/|$)(.*)" /$2 break;

    # Handle preflight requests
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' 'https://example.com';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Content-Length' 0;
        add_header 'Content-Type' 'text/plain';
        return 204;                                                                                                                                      
                return 204;
    }

    # Handle actual requests
    add_header 'Access-Control-Allow-Origin' 'https://example.com';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization';
    add_header 'Access-Control-Allow-Credentials' 'true';

    proxy_pass http://backend-service-api.example.com;
}
    location ~* "^/media(/|$)(.*)" {
        rewrite "(?i)/media(/|$)(.*)" /$2 break;                                                                                                    
        root /home/ubuntu/PRODUCTION/FileServer;
    }

     error_page 401 = @error401;
    location @error401 {
        add_header 'Access-Control-Allow-Origin' 'https://example.com';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization';
        add_header 'Access-Control-Allow-Credentials' 'true';
        return 401;
    }
    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/api.example.com-0001/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/api.example.com-0001/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 = api.example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
    listen [::]:80;

    server_name api.example.com;
    return 404; # managed by Certbot
}   

OPTIONS请求响应(curl -I -X OPTIONS https://api.example.com/api/backapp-service/products/v1/search-product)

HTTP/1.1 200
Server: nginx/1.24.0 (Ubuntu)
Date: Wed, 30 Oct 2024 15:19:09 GMT
Content-Length: 0
Connection: keep-alive
reason: Invalid JWT token::CharSequence cannot be null or empty.
code: 401
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Allow: GET,HEAD,POST,OPTIONS
Accept-Patch:
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Origin, Content-Type, Accept, Authorization
Access-Control-Allow-Credentials: true

排查建议

  • 注意到OPTIONS预检请求返回了code:401错误,说明Spring Boot的鉴权逻辑拦截了该请求。预检请求默认不会携带Token,需在Spring Security(如果使用)中配置放行OPTIONS请求,或让鉴权过滤器跳过OPTIONS方法。
  • 同时在Spring Boot和Nginx配置CORS可能导致冲突,建议统一在一处配置:要么保留Spring Boot的CORS配置并禁用Nginx的相关配置,要么只保留Nginx的CORS配置并注释掉Spring Boot的CorsFilter Bean,避免重复添加响应头引发浏览器报错。
  • 检查Nginx的location匹配优先级:第一个location /会匹配所有请求,后续正则匹配的location ~* "^/api/backapp-service(/|$)(.*)"优先级更高,但需确认请求是否真的命中该location。可在Nginx的access_log中添加$request_uri和$location字段验证。
  • 查看浏览器控制台的具体CORS错误信息,明确是Access-Control-Allow-Origin不匹配、缺少响应头还是凭证相关问题,针对性排查。
  • Spring Boot中setAllowedOriginPatterns(List.of("*"))与setAllowCredentials(true)的组合需注意:允许凭证时,Access-Control-Allow-Origin不能是*,必须指定具体域名。虽然Spring的allowedOriginPatterns支持通配符,但需确保实际返回的响应头是https://example.com而非*,避免与Nginx的配置冲突。
  • 清理Nginx配置中OPTIONS请求处理的重复代码:删除重复的return 204;行,同时确保OPTIONS请求由Nginx直接返回204,不转发到Spring Boot,避免被鉴权逻辑拦截。

内容的提问来源于stack exchange,提问作者Joe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 18:41:03