微服务网关与配置中心

简介: 本文介绍了微服务架构下的网关路由与鉴权机制,重点讲解使用Spring Cloud Gateway实现请求路由、负载均衡及JWT身份校验。通过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

相关文章
|
3月前
|
人工智能 前端开发 算法
DeepCode:把论文和想法变成代码的 AI 工具
DeepCode 是香港大学开源的 AI 编码工具,通过多智能体协作实现论文转代码、需求转网站、描述转后端三大功能。采用 MIT 协议,已获 7900+ 星标。适合科研人员、独立开发者和技术学习者使用,能有效提升开发效率。
|
11月前
|
安全 Java Apache
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 身份和权限认证
本文介绍了 Apache Shiro 的身份认证与权限认证机制。在身份认证部分,分析了 Shiro 的认证流程,包括应用程序调用 `Subject.login(token)` 方法、SecurityManager 接管认证以及通过 Realm 进行具体的安全验证。权限认证部分阐述了权限(permission)、角色(role)和用户(user)三者的关系,其中用户可拥有多个角色,角色则对应不同的权限组合,例如普通用户仅能查看或添加信息,而管理员可执行所有操作。
554 0
|
2月前
|
安全 Java Spring
过滤器链加载原理
本文深入解析Spring Security核心过滤器链原理,重点剖析DelegatingFilterProxy如何通过代理模式加载springSecurityFilterChain,结合FilterChainProxy与SecurityFilterChain源码,揭示十五个安全过滤器的初始化及执行流程,帮助理解框架底层机制。
过滤器链加载原理
|
2月前
|
安全 数据安全/隐私保护 微服务
什么是权限管理
权限管理包含认证与授权两大核心:认证确认用户身份(如登录),授权则根据角色分配访问权限,确保系统安全。常见模型有ACL、RBAC等,通过角色叠加实现菜单级控制,保障数据安全与操作合规,是系统安全的基石。
什么是权限管理
|
2月前
|
安全 Java 数据安全/隐私保护
认识SpringSecurity
SpringSecurity是Java领域主流的安全框架,核心功能包括认证、鉴权及防护常见攻击。支持表单、OAuth2、JWT等多种认证方式,基于过滤器链实现灵活权限控制,并提供CSRF、会话固定等安全防护机制。
|
2月前
|
存储 安全 Java
认证源码分析与自定义后端认证逻辑
本文深入分析Spring Security认证流程,从UsernamePasswordAuthenticationFilter到AuthenticationManager、ProviderManager,层层剖析认证机制。重点讲解自定义UserDetailsService实现、权限加载及SecurityContext存储原理,最终实现基于数据库的用户认证,并提供完整配置与代码示例,助你掌握安全认证核心逻辑。(238字)
|
2月前
|
前端开发 安全 Java
自定义认证前端页面
本文介绍Spring Security基础配置:前端login.html页面设置,后端新增接口与SecurityConfig安全配置,实现表单登录认证,包括登录页、接口权限、CSRF关闭等,并通过访问/demo/index验证登录跳转流程。
|
2月前
|
安全 Java 开发工具
工程搭建与验证
本文介绍如何使用阿里云脚手架快速搭建Spring Boot工程,并整合Spring Security。内容涵盖工程创建、代码导入、Web依赖配置及安全验证流程,包含访问控制与默认登录机制,附完整代码仓库地址,助你快速上手安全开发。
|
2月前
|
存储 缓存 Java
自定义注解
本文介绍Java自定义注解的实现原理与应用,结合Spring AOP和过滤器完成日志、权限控制及登录验证。通过@Target、@Retention等元注解定义注解,并在控制器中通过拦截器解析,实现如无需登录访问等功能,提升代码可读性与复用性。(239字)
|
2月前
|
存储 缓存 安全
常用过滤器介绍
Spring Security基于AOP思想,通过过滤器链实现安全控制。本文详解15个核心过滤器,如SecurityContextPersistenceFilter、CsrfFilter、UsernamePasswordAuthenticationFilter等,分别负责上下文管理、防跨站请求伪造、认证处理等功能,助你深入理解其工作原理。(238字)