【日常小问】Spring Cloud Gateway 5.x 跨域和路由配置踩坑实录

简介: Spring Cloud Gateway 升级 5.x 后,配置前缀改为 spring.cloud.gateway.server.webflux,依赖坐标改名为 spring-cloud-starter-gateway-server-webflux,CORS 需网关统一处理,避免下游服务重复设置导致浏览器拒绝。

一、问题出现

最近把一个 Spring Boot 3.x 的项目升级到 Spring Boot 4.0.6 + Spring Cloud 2025.1.x(对应 Gateway 5.x),顺便引入 Spring Cloud Gateway 做统一的 API 网关。本以为只是个常规的架构调整,结果折腾了一整个下午。

前端项目跑在 localhost:3000,网关跑在 8080,按理说配个路由和跨域就完事了。结果启动后遇到三个连环报错:

第一关:Gateway 启动就挂

ClassNotFoundException: org.springframework.boot.web.context.WebServerInitializedEvent

第二关:路由全 404

No RouteDefinition found for [Exchange: POST http://localhost:8080/api/auth/login/password]

第三关:跨域重复

Access-Control-Allow-Origin header contains multiple values 'http://localhost:3000, http://localhost:3000'

这三个问题其实是三个独立的原因,只是凑在一起爆发了。

二、问题原因

2.1 依赖坐标改了

第一个问题最直接。旧版 Gateway 的依赖是:

<!-- 旧版(Spring Cloud 2024.x 及之前) -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

到了 5.x,这个 artifact 被重命名了:

<!-- 新版(Spring Cloud 2025.x / Gateway 5.x) -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>

直接用旧坐标会导致类找不到 WebServerInitializedEvent——因为旧的 spring-cloud-starter-gateway 依赖了 spring-cloud-gateway-server,而后者在 5.x 中被拆分为 spring-cloud-gateway-server-webflux,包结构和类名都有变化。

2.2 配置前缀改了——这是最坑的

这是今天最大的坑。Gateway 5.x 把配置前缀改了:

版本 配置前缀
Gateway 4.x(旧) spring.cloud.gateway
Gateway 5.x(新) spring.cloud.gateway.server.webflux

看源码的 GatewayProperties.java 第 45 行:

public static final String PREFIX = "spring.cloud.gateway.server.webflux";

也就是说,路由配置要从:

# ❌ 旧写法——Gateway 5.x 不认
spring:
  cloud:
    gateway:
      routes:
        - id: auth-service
          uri: http://localhost:8001

改成:

# ✅ 新写法
spring:
  cloud:
    gateway:
      server:
        webflux:
          routes:
            - id: auth-service
              uri: http://localhost:8001

不只是路由,globalcors 的配置也一样:

# ✅ globalcors 也在新前缀下面
spring:
  cloud:
    gateway:
      server:
        webflux:
          globalcors:
            cors-configurations:
              '[/**]':
                allowedOriginPatterns: "*"

我当时就纳闷,明明配置看起来没问题,为什么路由就是加载不到?看启动日志只看到 No RouteDefinition found,没有任何报错。翻源码才找到这个前缀变化。

2.3 跨域重复——Gateway 和下游服务同时在设

第三个问题最有意思。Gateway 的 RoutePredicateHandlerMapping 在匹配路由后,会自动根据 globalcors 配置往响应头里写 Access-Control-Allow-Origin

但我的下游服务(auth-service、user-service 等)也各自配了 Spring Security 的 CORS:

// 每个服务的 SecurityConfig 都有这个
.cors(cors -> cors.configurationSource(corsConfigurationSource()))

@Bean
public CorsConfigurationSource corsConfigurationSource() {
   
    // ... 设置了 allowedOrigins、allowedMethods 等
}

结果就是:浏览器发一个请求,Gateway 写一次 CORS 头,下游服务又写一次。最终响应头里出现两个一模一样的 Access-Control-Allow-Origin: http://localhost:3000,浏览器直接拒绝。

三、解决问题

3.1 更换依赖坐标

pom.xml 中的 spring-cloud-starter-gateway 换成 spring-cloud-starter-gateway-server-webflux

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>

注意 Spring Cloud BOM 版本也要对得上:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2025.1.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

3.2 修正配置前缀

application.yml 中的所有 spring.cloud.gateway.* 配置移到 spring.cloud.gateway.server.webflux.* 下面。包括 routes 和 globalcors。

路由改前改后对比:

# ❌ 改前(404 找不着路由)
spring:
  cloud:
    gateway:
      routes:
        - id: auth-service
          uri: http://localhost:8001
          predicates:
            - Path=/api/auth/**

# ✅ 改后(路由正常加载)
spring:
  cloud:
    gateway:
      server:
        webflux:
          routes:
            - id: auth-service
              uri: http://localhost:8001
              predicates:
                - Path=/api/auth/**

3.3 统一 CORS 处理

思路很简单:网关是唯一入口,跨域只在网关层处理。下游服务只被网关调用,不需要自己处理跨域。

Gateway 加上 globalcors:

spring:
  cloud:
    gateway:
      server:
        webflux:
          globalcors:
            cors-configurations:
              '[/**]':
                allowedOriginPatterns: "*"
                allowedMethods:
                  - GET
                  - POST
                  - PUT
                  - DELETE
                  - OPTIONS
                  - PATCH
                allowedHeaders: "*"
                allowCredentials: true
                maxAge: 3600

所有下游服务去掉 CORS 配置:

每个微服务的 *SecurityConfig.java 中去掉三样东西:

  1. 删除 .cors(cors -> cors.configurationSource(...)) 这一行
  2. 删除 corsConfigurationSource() 方法
  3. 删除相关 import(CorsConfigurationCorsConfigurationSourceUrlBasedCorsConfigurationSource
// ❌ 改前:每个服务都有自己的 CORS
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
// ...

@Bean
public CorsConfigurationSource corsConfigurationSource() {
   
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(List.of("http://localhost:3000", ...));
    // ...
    return source;
}

// ✅ 改后:完全去掉,让 Gateway 统一处理

四、补充:Predicate 的写法也变了?

这里还有个细节。旧版 Gateway 支持在 Path 谓词里用逗号分隔多个路径:

# 旧写法(一个 Path 配多个模式)
predicates:
  - Path=/api/auth/**,/api/admin/auth/**

在 5.x 里,这个写法也行,但要确认 ShortcutType.GATHER_LIST_TAIL_FLAG 的解析逻辑。最稳妥的做法是拆成多个 Path 条目(注意这里是多条路由,不是多个谓词):

# 最稳妥的写法:一条路由配一个 Path
- id: auth-service
  uri: http://localhost:8001
  predicates:
    - Path=/api/auth/**
- id: auth-service-admin
  uri: http://localhost:8001
  predicates:
    - Path=/api/admin/auth/**

不过要注意,不能在同一条路由下写两个 Path 谓词

# ❌ 错误:同一个 route 里多个 Path 是 AND 关系,永远匹配不到
- id: auth-service
  uri: http://localhost:8001
  predicates:
    - Path=/api/auth/**
    - Path=/api/admin/auth/**

因为路由的多个谓词之间是 AND 关系,一个路径不可能同时匹配 /api/auth/**/api/admin/auth/**

五、总结

这次升级 Gateway 5.x 遇到的核心问题就一个:旧资料还停留在 4.x,但 5.x 的配置体系改了

几个教训:

  1. 依赖坐标变了就查官方源码——网上搜到的教程大概率还是旧版的 spring-cloud-starter-gateway
  2. 配置前缀变了也查源码——GatewayProperties.java 那条 PREFIX 常量一眼就能看到
  3. CORS 只管一层——网关架构下,跨域在网关层统一处理就好,下游服务不需要操这个心
  4. 启动日志里有黄金——TRACE 级别的日志能看到路由匹配的全过程,比瞎猜有用得多

如果升级过程中也遇到类似的"配置明明写了但不生效"的问题,大概率也是前缀或者坐标的问题。

目录
相关文章
|
22天前
|
人工智能 自然语言处理 Java
Java做AI真不行?2026年最被低估的机会来了
Spring官宣集成DeepSeek,Java正式迈入AI驱动时代!2026年AI岗位缺口巨大,大厂招聘普遍要求大模型能力。Java团队借力Spring生态与JBoltAI等国产框架,可低门槛接入代码生成、RAG、Agent等全链路AI能力,实现差异化突围。(239字)
154 3
|
2月前
|
安全 前端开发 搜索推荐
【SpringSecurity新手村系列】(3)自定义登录页与表单认证
自定义登录页与表单认证本文围绕自定义登录页展开,详解 formLogin、loginProcessingUrl 与跳转配置,重点解释 CSRF 隐藏域的作用、校验原理及常见错误,帮助你稳定完成表单登录改造。
188 6
|
2月前
|
安全 前端开发 Java
【SpringSecurity新手村系列】(1)初识安全框架
本文从零开始引入 Spring Security,演示默认登录页与接口保护效果,并解释认证、授权与过滤器链的基础机制,帮助你快速建立安全开发的整体认知。
202 1
|
1月前
|
缓存 Java 关系型数据库
90% Java 开发都踩过坑的 @Resource 与 @Autowired
本文深度解析Spring中`@Resource`与`@Autowired`的核心差异:前者属Java官方JSR-250规范(JDK8为`javax.annotation.Resource`,JDK11+为`jakarta.annotation.Resource`),默认按名注入、兼容多容器;后者为Spring原生注解,默认按类型注入、强耦合Spring生态。详述两者在注入逻辑、查找顺序、容错机制、构造器支持及源码执行优先级等维度的全量对比,并梳理高频踩坑场景与选型建议。
309 1
|
15天前
|
人工智能 API 网络安全
OpenClaw新手必看!阿里云一键部署+百炼Token Plan开通与配置完整手册
2026年,AI智能体(Agent)已成为个人与团队提升效率的核心工具,OpenClaw(原Clawdbot)凭借轻量化、全场景适配、可深度自定义的优势,成为最受欢迎的开源AI智能体框架之一。它能对接阿里云百炼大模型,实现代码生成、任务自动化、信息检索、多平台交互等能力,无需复杂开发即可拥有专属AI助手。
141 2
|
16天前
|
数据采集 人工智能 数据可视化
从数据到知识:Dataphin 知识图谱,重新定义企业智能决策
Dataphin知识图谱助力企业从PB级数据迈向可理解、可推理、可决策的知识智能。它深度融合数据研发体系,支持可视化建模、结构化/非结构化数据双通道入图、Schema全生命周期管理及GraphRAG问答,真正实现“数据即知识”。
212 0
从数据到知识:Dataphin 知识图谱,重新定义企业智能决策
|
人工智能 NoSQL Java
【SpringAIAlibaba新手村系列】(8)持久化会话与 Redis 内存管理
本文详解 Spring AI 的会话记忆机制,从内存版 MemorySaver 到 Redis 版 RedisSaver,实现 AI 对话的上下文连续性。文章以 ReactAgent 为核心,讲解如何通过 threadId 管理会话线程,并将 Agent 状态持久化到 Redis 中。
988 5
|
11天前
|
JSON 运维 监控
线上CPU突然飙到500%,凶手竟是一条日志
一次CPU飙升至500%的故障,根源竟是一行日志:`logger.error(&quot;用户信息解析失败:&quot; + userJson)`。异常请求携带近5万行乱码JSON,导致高频字符串拼接与磁盘写入,拖垮CPU。通过线程栈定位、降级日志、规范输出(限流/精简/监控),成功止损。教训深刻:看似无害的日志,亦是性能杀手。
|
12天前
|
消息中间件 监控 NoSQL
线上Kafka积压后,我是怎么处理的
本文记录一次Kafka消费组Lag飙升20万+的实战排障全过程:从快速定位积压分区、紧急扩容消费者、优化消费参数,到发现Redis大key根因、临时降级、事后加固监控与自动化响应。强调“可观测性+自动化”是应对消息积压的关键。
|
16天前
|
人工智能 开发框架 监控
Agent 时代,为什么传统的可观测方案不适用了?
Agent时代,系统“运行正常”不等于任务“做对了”。传统可观测聚焦稳定性,却无法评估语义正确性、推理合理性与业务效果。新范式需融合Trace、评估与归因,实现“观测→评估→优化”闭环,支撑可信AI落地。
76 0
Agent 时代,为什么传统的可观测方案不适用了?