Spring Cloud Gateway 实战:从网关搭建到过滤器与跨域解决方案

Spring Cloud Gateway 实战:从网关搭建到过滤器与跨域解决方案

  • [一 . 为什么需要网关 ?](#一 . 为什么需要网关 ?)
    • [1.1 网关的介绍](#1.1 网关的介绍)
    • [1.2 网关的技术实现](#1.2 网关的技术实现)
    • [1.3 小结](#1.3 小结)
  • [二 . 搭建网关服务](#二 . 搭建网关服务)
    • [2.1 创建 gateway 模块 , 引入依赖](#2.1 创建 gateway 模块 , 引入依赖)
    • [2.2 编写基础配置和路由规则](#2.2 编写基础配置和路由规则)
    • [2.3 重启测试](#2.3 重启测试)
    • [2.4 小结](#2.4 小结)
  • [三 . 路由断言工厂 (Route Predicate Factory)](#三 . 路由断言工厂 (Route Predicate Factory))
    • [3.1 定义](#3.1 定义)
    • [3.2 具体的断言工厂](#3.2 具体的断言工厂)
    • [3.3 小结](#3.3 小结)
  • [四 . 过滤器工厂](#四 . 过滤器工厂)
    • [4.1 路由过滤器的种类](#4.1 路由过滤器的种类)
    • [4.2 请求头过滤器](#4.2 请求头过滤器)
    • [4.3 默认过滤器](#4.3 默认过滤器)
    • [4.4 自定义全局过滤器](#4.4 自定义全局过滤器)
    • [4.5 过滤器的执行顺序](#4.5 过滤器的执行顺序)
    • [4.6 小结](#4.6 小结)
  • [五 . 跨域问题](#五 . 跨域问题)
    • [5.1 什么是跨域问题 ?](#5.1 什么是跨域问题 ?)
    • [5.2 模拟跨域问题](#5.2 模拟跨域问题)
    • [5.3 解决跨域问题](#5.3 解决跨域问题)

在微服务架构中,网关作为系统的统一入口,承担着路由分发、权限校验、流量控制等核心功能。本文将深入讲解 Spring Cloud Gateway 的核心应用,从为什么需要网关开始,逐步介绍网关服务搭建、路由断言配置、过滤器工厂使用及跨域问题解决方案。内容涵盖 Gateway 基础路由规则、多种断言工厂的实际应用、请求过滤与全局拦截实现,以及跨域资源访问的完整解决方案,帮助读者构建安全、高效的微服务网关层。

本专栏的内容均来自于 B 站 UP 主黑马程序员的教学视频,感谢你们提供了优质的学习资料,让编程不再难懂。

专栏地址 : https://blog.csdn.net/m0_53117341/category_12835102.html

一 . 为什么需要网关 ?

1.1 网关的介绍

网关的介绍.png

那网关的功能如下 :

  1. 身份认证和权限校验 : 网关作为微服务的入口 , 需要校验用户是否具有请求资格 , 如果没有则需要进行拦截
  2. 服务路由、负载均衡 : 一切请求都需要先经过 gateway , 但是网关不处理任何业务 , 而是根据某种规则 , 把请求转发到某个微服务 , 这个过程叫做路由 . 当路由的目标服务存在多个时 , 还需要进行负载均衡策略 .
  3. 请求限流 : 当请求的流量过高 , 就会在网关中按照下流方的微服务所能够接受的速度来放行请求 , 避免服务压力过大

1.2 网关的技术实现

在 Spring Cloud 中网关的实现主要包括两种 :

  1. gateway
  2. zuul

那 zuul 是基于 Servlet 的实现 , 属于阻塞式编程 . 而 SpringCloudGateway 则是基于 Spring5 来实现的 , 属于响应式编程的实现 , 具备更好的性能 .

1.3 小结

网关的作用 :

  1. 对用户请求做身份验证、权限校验
  2. 将用户请求路由到微服务 , 并且实现负载均衡
  3. 对用户请求做限流

二 . 搭建网关服务

2.1 创建 gateway 模块 , 引入依赖

首先需要我们创建一个新的 module

然后引入 SpringCloudGateway 的依赖和 Nacos 的服务发现依赖

因为网关也需要拉取健康的服务列表 , 所以还需要引入 Nacos 的依赖

xml 复制代码
<!-- Gateway 网关 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos 服务发现依赖 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

然后将该模块的启动类以及配置文件创建出来

2.2 编写基础配置和路由规则

接下来打开 yml 配置文件 , 填写下面的配置

yaml 复制代码
server:
  port: 10010 # 网关端口
spring:
  application:
    name: gateway # 给当前微服务起一个名称
  cloud:
    nacos:
      username: nacos # Nacos 的账号
      password: nacos # Nacos 的密码
      discovery:
        server-addr: localhost:8848 # Nacos 地址
    gateway:
      routes: # 网关路由配置
        - id: user-service # 当前路由 ID, 自定义保持唯一即可
          uri: lb://userservice # 路由的目标地址, lb 表示负载均衡
          predicates: # 路由断言, 判断请求是否符合路由规则的条件
            - Path=/user/**

我们也可以通过源码来看一下 routes 需要填写哪些参数

我们目前先关心这三个信息

其中的这个 uri , 如果我们访问单独的某个机器 , 直接设置成具体的 URL 即可

yaml 复制代码
uri: http://127.0.0.1:8081 # 路由的目标地址, http 就是固定地址

但是我们如果通过网关 , 就只能一直使用这个端口对应的机器 , 肯定是不可以的 .

那我们就可以使用这种方式 lb://userservice , 它指的是负载均衡的访问 userservice 这个服务 .

然后下面还指定了一些条件, - Path=/user/** 指的是所有以 /user 开头的路径 , 都会以负载均衡的方式访问 user-service 服务 . 如果想要指定多个拦截规则的话 , 在后面继续指定即可 , 比如 :

yaml 复制代码
- Path=/user/**,/user2/**

/** 代表多级路径 , 也就是以 /user 开头的 URL 就满足规则 , 比如 : /user/id/1

/* 代表一级路径 , 只能满足 /user 后面只有一级的情况 , 比如 : /user/1

那如果有多个网关路由的配置 , 在后面继续添加即可

yaml 复制代码
server:
  port: 10010 # 网关端口
spring:
  application:
    name: gateway # 给当前微服务起一个名称
  cloud:
    nacos:
      username: nacos # Nacos 的账号
      password: nacos # Nacos 的密码
      discovery:
        server-addr: localhost:8848 # Nacos 地址
      gateway:
        routes: # 网关路由配置
          - id: user-service # 当前路由 ID, 自定义保持唯一即可
            uri: lb://userservice # 路由的目标地址, lb 表示负载均衡
            predicates: # 路由断言, 判断请求是否符合路由规则的条件
              - Path=/user/**
          - id: order-service # 当前路由 ID, 自定义保持唯一即可
            uri: lb://orderservice # 路由的目标地址, lb 表示负载均衡
            predicates: # 路由断言, 判断请求是否符合路由规则的条件
              - Path=/order/**

2.3 重启测试

接下来 , 我们运行 GatewayApp , 来看一下效果

访问 http://127.0.0.1:10010/order/101

http://127.0.0.1:10010/user/1

2.4 小结

网关搭建步骤 :

  1. 创建项目 , 引入 Nacos 服务发现和 Gateway 依赖
  2. 配置 application.yml , 包括服务基本信息、Nacos 地址、路由

路由配置包括 :

  1. 路由 id : 路由的唯一标识
  2. 路由目标 (uri) : 路由的目标地址 , HTTP 代表固定地址 , lb 代表根据服务名称进行负载均衡策略
  3. 路由断言 (predicates) : 判断路由的规则
  4. 路由过滤器 (filters) : 对请求或相应做处理

三 . 路由断言工厂 (Route Predicate Factory)

我们之前已经完成了搭建网关服务 , 那需要以下配置 :

  1. 路由 id : 路由的唯一标识
  2. uri : 路由的目的地 , 支持 lb 和 HTTP 两种
  3. predicates : 路由断言 , 判断请求是否符合要求 , 符合则转发到路由目的地
  4. filters : 路由过滤器 , 处理请求或响应

那其中的第三点 : 路由断言 , 就是我们接下来需要讨论的问题

3.1 定义

我们在配置文件中写的断言规则只是字符串 , 那这些字符串会被 Predicate Factory 读取并且处理 , 转变为路由判断的条件 .

比如 : - Path = /user/** 就是按照路径匹配 , 那这个规则就是由 Predicate Factory 来处理的

3.2 具体的断言工厂

名称 说明 示例
After 是某个时间点后的请求 - After=2037-01-20T17:42:47.789-07:00America/Denver
Before 是某个时间点之前的请求 - Before=2031-04-13T15:14:47.433+08:00Asia/Shanghai
Between 是某两个时间点之间的请求 - Between=2037-01-20T17:42:47.789-07:00America/Denver, 2037-01-21T17:42:47.789-07:00America/Denver
Cookie 请求必须包含某些 cookie - Cookie=chocolate, ch.p
Header 请求必须包含某些 header - Header=X-Request-Id, \d+
Host 请求必须是访问某个 host(域名) - Host=.somehost.org,.anotherhost.org
Method 请求方式必须是指定方式 - Method=GET,POST
Path 请求路径必须符合指定规则 - Path=/red/{segment},/blue/**
Query 请求参数必须包含指定参数 - Query=name, Jack或者- Query=name
RemoteAddr 请求者的 ip 必须是指定范围 - RemoteAddr=192.168.1.1/24
Weight 权重处理

3.3 小结

❓ RoutePredicateFactory (路由断言工厂) 的作用是什么 ?

✔️ 根据请求的参数进行条件判断 , 成立的话就进行放行 . 不成立的话 , 就会返回 404

❓ Path=/user/** 是什么含义 ?

✔️ /user 开头的任意路径 , 只要符合条件就进行放行

四 . 过滤器工厂

GatewayFilter 是网关中提供的一种过滤器 , 可以对进入网关的请求和微服务所返回的响应进行处理 , 如图所示 :

4.1 路由过滤器的种类

Spring 提供了共 31 种不同的路由过滤器工厂 , 我们介绍几个比较常见的

名称 说明
AddRequestHeader 给当前请求添加一个请求头
RemoveRequestHeader 移除请求中的一个请求头
AddResponseHeader 给响应结果中添加一个响应头
RemoveResponseHeader 从响应结果中移除一个响应头
RequestRateLimiter 限制请求的流量
...

4.2 请求头过滤器

需求 : 给所有经过 user-service 服务的请求添加一个请求头 : Hello World

实现方式 : 在 Gateway 中修改 application.yml 文件 , 给 userservice 的路由添加过滤器

yaml 复制代码
server:
  port: 10010 # 网关端口
spring:
  application:
    name: gateway # 给当前微服务起一个名称
  cloud:
    nacos:
      username: nacos # Nacos 的账号
      password: nacos # Nacos 的密码
      discovery:
        server-addr: localhost:8848 # Nacos 地址
    gateway:
      routes: # 网关路由配置
        - id: user-service # 当前路由 ID, 自定义保持唯一即可
          uri: lb://userservice # 路由的目标地址, lb 表示负载均衡
          predicates: # 路由断言, 判断请求是否符合路由规则的条件
            - Path=/user/**
          filters: # 过滤器
            - AddRequestHeader=key, Hello World! # , 前半部分是 key, 后半部分是内容
        - id: order-service # 当前路由 ID, 自定义保持唯一即可
          uri: lb://orderservice # 路由的目标地址, lb 表示负载均衡
          predicates: # 路由断言, 判断请求是否符合路由规则的条件
            - Path=/order/**

接下来 , 我们就可以编写一个方法来进行测试了

那我们需要在参数中添加 @RequestHeader 注解来去获取请求头中的内容

除此之外 , 我们还需要额外指定一个属性

所以我们还需要添加 required 属性 , 设置为 false

required = true(默认值) :

若请求中缺少该请求头(如key不存在) , 则会抛出 400 Bad Request 异常 , 导致请求失败

required = false :

若请求中缺少该请求头 , 参数值会被自动设为null , 不会触发异常

java 复制代码
package com.example.user.web;

import com.example.user.pojo.User;
import com.example.user.service.UserService;
import com.example.user.vo.DateFormatVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
@RestController
// @RefreshScope
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/testHeader")
    public String testHeader(@RequestHeader(value = "key", required = false) String header) { // 在参数中添加注解 @RequestHeader 来去获取请求头中的内容
        return header;
    }
}

我们重启 user-service 服务和 gateway-server 服务

然后访问 http://127.0.0.1:10010/user/testHeader

4.3 默认过滤器

如果想要对所有的路由都生效 , 则可以将过滤器工厂写到 default-filters 下面

yaml 复制代码
server:
  port: 10010 # 网关端口
spring:
  application:
    name: gateway # 给当前微服务起一个名称
  cloud:
    nacos:
      username: nacos # Nacos 的账号
      password: nacos # Nacos 的密码
      discovery:
        server-addr: localhost:8848 # Nacos 地址
    gateway:
      routes: # 网关路由配置
        - id: user-service # 当前路由 ID, 自定义保持唯一即可
          uri: lb://userservice # 路由的目标地址, lb 表示负载均衡
          predicates: # 路由断言, 判断请求是否符合路由规则的条件
            - Path=/user/**
        - id: order-service # 当前路由 ID, 自定义保持唯一即可
          uri: lb://orderservice # 路由的目标地址, lb 表示负载均衡
          predicates: # 路由断言, 判断请求是否符合路由规则的条件
            - Path=/order/**
      default-filters: # 全局过滤器
        - AddRequestHeader=key, Hey World! # , 前半部分是 key, 后半部分是内容

那我们可以重启一下网关服务 , 看一下效果

访问 http://127.0.0.1:10010/user/testHeader

4.4 自定义全局过滤器

全局过滤器 , 其实就是自定义过滤器 . 它的作用也是对一切进入网关的请求和微服务的响应进行处理的 , 与 GatewayFilter 的作用一致 .

但是之前学习的过滤器 , 它的作用都是固定的 . 如果我们希望拦截请求 , 然后实现自己的业务逻辑 , 那 Spring 提供给我们的 31 种过滤器则无法实现 .

那接下来我们就给大家讲一下自定义全局过滤器的实现步骤

需求 : 自定义一个全局过滤器 , 拦截请求 , 判断请求的参数中是否满足以下条件

  • 参数中是否有 keyword
  • keyword 参数值是否为 helloworld

如果同时满足则放行 , 否则进行拦截 , 并返回 403 状态码给用户 .

第一步 : 创建一个全局过滤器类

第二步 : 实现 GlobalFilter 接口 , 重写他的 filter 方法

其中 , 这个方法提供了两个参数

  • ServerWebExchange exchange : 封装了请求的上下文信息, 包含: request、response
  • GatewayFilterChain chain : 过滤器链

第三步 : 添加 @Component 注解 , 将全局过滤器添加到容器中 . 添加 @Order(-1) 注解表示优先执行

数字越小 , 执行越靠前

第四步 : 编写校验规则

java 复制代码
package com.example.gatway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
@Order(-1) // 数字越小, 执行越靠前
public class AuthorizeFilter implements GlobalFilter {
    /**
     * 过滤规则方法实现
     *
     * @param exchange 封装了请求的上下文信息, 包含: request response
     * @param chain    过滤器链
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 获取 URL 的参数
        // e.g. http://127.0.0.1:8080/testHeader?name=zhangsan
        // 获取的是 ? 后面的内容
        MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();// 建议通过 var 的方式直接生成返回值类型

        // 2. 获取 ? 后面的第一个参数
        String keyword = queryParams.getFirst("keyword");

        // 3. 设置条件
        if (keyword.equals("helloworld")) {
            // 满足条件则放行
            return chain.filter(exchange);
        }

        // 4. 不满足规则, 需要设置状态码 403
        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);

        // 5. 返回 setComplete 方法就代表过滤器链结束
        return exchange.getResponse().setComplete();
    }
}

我们可以重启一下 gateway-server 服务 , 看一下效果

不带 value 的情况 : http://127.0.0.1:10010/user/testHeader?keyword=

带 value 的情况 : http://127.0.0.1:10010/user/testHeader?keyword=helloworld

4.5 过滤器的执行顺序

当请求进入到网关 , 会碰到三类过滤器 : 当前路由的过滤器、DefaultFilter、GlobalFilter

请求路由之后 , 会将这三类过滤器 , 合并到一个过滤器链 (集合) 中 , 排序后依次执行每个过滤器

过滤器的执行顺序

  1. 每个过滤器都必须指定一个 int 类型的 order 值 , order 值越小 , 优先级越高 , 执行顺序越靠前 .
  2. GlobalFilter 通过添加 @Order 注解来指定 order 的值
  3. 路由过滤器和 defaultFilter 的 order 由 Spring 指定 , 默认是按照声明顺序从 1 递增
  4. 当过滤器的 order 值一样的时候 , 会按照 defaultFilter > 路由过滤器 > GlobalFilter 的顺序去执行 .

4.6 小结

过滤器的作用是什么 ?

  1. 对路由的请求或响应做加工处理 , 比如添加请求头
  2. 配置在路由下的过滤器只对当前路由的请求生效

defaultFilters 的作用是什么 ?

  • 对所有路由都生效的过滤器

全局过滤器的作用是什么 ?

  • 对所有路由都生效的过滤器 , 并且可以自定义处理逻辑

实现全局过滤器的步骤

  1. 添加 @Component 注解将全局过滤器注册到 Spring 中
  2. 实现 GlobalFilter 接口 , 重写 filter 方法
  3. 添加 @Order(-1) 注解
  4. 编写处理逻辑

路由过滤器、defaultFilter、全局过滤器的执行顺序

  1. order 值越小 , 优先级越高
  2. 当 order 的值一样时 , defaultFilter > 局部的路由过滤器 > 全局过滤器

五 . 跨域问题

5.1 什么是跨域问题 ?

跨域问题就是浏览器禁止请求的发起者与服务端发生跨域 ajax 请求 , 请求被浏览器拦截的问题

  • 域名不同 : 比如 www.taobao.comwww.taobao.org
  • 端口不同 : localhost:8080 和 localhost:8081
  • 协议不同 : 一个是 HTTPS , 一个是 HTTP

5.2 模拟跨域问题

我们先将需要用到的测试文件发给大家

index.html

我们可以看一下里面的内容

然后我们将这个文件替换掉 nginx 内部默认的 index.html

那这时候 , 就出现跨域问题了 , 我们的 index.html 要访问 10010 端口 , 但是它所对应的 nginx 端口号是 80 , 这就是域名相同但是端口号不同的情况 .

那我们就可以启动 nginx 服务 , 来看一下效果

浏览器直接访问 localhost : http://localhost/

5.3 解决跨域问题

在 gateway-server 的 application.yml 中进行配置即可

yaml 复制代码
spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决 options 请求 (非 get、post 方法) 被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
              - "http://localhost"  # 80 端口要省略不写
              - "http://127.0.0.1"
            allowedMethods: # 允许的跨域 ajax 的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息, * 代表请求头包含什么信息都可以
            allowCredentials: true # 是否允许携带 cookie
            maxAge: 360000 # 这次跨域检测的有效期 - 避免频繁发起跨域检测, 服务端返回 Access-Control-Max-Age 来声明的有效期

注意 : globalcors 要与 routes 或者 default-filters 同级

此时我们重启网关服务


我们还可以通过 @CrossOrigin 注解让当前类解决跨域问题


小结 :

  1. 网关核心概念与作用

    1. 核心功能:身份认证、服务路由、负载均衡、请求限流,作为微服务统一入口拦截和转发请求。
    2. 技术选型:Spring Cloud 中主流网关实现为 Gateway(响应式编程)和 Zuul(阻塞式),Gateway 性能更优。
  2. 网关服务搭建与路由配置

    1. 搭建步骤:创建网关模块,引入 Gateway 和 Nacos 依赖,配置端口、服务名、Nacos 地址及路由规则。

    2. 路由要素:

      • id:路由唯一标识;
      • uri :目标地址(lb://服务名实现负载均衡);
      • predicates:路由断言(如 Path、Method、Header 等条件匹配)。
  3. 路由断言与过滤器工厂

    1. 断言工厂 :通过规则字符串(如 Path=/user/**)判断请求是否符合路由条件,支持时间、路径、参数等多种断言类型。

    2. 过滤器工厂:

      • 路由过滤器 :针对单个路由生效,如 AddRequestHeader 添加请求头;
      • 默认过滤器(default-filters):对所有路由生效;
      • 全局过滤器 :自定义拦截逻辑,需实现 GlobalFilter 接口,通过 @Order 控制执行顺序。
  4. 跨域问题解决方案

    1. 跨域定义:域名、端口、协议不同导致的浏览器请求拦截问题。
    2. Gateway 配置 :通过 globalcors 配置允许的域名、请求方式、头信息等,解决跨域访问限制。
相关推荐
憧憬成为web高手32 分钟前
皮卡丘靶场速通--sql 2
数据库·sql·mybatis
段一凡-华北理工大学1 小时前
向量数据库实战:选型、调优与落地~系列文章12:文本分块策略实战:chunk_size 怎么选?重叠多少?
开发语言·数据库·后端·oracle·rust·工业智能体·高炉智能化
二宝哥2 小时前
CentOS 7.9 系统下 Redis 5.0.4 单机安装与源码编译安装指南
redis·centos
oradh3 小时前
Oracle XTTS实现跨版本迁移和升级(Oracle 11g单库升级至19C RAC集群)
数据库·oracle·11g升级19c·xtts跨版本迁移和升级
z123456789864 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
程序猿DD4 小时前
一个 API Key,统一调用大模型、生图和联网搜索
数据库·网关
TlSfoward5 小时前
抓包代理链路下的 TLS 指纹变化分析 TLSFOWARD抓包工具
数据库·爬虫·网络协议·搜索引擎·php
数智化管理手记5 小时前
账龄分析手工统计易遗漏?自动账龄分析工具怎么搭建
大数据·网络·数据库·人工智能·数据挖掘
LabVIEW开发5 小时前
NI Package Manager安装LabVIEW时InternalServerError错误
网络·数据库·labview·labview知识·labview功能·labview程序
SelectDB5 小时前
PostgreSQL 实时同步到 Apache Doris:一站式 CDC 解决方案
数据库