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; }
排查与解决建议
统一CORS配置入口,避免多层重复配置
当前Nginx、网关、微服务都在配置CORS,极易导致响应头重复或规则冲突。建议只在网关层统一配置CORS,关闭其他层级的CORS配置:- Nginx:删除OPTIONS请求的
add_header配置,让网关处理预检请求 - 微服务:在
SecurityConfig中注释掉.cors(cors -> cors.configurationSource(corsConfigurationSource())),或直接移除corsConfigurationSourceBean - 网关:保留
globalcors配置,删除路由中的AddResponseHeader过滤器(globalcors会自动添加合规的响应头)
- Nginx:删除OPTIONS请求的
对齐AllowedOrigin配置规则
微服务使用allowedOriginPatterns,网关使用allowedOrigins,两者规则不一致可能导致验证失败。建议网关也改用allowedOriginPatterns:globalcors: cors-configurations: '[/**]': allowedOriginPatterns: - "https://domain.pl" # 其他配置保持不变修正Nginx OPTIONS请求处理逻辑
如果要保留Nginx处理OPTIONS,需确保响应头完整;或者直接将OPTIONS请求转发给网关处理,删除Nginx中针对OPTIONS的特殊判断块:location /api/ { # 删除原有if ($request_method = OPTIONS)代码块 proxy_pass http://localhost:8000; # 其他proxy配置不变 }放行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校验逻辑 }浏览器端验证响应头
打开浏览器开发者工具,检查请求响应头:- 确保只有一个
Access-Control-Allow-Origin头,值为https://domain.pl Access-Control-Allow-Credentials需为true- 预检请求的响应头需包含所有需要的
Access-Control-Allow-Methods和Access-Control-Allow-Headers
- 确保只有一个
内容的提问来源于stack exchange,提问作者Wierzba
相关产品推荐
相关产品推荐

