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

Spring Boot微服务中CORS请求无效问题求助

Spring Boot微服务架构下CORS配置403问题排查与解决建议

我在基于Spring Boot的微服务架构中配置CORS时遇到了问题,架构组件包括:

  • 前端:Angular应用
  • 后端:Spring Boot微服务
  • Nginx:代理前端和API请求
  • 网关:Spring Cloud Gateway,负责请求转发到对应微服务

当前现象是:前端发起CORS请求时,OPTIONS请求返回204状态码,但浏览器仍提示403 Invalid CORS request错误。我已经尝试调整Nginx和Spring Security的CORS配置,但问题依旧。推测冲突可能出在Nginx、API网关、微服务自身这几个环节的CORS配置上,希望能找到正确的配置方式实现前后端正常通信。


目标微服务安全配置

public class SecurityConfig {

private final JwtService jwtService;
private final UserRepository userRepository;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
    http
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers("/api/auth/**").permitAll()
                    .requestMatchers("/api/admin/**","/actuator/**").hasRole("ADMIN")
                    .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
                    .requestMatchers("/api/system/**").hasRole("SYSTEM")
                    .anyRequest().authenticated()
            )
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .userDetailsService(userDetailsService)
            .addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

@Bean
public UrlBasedCorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOriginPatterns(List.of("https://domain.pl"));
    config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
    config.setAllowedHeaders(List.of("*"));
    config.setAllowCredentials(true);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return source;
}

@Bean
public JwtFilter jwtFilter() {
    return new JwtFilter(jwtService, userDetailsService());
}

@Bean
@Primary
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
}

@Bean
public UserDetailsService userDetailsService() {
    return new CustomUserDetailsService(userRepository, passwordEncoder());
}}

网关配置

uri: lb://user-service
predicates:
  - Path=/api/auth/**, /api/admin/users/**, /api/system/users/**, /api/user/**
filters:
  - AddResponseHeader=Access-Control-Allow-Origin, https://domain.pl
  - AddResponseHeader=Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS
  - AddResponseHeader=Access-Control-Allow-Headers, Content-Type, Authorization
  - AddResponseHeader=Access-Control-Allow-Credentials, true
  - name: RequestRateLimiter
    args:
      redis-rate-limiter.replenishRate: 5
      redis-rate-limiter.burstCapacity: 10
      redis-rate-limiter.requestedTokens: 1
      key-resolver: "#{@ipKeyResolver}"

globalcors:
  cors-configurations:
    '[/**]':
      allowedOrigins:
        - "https://domain.pl"
      allowedMethods:
        - GET
        - POST
        - PUT
        - PATCH
        - DELETE
        - OPTIONS
      allowedHeaders:
        - Content-Type
        - Authorization
      allowCredentials: true
      maxAge: 3600

API的Nginx配置

server {
    server_name api.domain.pl;
    listen 443 ssl;

    ssl_certificate /etc/letsencrypt/live/api.domain.pl/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.domain.pl/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location /api/ {
        if ($request_method = OPTIONS) {
            add_header 'Access-Control-Allow-Origin' 'https://domain.pl';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
            add_header 'Access-Control-Allow-Credentials' 'true';
            return 204;  # 无内容,但接受CORS
        }

        proxy_pass http://localhost:8000;

        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    access_log /var/log/nginx/api.access.log;
    error_log /var/log/nginx/api.error.log;
}

server {
    listen 80;
    server_name api.domain.pl;
    return 301 https://$host$request_uri;
}

前端的Nginx配置

server {
    listen 443 ssl;
    server_name domain.pl www.domain.pl;

    ssl_certificate /etc/letsencrypt/live/domain.pl/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/domain.pl/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/domain.pl/chain.pem;

    location / {
        proxy_pass http://127.0.0.1:4000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Referer $http_referer;

        proxy_cookie_path / "/; Secure; HttpOnly; SameSite=None";
    }

    access_log /var/log/nginx/frontend.access.log;
    error_log /var/log/nginx/frontend.error.log;
}

排查与解决建议

  1. 统一CORS配置入口,避免多层重复配置
    当前Nginx、网关、微服务都在配置CORS,极易导致响应头重复或规则冲突。建议只在网关层统一配置CORS,关闭其他层级的CORS配置:

    • Nginx:删除OPTIONS请求的add_header配置,让网关处理预检请求
    • 微服务:在SecurityConfig中注释掉.cors(cors -> cors.configurationSource(corsConfigurationSource())),或直接移除corsConfigurationSource Bean
    • 网关:保留globalcors配置,删除路由中的AddResponseHeader过滤器(globalcors会自动添加合规的响应头)
  2. 对齐AllowedOrigin配置规则
    微服务使用allowedOriginPatterns,网关使用allowedOrigins,两者规则不一致可能导致验证失败。建议网关也改用allowedOriginPatterns:

    globalcors:
      cors-configurations:
        '[/**]':
          allowedOriginPatterns:
            - "https://domain.pl"
          # 其他配置保持不变
    
  3. 修正Nginx OPTIONS请求处理逻辑
    如果要保留Nginx处理OPTIONS,需确保响应头完整;或者直接将OPTIONS请求转发给网关处理,删除Nginx中针对OPTIONS的特殊判断块:

    location /api/ {
        # 删除原有if ($request_method = OPTIONS)代码块
        proxy_pass http://localhost:8000;
        # 其他proxy配置不变
    }
    
  4. 放行OPTIONS请求绕过JWT校验
    微服务的JwtFilter可能拦截了OPTIONS请求,需在过滤器中直接放行:

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            filterChain.doFilter(request, response);
            return;
        }
        // 原有JWT校验逻辑
    }
    
  5. 浏览器端验证响应头
    打开浏览器开发者工具,检查请求响应头:

    • 确保只有一个Access-Control-Allow-Origin头,值为https://domain.pl
    • Access-Control-Allow-Credentials需为true
    • 预检请求的响应头需包含所有需要的Access-Control-Allow-Methods和Access-Control-Allow-Headers

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 16:42:02