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:00[America/Denver]
Before 是某个时间点之前的请求 - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between 是某两个时间点之间的请求 - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/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 配置允许的域名、请求方式、头信息等,解决跨域访问限制。
相关推荐
vivo互联网技术6 分钟前
号码生成系统的创新实践:游戏周周乐幸运码设计
redis·后端·架构
都叫我大帅哥28 分钟前
Redis中zset内存变形记
java·redis
大只鹅40 分钟前
两级缓存 Caffeine + Redis 架构:原理、实现与实践
redis·缓存·架构
都叫我大帅哥42 分钟前
Redis的ZSet:从“青铜”到“王者”的排序神器
java·redis
小小霸王龙!1 小时前
互联网大厂Java面试实录:Spring Boot与微服务在电商场景中的应用
java·spring boot·redis·微服务·电商
混乱意志1 小时前
dgraph example数据导入
数据库·后端
Web极客码1 小时前
WordPress 站点漏洞利用:数据库恶意注入与多重感染的案例分析
数据库·wordpress·网站安全·数据库注入·wordpress漏洞·wordpress安全插件
刺客xs1 小时前
MySQL数据库----DML语句
数据库·mysql
都叫我大帅哥1 小时前
Redis BitMap 深度解剖:比特世界的精密引擎
redis
嘉讯科技HIS系统1 小时前
嘉讯科技:医疗信息化、数字化、智能化三者之间的关系和区别
大数据·数据库·人工智能·科技·智慧医疗