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

Spring Cloud Gateway对接前端出现CORS跨域问题的解决咨询

微服务架构下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)
    }
}

核心疑问

  1. 仅在下游服务配置CORS是否正确,还是应该在API网关层面处理?
  2. 如何解决当前问题,允许前端通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 03:02:34