Spring Cloud Gateway对接前端出现CORS跨域问题的解决咨询
问题场景
基于微服务架构开发,采用Spring Cloud Gateway作为API网关(端口8090)。使用Postman和Swagger UI向网关发起请求时正常返回200 OK,但从localhost:3000的前端发起相同请求时,出现CORS错误:
Access to XMLHttpRequest at 'http://localhost:8090/api/v1/accounts/login' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
当前仅在下游服务中配置了CORS,以下是下游服务的相关配置代码:
下游服务CORS配置类
package com.example.e_commerce.helper.config import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.cors.CorsConfiguration import org.springframework.web.cors.CorsConfigurationSource import org.springframework.web.cors.UrlBasedCorsConfigurationSource @Configuration class CorsConfig { @Bean fun corsConfigurationSource(): CorsConfigurationSource { val configuration = CorsConfiguration().apply { allowedOrigins = listOf( "http://localhost:3000", "http://192.168.1.35:3000", "http://localhost:8090", "http://127.0.0.1:8090", ) allowedMethods = listOf("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") allowCredentials = true allowedHeaders = listOf("*") } val source = UrlBasedCorsConfigurationSource() source.registerCorsConfiguration("/**", configuration) return source } }
下游服务安全配置类
package com.example.e_commerce.authservice.config import com.example.e_commerce.authservice.service.UserService import com.example.e_commerce.helper.config.CorsConfig import com.example.e_commerce.helper.config.JwtConfig import com.example.e_commerce.helper.exceptions.GlobalAccessDeniedHandler import com.example.e_commerce.helper.exceptions.GlobalAuthenticationEntrypoint import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.ProviderManager import org.springframework.security.authentication.dao.DaoAuthenticationProvider import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity class SecurityConfig( private val globalAccessDeniedHandler: GlobalAccessDeniedHandler, private val globalAuthenticationEntrypoint: GlobalAuthenticationEntrypoint, private val corsConfig: CorsConfig, private val jwtConfig: JwtConfig, ) { private val whiteList: Array<String> = arrayOf( "/swagger-ui.html", "/swagger-ui/**", "/aggregate/*/v3/api-docs", "/v3/api-docs/**", "/swagger-resources/**", "/swagger-resources", "/webjars/**", "/configuration/**" ) @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { return http .csrf { csrf -> csrf.disable() } .authorizeHttpRequests { requests -> requests .requestMatchers(*whiteList).permitAll() .requestMatchers("/api/v1/users/deactivate/**").hasAnyRole("Admin") .requestMatchers("/api/v1/accounts/login", "/api/v1/accounts/signup").permitAll() .requestMatchers("/api/v1/users/exists/**").permitAll() .anyRequest().authenticated() } .exceptionHandling { it.accessDeniedHandler(globalAccessDeniedHandler) it.authenticationEntryPoint(globalAuthenticationEntrypoint) } .oauth2ResourceServer { oauth2 -> oauth2.jwt { jwt -> jwt.decoder(jwtConfig.jwtDecoder()) jwt.jwtAuthenticationConverter(jwtConfig.jwtAuthenticationConverter()) } } .sessionManagement { session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) } .cors { cors -> cors.configurationSource(corsConfig.corsConfigurationSource()) } .build() } @Bean fun authenticationManager(userService: UserService): AuthenticationManager { val daoAuthenticationProvider = DaoAuthenticationProvider().apply { setUserDetailsService(userService) setPasswordEncoder(BCryptPasswordEncoder()) } return ProviderManager(daoAuthenticationProvider) } }
核心疑问
- 仅在下游服务配置CORS是否正确,还是应该在API网关层面处理?
- 如何解决当前问题,允许前端通过API网关发起CORS请求?
额外环境信息:
- Spring Boot版本:3.3.4
- API网关运行端口:8090
- 前端运行地址:localhost:3000
解答
1. 仅下游配置CORS是否正确?
不正确。前端请求的直接目标是API网关,浏览器的CORS校验针对的是网关返回的响应头,而非下游服务。当前下游的CORS配置无法被浏览器识别,因为网关可能拦截OPTIONS预检请求,或者未将下游的CORS响应头正确透传给前端,导致浏览器检测不到Access-Control-Allow-Origin头而报错。
在微服务架构中,推荐在API网关层面统一处理CORS,优势如下:
- 集中管理:无需在每个下游服务重复配置,减少冗余
- 前置拦截:网关直接处理OPTIONS预检请求,避免无效请求到达下游
- 一致性:确保所有前端请求的CORS规则统一,避免配置差异引发的问题
2. 解决当前问题的具体步骤
步骤1:移除下游服务中的CORS配置
既然要在网关统一处理,可直接删除下游服务的CorsConfig类,或调整allowedOrigins为仅允许网关地址(如http://localhost:8090),避免配置冲突。
步骤2:在Spring Cloud Gateway中配置CORS
在网关服务中添加CORS配置类,示例Kotlin代码如下:
package com.example.gateway.config import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.cors.CorsConfiguration import org.springframework.web.cors.reactive.CorsWebFilter import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource @Configuration class GatewayCorsConfig { @Bean fun corsWebFilter(): CorsWebFilter { val corsConfig = CorsConfiguration().apply { // 允许的前端源地址 allowedOrigins = listOf("http://localhost:3000", "http://192.168.1.35:3000") // 允许的请求方法 allowedMethods = listOf("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") // 允许携带凭证(如Cookie、Token) allowCredentials = true // 允许的请求头 allowedHeaders = listOf("*") // 暴露给前端的响应头(若需前端获取自定义头则配置) exposedHeaders = listOf("Authorization") } val source = UrlBasedCorsConfigurationSource().apply { // 对所有路径生效 registerCorsConfiguration("/**", corsConfig) } return CorsWebFilter(source) } }
步骤3:验证配置
重启网关服务后,前端再次发起请求:
- 浏览器先发送OPTIONS预检请求,网关直接返回包含CORS响应头的结果
- 后续实际请求被网关转发到下游服务,网关确保响应头包含
Access-Control-Allow-Origin等必要字段
补充说明
若网关配置了自定义路由过滤器,需确保OPTIONS请求能正常通过,Spring Cloud Gateway默认不会拦截OPTIONS请求,但自定义过滤器需避免拦截此类请求。
内容的提问来源于stack exchange,提问作者Furkan Erol

