当前位置: 首页 > news >正文

微服务:网关路由和登录校验

续上篇:微服务:服务的注册与调用和OpenFiegn-CSDN博客

参考:黑马程序员之微服务

 💥 该系列属于【SpringBoot基础】专栏,如您需查看其他SpringBoot相关文章,请您点击左边的连接

目录

一、网关路由

1. 网关

2. 实践

3. 路由属性

4. 路由断言

5. 路由过滤器

二、网关登录校验

1. 网关过滤器

2. 实现登录校验【重点】 

3. 微服务获取用户【重点】 

4. OpenFeign传递用户【重点】


由于每个微服务都有不同的地址或端口,入口不同,在与前端联调会有一些问题:

  • 请求不同数据时要访问不同的入口,需要维护多个入口地址,麻烦

  • 前端无法调用nacos,无法实时更新服务列表

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

  • 每个微服务都需要编写登录校验、用户信息获取的功能吗?

  • 当微服务之间调用时,该如何传递用户信息?

答案:可以通过通过网关技术解决上述问题。

一、网关路由

1. 网关

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

前端请求不能直接访问微服务,而是要请求网关:

  • 网关可以做安全控制,也就是登录身份校验,校验通过才放行

  • 通过认证后,网关再根据请求判断应该访问哪个微服务,将请求转发过去

在SpringCloud当中,提供了两种网关实现方案:

  • Netflix Zuul:早期实现,目前已经淘汰

  • SpringCloudGateway:基于Spring的WebFlux技术,完全支持响应式编程,吞吐能力更强

2. 实践

 (1)创建项目

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

 (2)引入依赖

hm-gateway模块的pom.xml文件中引入网关、nacos和负载均衡的依赖:

        <!--网关--><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>

 (3)启动类和配置文件

项目结构

启动类: 

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);}
}

配置文件:

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

测试结果:

3. 路由属性

4. 路由断言

SpringCloudGateway中支持的断言类型有很多:

名称

说明

示例

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

权重处理

5. 路由过滤器

二、网关登录校验

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

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

  • 只需要在网关和用户服务保存秘钥

  • 只需要在网关开发登录校验功能

 登录校验的流程如图:

1. 网关过滤器

Gateway内部工作的基本原理:

如图所示:

  1. 客户端请求进入网关后由HandlerMapping对请求做判断,找到与当前请求匹配的路由规则(Route),然后将请求交给WebHandler去处理。

  2. WebHandler则会加载当前路由下需要执行的过滤器链(Filter chain),然后按照顺序逐一执行过滤器(后面称为Filter)。

  3. 图中Filter被虚线分为左右两部分,是因为Filter内部的逻辑分为prepost两部分,分别会在请求路由到微服务之前之后被执行。

  4. 只有所有Filterpre逻辑都依次顺序执行通过后,请求才会被路由到微服务。

  5. 微服务返回结果后,再倒序执行Filterpost逻辑。

  6. 最终把响应结果返回。

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

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

  • GatewayFilter:路由过滤器,作用范围比较灵活,可以是任意指定的路由Route.

  • GlobalFilter:全局过滤器,作用范围是所有路由,不可配置。

(1) GlobalFilter

测试结果:

刷新前端的网页,然后执行,取出请求头的JWT信息:

(2) GatewayFilter

测试结果:

刷新前端的网页,然后执行,可以看到过滤器按照优先级顺序执行:

带参过滤器: 

代码:

@Component
public class PrintAnyGatewayFilterFactory // 父类泛型是内部类的Config类型extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> {@Overridepublic GatewayFilter apply(Config config) {// OrderedGatewayFilter是GatewayFilter的子类,包含两个参数:// - GatewayFilter:过滤器// - int order值:值越小,过滤器执行优先级越高return new OrderedGatewayFilter(new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取config值String a = config.getA();String b = config.getB();String c = config.getC();// 编写过滤器逻辑System.out.println("a = " + a);System.out.println("b = " + b);System.out.println("c = " + c);// 放行return chain.filter(exchange);}}, 100);}// 自定义配置属性,成员变量名称很重要,下面会用到@Datastatic class Config {private String a;private String b;private String c;}// 将变量名称依次返回,顺序很重要,将来读取参数时需要按顺序获取@Overridepublic List<String> shortcutFieldOrder() {return List.of("a", "b", "c");}// 返回当前配置类的类型,也就是内部的Config@Overridepublic Class<Config> getConfigClass() {return Config.class;}}

测试结果:

2. 实现登录校验【重点】 

(1)JWT工具

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

具体作用如下:

  • AuthProperties:配置登录校验需要拦截的路径,因为不是所有的路径都需要登录才能访问

  • JwtProperties:定义与JWT工具有关的属性,比如秘钥文件位置

  • SecurityConfig:工具的自动装配

  • JwtTool:JWT工具,其中包含了校验和解析token的功能

  • hmall.jks:秘钥文件

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

hm:jwt:location: classpath:hmall.jksalias: hmallpassword: hmall123tokenTTL: 30mauth:excludePaths:- /search/**- /users/login- /items/**

完整application.yaml代码:

server:port: 8080
spring:application:name: gatewaycloud:nacos:server-addr: 192.168.88.128:8848gateway:routes:- id: item # 路由规则id,自定义,唯一uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务- Path=/items/**,/search/** # 这里是以请求路径作为判断规则- id: carturi: lb://cart-servicepredicates:- Path=/carts/**- id: useruri: lb://user-servicepredicates:- Path=/users/**,/addresses/**- id: tradeuri: lb://trade-servicepredicates:- Path=/orders/**- id: payuri: lb://pay-servicepredicates:- Path=/pay-orders/**
#      default-filters:
#        - PrintAny=1,2,3 # 注意,这里多个参数以","隔开,将来会按照shortcutFieldOrder()方法返回的参数顺序依次复制hm:jwt:location: classpath:hmall.jksalias: hmallpassword: hmall123tokenTTL: 30mauth:excludePaths:- /search/**- /users/login- /items/**

(2)登录校验过滤器

接下来,我们定义一个登录校验的过滤器AuthGlobalFilter:

@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();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取RequestServerHttpRequest request = exchange.getRequest();// 2.判断是否不需要拦截if (isExclude(request.getPath().toString())) {// 无需拦截,直接放行return chain.filter(exchange);}// 3.获取请求头中的tokenString token = null;List<String> headers = request.getHeaders().get("authorization");if (!CollUtils.isEmpty(headers)) {token = headers.get(0);}// 4.校验并解析tokenLong userId = null;try {userId = jwtTool.parseToken(token);} catch (UnauthorizedException e) {// 如果token无效,拦截ServerHttpResponse response = exchange.getResponse();response.setRawStatusCode(401);return response.setComplete();}// TODO 5.如果有效,传递用户信息System.out.println("userId = " + userId);// 6.放行return chain.filter(exchange);}private boolean isExclude(String antPath) {for (String pathPattern : authProperties.getExcludePaths()) {if (antPathMatcher.match(pathPattern, antPath)) {return true;}}return false;}@Overridepublic int getOrder() {return 0;}
}

测试:

excludePaths中的访问:

 不登录直接访问购物车:

登录后再访问:

3. 微服务获取用户【重点】 

网关已经可以完成登录校验并获取登录用户身份信息。但是当网关将请求转发到微服务时,微服务又该如何获取用户身份呢?

由于网关发送请求到微服务依然采用的是Http请求,因此我们可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。

考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。

(1)在登录校验的过滤器中,保存用户到请求头

首先,我们修改登录校验拦截器的处理逻辑,保存用户信息到请求头中:

(2)拦截器获取用户

在hm-common中已经有一个用于保存登录用户的ThreadLocal工具:

在hm-common模块下定义一个拦截器:

拦截器想要生效,还需要定一个配置类进行注册:

@ConditionalOnClass(DispatcherServlet.class) 表示希望Mvc在微服务中生效,但是不需要在网关中生效,因为网关中没有DispatcherServlet.class

不过,需要注意的是,这个配置类默认是不会生效的,因为它所在的包是com.hmall.common.config,与各个微服务的扫描包不一致,无法被扫描到,因此无法生效。

测试:

购物车请求商品信息,使用了查询当前线程的用户,并打印

进行测试:

jack登陆时输出:

当前用户:1

rose登陆时输出:

当前用户:2

4. OpenFeign传递用户【重点】

但有些业务是比较复杂的,请求到达微服务后还需要调用其它多个微服务。

微服务之间调用是基于OpenFeign来实现的,并不是我们自己发送的请求。我们如何才能让每一个由OpenFeign发起的请求自动携带登录用户信息呢?比如下单业务,流程如下:

微服务之间调用是基于OpenFeign来实现的,并不是我们自己发送的请求。我们如何才能让每一个由OpenFeign发起的请求自动携带登录用户信息呢?

这里要借助Feign中提供的一个拦截器接口:feign.RequestInterceptor

public interface RequestInterceptor {void apply(RequestTemplate template);
}

我们只需要实现这个接口,然后实现apply方法,利用RequestTemplate类来添加请求头,将用户信息保存到请求头中。这样以来,每次OpenFeign发起请求的时候都会调用该方法,传递用户信息。由于FeignClient全部都是在hm-api模块,因此我们在hm-api模块的com.hmall.api.config.DefaultFeignConfig中编写这个拦截器:

代码如下:

public class DefaultFeignConifg {@Beanpublic RequestInterceptor userInfoRequestInterceptor() {return new RequestInterceptor() {//所有微服务使用OpenFeign进行传递时,都要经过这个拦截器处理请求@Overridepublic void apply(RequestTemplate template) {// 获取登录用户Long userId = UserContext.getUser();if (userId == null) {// 如果为空则直接跳过return;}// 如果不为空则放入请求头中,传递给下游微服务template.header("user-info", userId.toString());}};}
}

要想生效,必须加载到Feign启动类上:

测试:

支付成功后(调用了支付微服务),购物车也清空了(调用了购物车微服务), 至此基于OpenFeign在微服务之间传递用户id的信息成功实现。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 计算机视觉与视觉大模型对板书检测效果对比
  • 上线eleme项目
  • 怎么整合spring security和JWT
  • 【Unity3D小技巧】Unity3D中实现FPS数值显示功能实现
  • CSS 的了解text-rendering属性
  • 大模型学习笔记 - LLM 之 LLaMA系列(待更新)
  • 缺失ffmpeg.dll要用什么修复方法?快速恢复丢失的ffmpeg.dll文件
  • C++基础面试题 | C和C++的区别?
  • 【小趴菜前端学习日记3】
  • 【速览】计算机网络(更新中)
  • 使用VRoid Studio二次元建模,创建专属于自己的二次元卡通人物模型,创建完全免费开源且属于自己VRM模型
  • css 宫格样式内容上下结构
  • 井盖异动传感器:为城市安全加码
  • 电机启动对单片机重启的影响
  • 【Android】Android AOP 编程框架
  • (十五)java多线程之并发集合ArrayBlockingQueue
  • [case10]使用RSQL实现端到端的动态查询
  • [deviceone开发]-do_Webview的基本示例
  • java8 Stream Pipelines 浅析
  • mac修复ab及siege安装
  • Objective-C 中关联引用的概念
  • Vue 2.3、2.4 知识点小结
  • WePY 在小程序性能调优上做出的探究
  • 安装python包到指定虚拟环境
  • 初识 beanstalkd
  • 关于Android中设置闹钟的相对比较完善的解决方案
  • 猴子数据域名防封接口降低小说被封的风险
  • 极限编程 (Extreme Programming) - 发布计划 (Release Planning)
  • 入门到放弃node系列之Hello Word篇
  • 深入 Nginx 之配置篇
  • # Java NIO(一)FileChannel
  • #{} 和 ${}区别
  • #NOIP 2014#Day.2 T3 解方程
  • (21)起落架/可伸缩相机支架
  • (C语言)逆序输出字符串
  • (笔试题)合法字符串
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (附源码)ssm基于web技术的医务志愿者管理系统 毕业设计 100910
  • (牛客腾讯思维编程题)编码编码分组打印下标题目分析
  • (四)Controller接口控制器详解(三)
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转)http-server应用
  • (转)Oracle存储过程编写经验和优化措施
  • (转载)在C#用WM_COPYDATA消息来实现两个进程之间传递数据
  • .gitignore文件设置了忽略但不生效
  • .mp4格式的视频为何不能通过video标签在chrome浏览器中播放?
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .NET Core WebAPI中使用swagger版本控制,添加注释
  • .NET Core使用NPOI导出复杂,美观的Excel详解
  • .NET MVC第五章、模型绑定获取表单数据
  • .NET/C# 阻止屏幕关闭,阻止系统进入睡眠状态
  • /deep/和 >>>以及 ::v-deep 三者的区别
  • @Repository 注解
  • [ CTF ] WriteUp-2022年春秋杯网络安全联赛-冬季赛
  • [ solr入门 ] - 利用solrJ进行检索