Spring API Gateway全局CORS错误:Angular登录请求失败
问题描述
基于Spring Security搭建用户管理微服务,搭配Spring Cloud Gateway做路由转发,Postman调用登录接口正常,但本地Angular应用发起登录请求时触发CORS错误。
相关配置代码
1. Spring Security配置
@Bean public SecurityFilterChain configure(HttpSecurity http) throws Exception { // Configure AuthenticationManagerBuilder AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); authenticationManagerBuilder.userDetailsService(userDetailsService) .passwordEncoder(bCryptPasswordEncoder); AuthenticationManager authenticationManager = authenticationManagerBuilder.build(); // Create AuthenticationFilter AuthenticationFilter authenticationFilter = getAuthenticationFilter(authenticationManager); IpAddressMatcher hasIpAddress = new IpAddressMatcher(Objects.requireNonNull(environment.getProperty("gateway.ip"))); IpAddressMatcher hasIpv4Address = new IpAddressMatcher(Objects.requireNonNull(environment.getProperty("gateway.ipv4"))); return http .cors(corsCustomizer -> corsCustomizer.configurationSource(corsConfigurationSource())) .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth .requestMatchers("/**") .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address)) .requestMatchers(new AntPathRequestMatcher("/actuator/**")) .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address)) .requestMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL) .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address)) .requestMatchers(HttpMethod.GET, SecurityConstants.EMAIL_VERIFICATION_URL) .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address)) .requestMatchers(HttpMethod.POST, SecurityConstants.PASSWORD_RESET_REQUEST_URL) .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address)) .requestMatchers(HttpMethod.POST, SecurityConstants.PASSWORD_RESET_URL) .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address)) .requestMatchers("/v2/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**", "/v3/api-docs/**").permitAll() .requestMatchers(HttpMethod.GET, "/hello") .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address)) ) .addFilterAfter(authenticationFilter, AuthenticationFilter.class) .addFilterAfter(new AuthorizationFilter(authenticationManager, jwtTokenProvider), AuthorizationFilter.class) .authenticationManager(authenticationManager) .sessionManagement((session) -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .build(); }
2. Spring Cloud Gateway全局CORS配置
# https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/cors-configuration.html spring.cloud.gateway.default-filters= DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin spring.cloud.gateway.globalcors.cors-configurations[/**].allowed-origins="http://localhost:4200, http://127.0.0.1:4200" spring.cloud.gateway.globalcors.cors-configurations[/**].allowed-headers="*" spring.cloud.gateway.globalcors.cors-configurations[/**].allowedMethods="POST", "GET", "PUT", "DELETE", "PATCH", "OPTIONS" spring.cloud.gateway.globalcors.cors-configurations[/**].max-age=3600 spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true
3. Gateway路由配置
# user registration routes do not need filter spring.cloud.gateway.routes[0].id=users-ws spring.cloud.gateway.routes[0].uri = lb://users-ws spring.cloud.gateway.routes[0].predicates[0]=Path=/users-ws/users spring.cloud.gateway.routes[0].predicates[1]=Method=POST spring.cloud.gateway.routes[0].filters[0]=RemoveRequestHeader=Cookie spring.cloud.gateway.routes[1].id = users-ws-login spring.cloud.gateway.routes[1].uri = lb://users-ws spring.cloud.gateway.routes[1].predicates[0]=Path=/users-ws/users/login spring.cloud.gateway.routes[1].predicates[1]=Method=POST spring.cloud.gateway.routes[1].filters[0]=RemoveRequestHeader=Cookie # user management routes do need filter spring.cloud.gateway.routes[2].id=users-management-ws spring.cloud.gateway.routes[2].uri=lb://users-ws spring.cloud.gateway.routes[2].predicates[0]=Path=/users-ws/users/** spring.cloud.gateway.routes[2].predicates[1]=Method=GET,PUT,DELETE spring.cloud.gateway.routes[2].predicates[2]=Header=Authorization, Bearer (.*) spring.cloud.gateway.routes[2].filters[0]=RemoveRequestHeader=Cookie spring.cloud.gateway.routes[2].filters[1]=AuthorizationHeaderFilter # configuring filter for actuator spring.cloud.gateway.routes[3].id = users-ws-actuator spring.cloud.gateway.routes[3].uri = lb://users-ws spring.cloud.gateway.routes[3].predicates[0]=Path=/users-ws/actuator/** spring.cloud.gateway.routes[3].predicates[1]=Method=GET spring.cloud.gateway.routes[3].filters[0]=RemoveRequestHeader=Cookie
4. 自定义AuthorizationHeaderFilter
@Component public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> { private final Environment env; @Autowired public AuthorizationHeaderFilter(Environment env) { super(Config.class); this.env = env; } @Override public GatewayFilter apply(Config config) { return ((exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); if(!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) return onError(exchange, " No authorization Header", HttpStatus.UNAUTHORIZED); String authHeader = Objects.requireNonNull( request.getHeaders().get(HttpHeaders.AUTHORIZATION)).get(0); String jwt = authHeader.replace("Bearer ", ""); if(!isJwtValid(jwt)) return onError(exchange, "Invalid JWT", HttpStatus.UNAUTHORIZED); return chain.filter(exchange); }); } private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(httpStatus); return response.setComplete(); } private boolean isJwtValid(String jwt) { boolean isValid = true; String subject = null; String tokenSecret = env.getProperty("token.secret"); assert tokenSecret != null; byte[] tokenSecretBytes = Base64.getEncoder().encode(tokenSecret.getBytes()); SecretKey signingKey = Keys.hmacShaKeyFor(tokenSecretBytes); JwtParser parser = Jwts.parser() .verifyWith(signingKey) .build(); try { subject = parser.parseSignedClaims(jwt).getPayload().getSubject(); } catch (Exception e) { System.out.println(e.getMessage()); isValid = false; } if(subject == null || subject.isEmpty()) { isValid = false; } return isValid; } public static class Config { // put configuration properties } }
5. 尝试的编程式CORS配置
@Configuration public class AppGlobalCorsConfiguration extends CorsConfiguration { @Bean public CorsConfigurationSource corsWebFilter() { final CorsConfiguration corsConfig = new CorsConfiguration(); final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); corsConfig.setAllowedOrigins(Arrays.asList("http://localhost:4200", "http://127.0.0.1:4200")); corsConfig.setMaxAge(3600L); corsConfig.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "PATCH", "OPTIONS")); corsConfig.setAllowedHeaders(Collections.singletonList("*")); source.registerCorsConfiguration("/**", corsConfig); return source; } }
6. Angular登录请求代码
login(email: string, password: string): Observable<any> { return this.http.post<AuthModel>(`${API_USERS_URL}/login`, { email, password, }); }
错误信息

跨源请求被阻止:同源策略禁止读取位于 http://localhost:8082/users-ws/users/login 的远程资源。(原因:CORS头缺少'Access-Control-Allow-Origin')。状态码:401。
解决方案
问题根因
- OPTIONS预检请求未被放行:Angular发起POST请求前会自动发送OPTIONS预检请求,但当前Gateway登录路由只匹配POST方法,OPTIONS请求被拦截返回401,触发CORS错误。
- 多重复CORS配置冲突:同时在Gateway、微服务Security中配置CORS,导致响应头混乱;另外编程式配置类错误继承
CorsConfiguration,属于无效配置。 - IP白名单限制:Security中所有请求都经过
getAccess方法校验IP,Angular请求来自localhost,可能不在配置的gateway.ip/gateway.ipv4白名单内,导致OPTIONS请求被拒绝。
修复步骤
1. 调整Gateway路由,允许OPTIONS请求
修改登录和注册路由的Method predicate,加入OPTIONS方法:
spring.cloud.gateway.routes[0].predicates[1]=Method=POST,OPTIONS spring.cloud.gateway.routes[1].predicates[1]=Method=POST,OPTIONS
2. 简化CORS配置,仅在Gateway层处理
- 移除微服务Security配置中的
.cors(...)代码块,避免和Gateway的CORS配置冲突。 - 删除微服务中的
AppGlobalCorsConfiguration类,微服务层无需重复配置CORS。 - 修正Gateway的CORS配置,去掉
allowed-origins的引号(引号会导致域名匹配失败):
spring.cloud.gateway.globalcors.cors-configurations[/**].allowed-origins=http://localhost:4200,http://127.0.0.1:4200
3. 调整Security规则,放行OPTIONS请求
在Security的authorizeHttpRequests中优先放行所有OPTIONS请求:
.authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 先放行所有OPTIONS预检请求 .requestMatchers("/**") .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address)) // 其他原有规则保持不变 )
或者在getAccess方法中判断请求方法,若为OPTIONS直接允许:
private AuthorizationDecision getAccess(Authentication authentication, AuthorizationContext context, IpAddressMatcher hasIpAddress, IpAddressMatcher hasIpv4Address) { HttpServletRequest request = context.getRequest(); if (HttpMethod.OPTIONS.name().equals(request.getMethod())) { return new AuthorizationDecision(true); } // 原有IP校验逻辑 }
4. 优化Gateway的响应头去重配置
确保DedupeResponseHeader配置明确去重策略,避免重复的CORS响应头:
spring.cloud.gateway.default-filters=DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_FIRST
内容的提问来源于stack exchange,提问作者davidvera
相关产品推荐
相关产品推荐

