微服务网关与配置中心 学习目标

简介: 本课程学习Spring Cloud Gateway网关的实现,掌握路由配置、负载均衡、内置过滤器(如StripPrefix)及全局过滤器的使用。重点包括自定义身份校验过滤器、JWT鉴权流程、微服务间用户信息传递,并结合Nacos实现服务发现与统一配置管理,完成前后端联调与权限控制。

学习目标

  1. 能够说出用什么实现的网关以及实现了哪些功能
  2. 能够创建网关工程实现路由功能
  3. 能够使用网关内置过滤器StripPrefix
  4. 能够定义全局过滤器并测试通过
  5. 能够实现全局身份校验过滤器
  6. 能够在微服务实现用户身份拦截器
  7. 能够说出网关鉴权具体的实现步骤
  8. 能够实现商城项目前后端联调
  9. 能够将微服务配置文件在Nacos统一管理
  10. 能够说出微服务配置文件的加载顺序
  11. 能够将微服务配置文件抽取公共配置
  12. 能够说出Nacos配置热更新方案

1 网关路由

1.1.认识网关

1.1.1 问题分析

目前为止我们已将黑马商城单体项目重构为微服务架构,今天的目标是前后端联调,下边思考几个问题:

1.1.1.1 前端面对多个后端入口

项目采用单体架构时前端通过nginx负载均衡访问后端服务,如下图:

同一个服务部署多份仅端口不同,且部署在不同地域(北上广深)的方式,一般称:水平复制、异地容灾

项目采用微服务架构时原来的黑马商城分成了购物车服务、交易服务、支付服务等多个服务,前端面对多个后端入口 ,如下图:

前端面对多个后端入口不方便前端开发,效率低下。仍然可以采用nginx去解决【注意不是最佳】,如下图:

在nginx中创建多个upstream ,例如:

http {
    upstream item_services {
        server 127.0.0.1:8081 weight=3;  # 分配较高权重
        server 127.0.0.1:8082 weight=2;  # 分配中等权重
        server 127.0.0.1:8083 weight=1;  # 分配较低权重
    }
    upstream carts_services {
        server 127.0.0.1:7081 weight=3;  # 分配较高权重
        server 127.0.0.1:7082 weight=2;  # 分配中等权重
        server 127.0.0.1:7083 weight=1;  # 分配较低权重
    }
    ....
    server {
        listen       80;  # 监听 80 端口,也可以根据需要更改
        server_name  localhost;  # 更改为你的域名或 IP 地址
        location /items/ {  # 这里可以根据需要调整路径前缀
            proxy_pass http://item_services;
        }
        location /carts/ {  # 这里可以根据需要调整路径前缀
            proxy_pass http://carts_services;
        }
        ....
   }

1.1.1.2 用户身份校验放在哪?

单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,这就存在一些问题:每个微服务都需要编写身份校验、用户信息获取的接口,非常麻烦。

用户身份校验最好放在一个统一的地方,放在上图中nginx的位置上上最合适,那nginx的作用如下:

1.请求路由,根据请求路径将请求转发到不同的应用服务器。

2.负载均衡,通过负载均衡算法将请求转发到不同的应用服务器。

3.用户身份鉴权,校验用户身份及用户的权限。

1.1.2 认识网关

Nginx目前扮演的角色就是:网关,什么是网关?

顾明思议,网关就是络的口。数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发以及数据安全的校验

我们现在要根据需求使用Java在网关实现路由转发和用户身份认证的功能:

  1. 根据请求Url路由到具体的微服务
  2. 校验用户的token,取出token中的用户信息。
  3. 从nacos中取出服务实例进行负载均衡。

但 在nginx中进行java编程是非常困难的,所以我们需要一个使用java开发的网关。

AI:java微服务网关

  • Netflix Zuul:早期实现,目前已经淘汰
  • Spring Cloud Gateway:基于Spring的WebFlux技术,完全支持响应式编程,吞吐能力更强

课堂中我们以Spring Cloud Gateway为例来讲解,如下图:

前端请求网关根据请求路径路由到微服务,网关从nacos获取微服务实例地址将请求转发到具体的微服务实例上。生产环境中网关也是集群部署,在网关前边通过nginx进行负载均衡,如下图:

为什么这里需要Naocs?

答:网关怎么根据用户访问路径:http://baidu.com/carts/findAll,决定找到carts购物车服务呢?

此时就会用到服务注册与发现的知识点,而能帮我们实现这个功能的无疑nacos就可以做到。

1.1.3. 面试题

说说Spring Cloud五大组件?

你们项目网关用什么实现,实现了什么功能?

1.2. 实现网关路由

接下来,我们先看下如何利用网关实现请求路由。由于网关本身也是一个独立的微服务,因此也需要创建一个模块,大概步骤如下:

AI:Spring Cloud Gateway实现路由

  • 创建网关微服务
  • 引入Spring Cloud Gateway、NacosDiscovery依赖
  • 编写启动类
  • 配置网关路由

1.2.1. 创建网关工程

首先,我们要在hmall下创建一个新的module,命名为hm-gateway,作为网关微服务:

1.2.2 引入依赖

hm-gateway模块的pom.xml文件中引入依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>hmall-parent</artifactId>
    <groupId>com.hmall</groupId>
    <version>1.0.0</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <artifactId>hm-gateway</artifactId>
  <properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>
  <dependencies>
    <!--common-->
    <dependency>
      <groupId>com.hmall</groupId>
      <artifactId>hm-common</artifactId>
      <version>1.0.0</version>
    </dependency>
    <!--网关-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!--nacos discovery-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--负载均衡-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
  </dependencies>
  <build>
    <finalName>${project.artifactId}</finalName>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

1.2.3 启动类

hm-gateway模块的com.hmall.gateway包下新建一个启动类:

代码如下:

package com.hmall.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

1.2.4 配置路由

接下来,在hm-gateway模块的resources目录新建一个application.yaml文件,内容如下:

server:
  port: 8080
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.101.68:8848
    gateway:
      routes:
        - id: item
          uri: lb://item-service
          predicates:
            - Path=/items/**

这里配置nacos地址,网关需要从nacos获取微服务的实例地址。

路由规则routes包括四个属性,定义语法如下:

  • id:路由的唯一标示,自定义即可,但要保证全局唯一
  • predicates:路由断言,Predicates 是用于判断请求是否满足特定条件的组件。
  • filters:路由过滤条件,稍后讲解。
  • uri:路由目标地址,lb://代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。

application.yaml文件完整内容如下:

server:
  port: 8080
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.101.68:8848
    gateway:
      routes:
        - id: item # 路由规则id,自定义,唯一
          uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
          predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
            - Path=/items/**,/search/** # 这里是以请求路径作为判断规则
        - id: cart
          uri: lb://cart-service
          predicates:
            - Path=/carts/**
        - id: user
          uri: lb://user-service
          predicates:
            - Path=/users/**,/addresses/**
        - id: trade
          uri: lb://trade-service
          predicates:
            - Path=/orders/**
        - id: pay
          uri: lb://pay-service
          predicates:
            - Path=/pay-orders/**

分别表示对当前五个微服务的路由分发

1.2.5 测试

启动GatewayApplication,通过网关请求微服务, http://localhost:8080是网关的根路径,根据网关路由的配置请求具体的URL。

例如:要访问商品服务需要URL以/items开头,访问交易服务需要以/orders开头.

下边访问商品查询的接口地址:

http://localhost:8080/items/page?pageNo=1&pageSize=1

启动网关服务、商品服务,访问此链接。

1.2.6.路由断言(了解)

路由规则的定义语法如下:

spring:
  cloud:
    gateway:
      routes:
        - id: item
          uri: lb://item-service
          predicates:
            - Path=/items/**,/search/**

这里我们重点关注predicates,也就是路由断言。Spring Cloud Gateway中支持的断言类型有很多:

名称

说明

示例

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

权重处理

拿Header举例,Header是根据请求头的内容来控制网关访问的。

Header需要两个参数header和regexp(正则表达式),也可以理解为Key和Value,匹配请求携带信息。

修改网关的配置文件application.yml,如下:

Header中包括X-Request-Id才可正常访问,X-Request-Id 并不是HTTP标准的一部分,常用用于标识一次请求的ID。

重启网关,访问http://localhost:8080/items/page?pageNo=1&pageSize=1,不可以正常访问,因为没有添加X-Request-Id请求头。

下边我们用idea的httpclient插件测试,使用该插件可以方便的添加http请求头信息。

创建一个请求:

在下边的界面中输入请求url并设置X-Request-Id请求头

点击“运行”可以正常访问。

如果IDEA没有HTTP client插件请自行安装

1.3. 小结

我们使用Spring Cloud Gateway创建网关实现了路由、负载均衡的功能。

  1. 网关路由

在application.yaml中配置网关路由,格式如下:

- id: product
  uri: lb://item-service
  predicates:
    - Path=/product/**
  filters:

id: 路由规则id,自定义,唯一

uri: 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表

predicates: 路由断言,判断当前请求是否符合当前规则,符合则转发到目标服务

Path:符合请求路径执行此路由

filters: 网关过滤器(稍后讲解)

  1. 负载均衡

请求首先到达网关,网关从nacos获取服务实例列表,通过Spring Cloud LoadBalancer负载均衡器选取一个服务实例,请求转发到该服务实例上。

2 网关鉴权(了解)

2.1.认识网关鉴权

单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,不再共享数据。也就意味着每个微服务都需要做身份校验,这显然不可取。

我们的登录是基于JWT来实现的,校验JWT的算法复杂,而且需要用到密钥。如果每个微服务都去做身份校验,这就存在着两大问题:

  • 每个微服务都需要知道JWT的密钥,不安全。
  • 每个微服务重复编写身份校验代码、权限校验代码,代码重复不易维护。

既然网关是所有微服务的入口,一切请求都需要先经过网关。我们完全可以把身份校验的工作放到网关去做,这样之前说的问题就解决了:

  • 只需要在网关和用户服务保存秘钥
  • 只需要在网关开发身份校验功能

网关鉴权是指在网关对请求进行身份验证的过程。这个过程确保只有经过授权的用户或设备才能访问特定的服务或资源。下图是网关鉴权的流程图:

流程如下:

  1. 用户登录成功生成token并存储在前端
  2. 前端携带token访问网关
  3. 网关解析token中的用户信息,网关将请求转发到微服务,转发时携带用户信息
  4. 微服务从http头信息获取用户信息
  5. 微服务之间远程调用使用内部接口(无状态接口,即后端微服务集群都不做权限校验)

网关鉴权除了验证token的合法性还有一层含义是校验用户的权限通常校验用户的权限不放在网关而是放在微服务去实现,因为具体的接口在微服务,在token中包括了用户的权限字符串,微服务接收到权限字符串通过spring security框架进行拦截实现,具体的方案就是在controller接口上通过下边的注解实现:

hasAuthority('authority'): 检查是否有指定的权限(authority),这通常与数据库中的权限字符串对应,可通过AI学习spring security框架(当然我们中州就已经学了,所以这块就很明晰):

AI:spring security授权注解有哪些

AI:@PreAuthorize("hasRole('ADMIN')") 除了hasRole还有哪些

这些问题将在接下来几节一一解决。

除了上述网关鉴权,微服务集群还有很多通用的方案,笔者大致罗列,感兴趣的自行搜索学习:

  1. 集中式认证与授权(如 OAuth2 + JWT、SSO单点登录)。
  2. API 网关统一校验
  3. 服务间调用的权限校验(如 mTLS、服务令牌)。
  4. 基于角色的访问控制(RBAC)
  5. 基于属性的访问控制(ABAC)
  6. 分布式权限校验(如 Casbin)。
  7. 自定义权限校验
  8. 混合方案。

2.2.网关内置过滤器

2.2.1 认识网关过滤器

网关鉴权必须在请求转发到微服务之前做,否则就失去了意义。而网关的请求转发是Gateway内部代码实现的,要想在请求转发之前做身份校验,就必须了解Gateway内部工作的基本原理。

如图所示:

  1. 客户端请求进入网关后由HandlerMapping对请求做判断,找到与当前请求匹配的路由规则(Route),然后将请求交给WebHandler去处理。
  2. WebHandler则会加载当前路由下需要执行的过滤器链(Filter chain),然后按照顺序逐一执行过滤器(后面称为Filter)。
  3. 图中Filter被虚线分为左右两部分,是因为Filter内部的逻辑分为prepost两部分,分别会在请求路由到微服务之前之后被执行。
  4. 只有所有Filterpre逻辑都依次顺序执行通过后,请求才会被路由到微服务。
  5. 微服务返回结果后,再倒序执行Filterpost逻辑。
  6. 最终把响应结果返回。

如图中所示,最终请求转发是有一个名为NettyRoutingFilter的过滤器来执行的,而且这个过滤器是整个过滤器链中顺序最靠后的一个。如果我们能够定义一个过滤器,在其中实现身份校验逻辑,并且将过滤器执行顺序定义到NettyRoutingFilter之前,这就符合我们的需求了!

那么,该如何实现一个网关过滤器呢?

网关过滤器链中的过滤器有两种:

  • GatewayFilter:路由过滤器,作用范围比较灵活,可以是任意指定的路由Route.
  • GlobalFilter:全局过滤器,作用范围是所有路由,不可配置。

其实GatewayFilterGlobalFilter这两种过滤器的方法签名完全一致:

/**
 * 处理请求并将其传递给下一个过滤器
 * @param exchange 当前请求的上下文,其中包含request、response等各种数据
 * @param chain 过滤器链,基于它向下传递请求
 * @return 根据返回值标记当前请求是否被完成或拦截,chain.filter(exchange)就放行了。
 */
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);

FilteringWebHandler在处理请求时,会将GlobalFilter装饰为GatewayFilter,然后放到过滤器链中,排序以后依次执行。

2.2.2 内置过滤器

Gateway中内置了很多的GatewayFilter,详情可以参考官方文档,见下表:

过滤器工厂

作用

参数

AddRequestHeader

为原始请求添加Header

Header的名称及值

AddRequestParameter

为原始请求添加请求参数

参数名称及值

AddResponseHeader

为原始响应添加Header

Header的名称及值

DedupeResponseHeader

剔除响应头中重复的值

需要去重的Header名称及去重策略

Hystrix

为路由引入Hystrix的断路器保护

HystrixCommand的名称

FallbackHeaders

为fallbackUri的请求头中添加具体的异常信息

Header的名称

PrefixPath

为请求添加一个 preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host

RequestRateLimiter

用于对请求限流,限流算法为令牌桶

keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus

RedirectTo

将原始请求重定向到指定的URL

http状态码及重定向的url

RemoveHopByHopHeadersFilter

为原始请求删除IETF组织规定的一系列Header

默认就会启用,可以通过配置指定仅删除哪些Header

RemoveResponseHeader

为原始请求删除某个Header

Header名称

RemoveRequestHeader

为原始请求删除某个Header

Header名称

RewritePath

重写原始的请求路径

原始路径正则表达式以及重写后路径的正则表达式

RewriteResponseHeader

重写原始响应中的某个Header

Header名称,值的正则表达式,重写后的值

SaveSession

在转发请求之前,强制执行websession::save操作

secureHeaders

为原始响应添加一系列起安全作用的响应头

无,支持修改这些安全响应头的值

SetPath

修改原始的请求路径

修改后的路径

SetResponseHeader

修改原始响应中某个Header的值

Header名称,修改后的值

SetStatus

修改原始响应的状态码

HTTP状态码,可以是数字,也可以是字符串

StripPrefix

用于截断原始请求的路径

使用数字表示要截断的路径的数量

Retry

针对不同的响应进行重试

retries、statuses、methods、series

RequestSize

设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回413 Payload Too Large

请求包大小,单位为字节,默认值为5M

ModifyRequestBody

在转发请求之前修改原始请求体内容

修改后的请求体内容

ModifyResponseBody

修改原始响应体的内容

修改后的响应体内容

内置过滤器有很多,具体在工作中根据需求去使用即可。

下边演示一个非常有用的过滤器 StripPrefix,它的作用:移除路径前缀

比如:为了路径的统一,我们规定所有请求以/product/开头的全部路由到商品服务,

网关的路由配置如下

- id: product
  uri: lb://item-service
  predicates:
    - Path=/product/**

比如访问商品分页查询接口,访问网关的路径为:/product/items/page, 实际的下游服务器地址是 /items/page,如果使用上边的路由配置是无法实现的。

原因是:

请求:http://localhost:8080/product/items/page?pageNo=1&pageSize=1

路由到:http://localhost:8081/product/items/page?pageNo=1&pageSize=1

正确的应该路由到:http://localhost:8081/items/page?pageNo=1&pageSize=1

可以使用StripPrefix过滤器实现

下边配置一个新的路由:

- id: product
  uri: lb://item-service
  predicates:
    - Path=/product/**
  filters:
    - StripPrefix=1

StripPrefix=1表示去除一级路径前缀

使用StripPrefix=1后

请求:http://localhost:8080/product/items/page?pageNo=1&pageSize=1

路径到:http://localhost:8081/items/page?pageNo=1&pageSize=1(正确)

如果配置StripPrefix=2,最终转发到商品服务的路径为/page?pageNo=1&pageSize=1(此路径在商品服务不存在)

2.3. 自定义过滤器

无论是GatewayFilter还是GlobalFilter支持自定义,只不过编码方式、使用方式略有差别。

2.3.1 自定义GlobalFilter

2.3.2.1 编码实现

自定义GlobalFilter简单很多,直接实现GlobalFilter即可,而且也无法设置动态参数,我们在gateway中实现:

package com.hmall.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class PrintAnyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 编写过滤器逻辑
        log.info("打印全局过滤器");
        // 放行
        return chain.filter(exchange);
        // 拦截
        //        ServerHttpResponse response = exchange.getResponse();
        //        response.setRawStatusCode(401);
        //        return response.setComplete();
    }
    @Override
    public int getOrder() {
        // 过滤器执行顺序,值越小,优先级越高
        return 0;
    }
}

全局过滤器不用在路由中配置。请自行测试。

2.3.2 自定义GatewayFilter(自学)

2.3.2.1 定义GatewayFilter

自定义GatewayFilter不是直接实现GatewayFilter,而是继承AbstractGatewayFilterFactory。最简单的方式是这样的:

注意:该类的名称一定要以GatewayFilterFactory为后缀!

package com.hmall.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
 * @author Mr.M
 * @version 1.0
 * @description 第一个网关过滤器
 * @date 2024/8/7 14:41
 */
@Component
@Slf4j
public class FirstFilterGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
    @Override
    public GatewayFilter apply(Object config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerHttpRequest request = exchange.getRequest();
                log.info("请求路径:{}",request.getPath());
                log.info("网关过滤器FirstFilterGatewayFilterFactory执行啦...");
                //放行
                return chain.filter(exchange);
                //拦截 返回401状态码
                //exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                //return exchange.getResponse().setComplete();
            }
        };
    }
    
}

2.3.2.2 配置过滤器

下边的配置仅在product路由中有效

- id: product
  uri: lb://item-service
  predicates:
    - Path=/product/**
  filters:
    - StripPrefix=1
    - FirstFilter # 此处直接以自定义的GatewayFilterFactory类名称前缀类声明过滤器

配置完成重启网关,访问http://localhost:8080/product/items/page?pageSize=1&pageNo=1 ,观察控制台打出“请求路径...”日志,说明过滤器成功执行。

下边的配置在所有路由中都有效:

spring:
  cloud:
    gateway:
      default-filters:
        - FirstFilter # 此处直接以自定义的GatewayFilterFactory类名称前缀类声明过滤器

请自行测试。

2.3.3 小结

两种自定义过滤器的方式:

  • GatewayFilter:路由过滤器

作用范围比较灵活,可以是任意指定的路由Route.

继承AbstractGatewayFilterFactory,并在路由配置中指定过滤器。

过滤器的名称规则:以GatewayFilterFactory作为后缀。

  • GlobalFilter:全局过滤器

作用范围是所有路由,不可配置。

实现GlobalFilter接口。

实现Ordered 接口可以指定过滤器顺序,实现getOrder()方法,返回值越小优先级越好。

2.4.身份校验过滤器

接下来,我们就利用自定义GlobalFilter来完成身份校验。

2.4.1 JWT工具类

身份校验需要用到JWT,而且JWT的加密需要秘钥和加密工具。这些在单体项目hm-service中已经有了,我们直接拷贝过来:

具体作用如下:

  • AuthProperties:配置身份校验需要拦截的路径,因为不是所有的路径都需要登录才能访问
  • 这个刚复制进去会报错,先不用管,执行后续步骤后自己就没了
  • JwtProperties:定义与JWT工具有关的属性,比如秘钥文件位置
  • SecurityConfig:工具的自动装配
  • JwtTool:JWT工具,其中包含了校验和解析token的功能
  • hmall.jks:秘钥文件

2.4.2 配置白名单

其中AuthPropertiesJwtProperties所需的属性要在application.yaml中配置。

hm:
  jwt:
    location: classpath:hmall.jks # 秘钥地址
    alias: hmall # 秘钥别名
    password: hmall123 # 秘钥文件密码
    tokenTTL: 30m # 登录有效期
  auth:
    excludePaths: # 无需身份校验的路径
      - /search/**
      - /users/login
      - /items/**

excludePaths配置白名单地址,即无需身份校验的路径。

2.4.3 身份校验过滤器

接下来,我们定义一个身份校验的过滤器:

代码如下:

package com.hmall.gateway.filter;
import com.hmall.common.exception.UnauthorizedException;
import com.hmall.common.utils.CollUtils;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    private final JwtTool jwtTool;
    private final AuthProperties authProperties;
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取Request
        ServerHttpRequest request = exchange.getRequest();
        //请求路径
        String path = request.getPath().toString();
        //白名单
        List<String> excludePaths = authProperties.getExcludePaths();
        //判断当前请求路径是否是白名单,使用antPathMatcher进行匹配
        for (String excludePath : excludePaths) {
            boolean match = antPathMatcher.match(excludePath, path);
            if (match) {
                return chain.filter(exchange);
            }
        }
        //获取请求头的token
        String token = exchange.getRequest().getHeaders().getFirst("authorization");
        if(token==null){
            //如果token为空则返回401错误
            ServerHttpResponse response = exchange.getResponse();
            response.setRawStatusCode(401);
            return response.setComplete();
        }
        //如果token不为空则校验token
        Long userId = null;
        try {
            userId = jwtTool.parseToken(token);
        } catch (Exception e) {
            //返回token无效的错误
            ServerHttpResponse response = exchange.getResponse();
            response.setRawStatusCode(401);
            return response.setComplete();
        }
        // TODO 5.如果有效,传递用户信息
        log.info("userId:{}",userId);
        // 6.放行
        return chain.filter(exchange);
    }
    @Override
    public int getOrder() {
        return 0;
    }
}

2.4.4 测试

重启网关进行测试。

由于“/items/** ” 在excludePaths中配置,所以访问/items开头的路径在未登录状态下不会被拦截:

http://localhost:8080/items/page?pageNo=1&pageSize=1

相关文章
|
12天前
|
数据采集 人工智能 安全
|
7天前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:七十、小树成林,聚沙成塔:随机森林与大模型的协同进化
随机森林是一种基于决策树的集成学习算法,通过构建多棵决策树并结合它们的预测结果来提高准确性和稳定性。其核心思想包括两个随机性:Bootstrap采样(每棵树使用不同的训练子集)和特征随机选择(每棵树分裂时只考虑部分特征)。这种方法能有效处理大规模高维数据,避免过拟合,并评估特征重要性。随机森林的超参数如树的数量、最大深度等可通过网格搜索优化。该算法兼具强大预测能力和工程化优势,是机器学习中的常用基础模型。
344 164
|
6天前
|
机器学习/深度学习 自然语言处理 机器人
阿里云百炼大模型赋能|打造企业级电话智能体与智能呼叫中心完整方案
畅信达基于阿里云百炼大模型推出MVB2000V5智能呼叫中心方案,融合LLM与MRCP+WebSocket技术,实现语音识别率超95%、低延迟交互。通过电话智能体与座席助手协同,自动化处理80%咨询,降本增效显著,适配金融、电商、医疗等多行业场景。
345 155
|
7天前
|
编解码 人工智能 自然语言处理
⚽阿里云百炼通义万相 2.6 视频生成玩法手册
通义万相Wan 2.6是全球首个支持角色扮演的AI视频生成模型,可基于参考视频形象与音色生成多角色合拍、多镜头叙事的15秒长视频,实现声画同步、智能分镜,适用于影视创作、营销展示等场景。
580 4
|
15天前
|
SQL 自然语言处理 调度
Agent Skills 的一次工程实践
**本文采用 Agent Skills 实现整体智能体**,开发框架采用 AgentScope,模型使用 **qwen3-max**。Agent Skills 是 Anthropic 新推出的一种有别于mcp server的一种开发方式,用于为 AI **引入可共享的专业技能**。经验封装到**可发现、可复用的能力单元**中,每个技能以文件夹形式存在,包含特定任务的指导性说明(SKILL.md 文件)、脚本代码和资源等 。大模型可以根据需要动态加载这些技能,从而扩展自身的功能。目前不少国内外的一些框架也开始支持此种的开发方式,详细介绍如下。
1018 7