Gateway:网关路由与登录鉴权

在微服务架构中,用户登录和身份校验的处理方式确实与单体应用有所不同。在单体架构中,一旦用户通过身份验证,其会话信息可以在整个应用范围内共享,所有模块都能访问到用户信息。然而,在微服务架构下,每个服务独立部署且通常运行在不同的进程中,因此需要一种机制来确保用户的身份信息能够在各个微服务之间安全、高效地传递和验证。

目录

 

网关实现路由

网关登录鉴权

鉴权思路

登录校验流程图

网关过滤器详解

实现网关过滤器

拦截器流程图

服务信息鉴权


 

网关实现路由

问题:每个微服务都有不同的地址或端口,入口不同,请求不同数据时要访问不同的入口,需要维护多个入口地址,前端无法调用nacos,无法实时更新服务列表。

解决方案:采用微服务网关,数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发。

在微服务中新建一个网关模块,作为网关微服务

引入依赖(需要加入nacos依赖,让nacos管理)

 <dependencies><!--common--><dependency><groupId>com.heima</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>

application.yaml文件,配置路由

  gateway:routes:- id: item # 路由规则id,自定义,唯一uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务- Path=/items/**,/search/** # 这里是以请求路径作为判断规则

经测试成功

网关登录鉴权

问题:单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。但是,在微服务中,每个微服务都独立部署,一般只有用户微服务能校验登录信息,事实上,这是不安全的。

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

鉴权思路

登录是基于JWT来实现的,校验JWT的算法复杂,而且需要用到秘钥。我们不可能让个微服务都需要知道JWT的秘钥,不安全。也不可能每个微服务重复编写登录校验代码、权限校验代码,麻烦。

所以,我们在网关和用户服务保存秘钥,开发登录校验功能。

登录校验流程图

我们可以看到:前端——网关——后端服务

我们在网关层去实现过滤请求。

网关过滤器详解

Gateway内部工作的基本原理:

如图所示:

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

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

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

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

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

  6. 最终把响应结果返回

反正就是:我们需要在NettyRoutingFilter过滤器之前,在发起Request时,即pre时,定义一个过滤器,进行网关登录校验。

实现网关过滤器

我们采用全局过滤器,作用范围是所有路由,即GlobalFilter。

package com.hmall.gateway.filters;import com.hmall.common.exception.UnauthorizedException;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.utils.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
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;@RequiredArgsConstructor
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final AuthProperties authProperties;private final JwtTool jwtTool;private final AntPathMatcher antPathMatcher = new AntPathMatcher();/*** 过滤器* @param exchange* @param chain* @return*/@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取请求对象ServerHttpRequest 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 (headers != null && !headers.isEmpty()) {token = headers.get(0);}// 4.解析tokenLong userId = null;try {userId = jwtTool.parseToken(token);} catch (UnauthorizedException e) {// 如果无效,拦截ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();}// 5.获取用户信息String userInfo = userId.toString();ServerWebExchange swe = exchange.mutate().request(builder -> builder.header("user-info",userInfo )).build();// 6.放行return chain.filter(swe);}/*** 判断路径是否需要拦截* @param antPath* @return*/private boolean isExclude(String antPath) {for (String pathPattern : authProperties.getExcludePaths()) {if(antPathMatcher.match(pathPattern, antPath)){return true;}}return false;}// 优先级@Overridepublic int getOrder() {return 0;}
}

因为我采用了JWT令牌进行校验,所以引入了JWT工具类,进行令牌的获取与解析。

至于AuthProperties是配置了不需要鉴权就能访问的路径。

经测试,网关已经可以完成登录校验并获取登录用户身份信息。


问题:当网关将请求转发到微服务时,微服务如何获取用户身份,我们不可能每个微服务都写一个拦截器去得到用户身份信息。

解决方案:将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息(上述代码第五步已经完成了)。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal。

拦截器流程图

提供一个用于保存登录用户的ThreadLocal工具UserContext:

public class UserContext {private static final ThreadLocal<Long> tl = new ThreadLocal<>();/*** 保存当前登录用户信息到ThreadLocal* @param userId 用户id*/public static void setUser(Long userId) {tl.set(userId);}/*** 获取当前登录用户信息* @return 用户id*/public static Long getUser() {return tl.get();}/*** 移除当前登录用户信息*/public static void removeUser(){tl.remove();}
}

在公用模块下定义一个拦截器:

public class UserInfoInterceptor implements HandlerInterceptor {/*** 请求拦截器* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的 用户信息String userInfo = request.getHeader("user-info");// 2.判断是否为空if (StringUtils.isNotBlank(userInfo)) {UserContext.setUser(Long.valueOf(userInfo));}// 3.放行return true;}/*** 响应拦截器* @param request* @param response* @param handler* @param ex* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserContext.removeUser();}
}

编写SpringMVC的配置类,配置登录拦截器:

@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());}
}

注意:这个配置类默认是不会生效的,基于SpringBoot的自动装配原理,我们要将其添加到resources目录下的META-INF/spring.factories文件中:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.hmall.common.config.MyBatisConfig,\com.hmall.common.config.MvcConfig

经测试,服务得到网关传递的用户身份信息。

服务信息鉴权

问题:因为之前编写的过滤器和拦截器功能,微服务可以轻松获取登录用户信息。但是,有时候请求到达微服务后还需要调用其它多个微服务。我们没有实现服务之间的用户身份信息的传递。

解决方案:由于微服务获取用户信息是通过拦截器在请求头中读取,因此要想实现微服务之间的用户信息传递,就必须在微服务发起调用时把用户信息存入请求头

因为我们之前微服务之间调用是基于OpenFeign来实现的,并不是我们自己发送的请求。所以我们可以采用Feign中提供的一个拦截器接口:feign.RequestInterceptor。

在公用模块下定义一个userInfoRequestInterceptor

    /*** feign请求拦截器 微服务之间的远程调用时,将当前登录用户的userId传递给目标服务* @return*/@Beanpublic RequestInterceptor userInfoRequestInterceptor() {return new RequestInterceptor() {public void apply(RequestTemplate requestTemplate) {Long userId = UserContext.getUser();if (userId != null) {requestTemplate.header("user-info", userId.toString());}}};}

总结:

  • 为了实现网关处简便的登录校验,我们采用了GlobalFilter
  • 为了实现网关传递用户信息到多个微服务,我们采用了UserInfoInterceptor
  • 为了实现微服务之间用户身份信息传递,我们采用了userInfoRequestInterceptor。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/72100.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【结构光相机的精度极限】

1. 光源波长&#xff08;(\lambda)&#xff09; 光源波长是决定结构光相机精度极限的核心因素之一。根据光学衍射极限理论&#xff0c;光的波长越短&#xff0c;能够分辨的细节越小&#xff0c;精度越高。 理论依据&#xff1a; 根据瑞利判据&#xff08;Rayleigh Criterion&…

Vision Transformer (ViT):将Transformer带入计算机视觉的革命性尝试(代码实现)

Vision Transformer (ViT)&#xff1a;将Transformer带入计算机视觉的革命性尝试 作为一名深度学习研究者&#xff0c;如果你对自然语言处理&#xff08;NLP&#xff09;领域的Transformer架构了如指掌&#xff0c;那么你一定不会对它在序列建模中的强大能力感到陌生。然而&am…

【实战ES】实战 Elasticsearch:快速上手与深度实践-8.1.1基于ES的语义搜索(BERT嵌入向量)

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 基于Elasticsearch与BERT的语义搜索架构设计与实战1. 传统搜索的局限性与语义搜索的崛起1.1 关键词搜索 vs 语义搜索1.2 Elasticsearch向量检索演进历程关键版本特性对比 2.…

linux 学习笔记

# Linux学习笔记 ## 1 Linux入门 ### 1.1 概述 Linux内核最初只是芬兰人在赫尔辛基大学上学时处于个人爱好而编写的。 Linux是一套免费使用和自 由传播的类Unix操作系统&#xff0c;是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。Linux能运行主要的U…

Python个人学习笔记(14):函数(匿名函数、内置函数(下)、三元表达式)

九、匿名函数 lambda表达式 语法规则&#xff1a; 变量 lambda 参数1,参数2,…:返回值 例&#xff1a;用lambda简化下述操作 def func(a,b):return ab ret func(1, 2) print(ret)代码&#xff1a; fn lambda a,b:ab print(fn) print(fn(12,13))结果&#xff1a; <fun…

dns劫持是什么?常见的劫持类型有哪些?如何预防?

DNS劫持的定义 DNS劫持&#xff08;Domain Name System Hijacking&#xff09;是一种网络攻击手段&#xff0c;攻击者通过篡改域名解析的过程&#xff0c;将用户对某个域名的访问请求重定向到错误或恶意的IP地址。这种攻击可能导致用户访问到钓鱼网站、恶意广告页面&#xff0…

prompt大师高效提示词解析

Prompt大师李继刚高效提示词示例解析 一、「汉语新解」提示词 核心结构 采用Lisp语言框架嵌套中文语义&#xff0c;通过(defun 新汉语老师 ()...)定义角色风格&#xff08;融合奥斯卡王尔德、鲁迅的批判性语言&#xff09;&#xff0c;用(隐喻 (一针见血...))构建解释逻辑链。…

基于 Vue 的Deepseek流式加载对话Demo

目录 引言组件概述核心组件与功能实现1. 消息显示组件&#xff08;Message.vue&#xff09;2. 输入组件&#xff08;Input.vue&#xff09;3. 流式请求处理&#xff08;useDeepseek.ts&#xff09;4. 语音处理模块&#xff08;Voice.vue&#xff09; 总结Demo Github 地址 引言…

RK3588 编译 openssl

在编译 OpenSSL 时,你需要确保你的系统环境已经配置好了所有必要的依赖和编译工具。下面是一般步骤和一些常见问题的解决方案,特别是在使用 RK3588 这类的 ARM 处理器上。 1. 安装依赖 首先,你需要安装编译 OpenSSL 所需的依赖。这通常包括编译器(如 GCC)、make 工具、Per…

常见JVM命令

1. java -XX:PrintCommandLineFlags HelloGC 作用&#xff1a;打印 JVM 启动时的命令行参数&#xff0c;包括用户显式设置的参数和 JVM 自动默认设置的参数。用于确认 JVM 实际使用的配置。 2. java -Xmn10M -Xms40M -Xmx60M -XX:PrintCommandLineFlags -XX:PrintGC -XX:Prin…

easy-poi导出and导入一对多数据excel

easy-poi导出and导入一对多数据excel 一、导入jar包 <!-- easy-poi --><dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>4.4.0</version></dependency> 二…

c#如何直接获取json中的某个值

在 C# 中直接获取 JSON 中的某个值,通常可以通过以下方法实现(以 Newtonsoft.Json 和 .NET 内置的 System.Text.Json 为例): 方法 1:使用 System.Text.Json(.NET 内置库) using System.Text.Json;// 示例 JSON 字符串 string json = @"{""name"&qu…

WPS二次开发系列:Android 第三方应用如何获取WPS端内文档

1.需求场景 在项目开发中碰到这种情况&#xff0c;我们需要利用WPS的文档管理能力&#xff0c;比如需要调用WPS的文件选择器&#xff0c;来选择文档&#xff0c;同时需要得到WPS选择的文档结果返回给我们的应用。之前在网上找到了很久都没有找到WPS移动端有相关的API接口文档和…

Pytesseract识别图片

1. Pytesseract识别图片原理 1.1 Tesseract引擎工作原理 Tesseract OCR 引擎是一个功能强大的开源文字识别工具&#xff0c;其工作原理可以分为以下几个关键步骤&#xff1a; 图像预处理&#xff1a;Tesseract 首先对输入的图像进行预处理&#xff0c;包括灰度化、二值化、去…

Flutter 基础组件 Text 详解

目录 1. 引言 2. 基本使用 3. 自定义样式 4. 文本对齐与溢出控制 5. 外边距 5.1 使用 Container 包裹 5.2 使用 Padding 组件 5.3 在 Row/Column 中使用 5.4 动态边距调整 5.5 关键区别说明 5.6 设置 margin 无效 6. 结论 相关推荐 1. 引言 Text 组件是 Flutter 中…

Acknowledgment.nack方法重试消费kafka消息异常

文章目录 问题示例异常 原因nack方法Acknowledgment接口实现类&#xff1a;ConsumerAcknowledgment实现类&#xff1a;ConsumerBatchAcknowledgment 解决方案1 批量消费指定index示例 2 单条消费示例 问题 使用BatchAcknowledgingMessageListener 批量消费Kafka消息&#xff0…

Java 反序列化 - commons collection 之困(一)

#01多余的碎碎念 说到 java 反序列化&#xff0c;去搜索的话能看到网上有很多分析关于 commons collection 利用链的文章&#xff0c;emm 我一开始看不懂&#xff0c;看到很多代码的图头晕。 这篇文章的话其实是我跟着 p 神的文章一路走下来的&#xff0c;所以整个逻辑会按照…

python LLM工具包

阿里云镜像pypi http://mirrors.aliyun.com/pypi/simple/ modelscope魔塔 pip install modelscope https://modelscope.cn/docs/models/download Sentence-transformers pip install -U sentence-transformers pip3 install torch -i https://pypi.tuna.tsinghua.edu.cn/sim…

Linux账号和权限管理

用户账户管理 理论 /etc/passwd 该目录用于保存用户名&#xff0c;宿主目录&#xff0c;登录shel等基本信息 /etc/shadow 该目录用于保存 用户密码&#xff0c;账户有效期等信息 图上每一行中都有用“&#xff1a;”隔断的字段 字段含义&#xff1a; 第1字段:用户账号的名…