【自研网关系列】过滤器链 -- 鉴权过滤器

🌈Yu-Gateway:基于 Netty 构建的自研 API 网关,采用 Java 原生实现,整合 Nacos 作为注册配置中心。其设计目标是为微服务架构提供高性能、可扩展的统一入口和基础设施,承载请求路由、安全控制、流量治理等核心网关职能。

🌈项目代码地址:https://github.com/YYYUUU42/YuGateway-master

如果该项目对你有帮助,可以在 github 上点个 ⭐ 喔 🥰🥰

🌈自研网关系列:可以点开专栏,参看完整的文档

目录

什么是JWT

鉴权过滤器实现过程

配置文件修改

用户登录

登录具体实现

鉴权功能具体实现


1、什么是JWT

在自研网关这个项目中,主要是使用 Jwt 来实现简易的鉴权功能

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为 JSON 对象。它通常用于在不同系统之间进行身份验证和授权,以及在各种应用中传递声明性信息。

JWT 由三部分组成,它们通过点号(.)分隔:

  • Header(头部):包含了关于生成的 JWT 的元数据信息,例如算法和令牌类型。
  • Payload(负载):包含了实际的声明(claim)信息,这些声明是关于实体(通常是用户)和其他数据的信息。有三种类型的声明:注册声明、公共声明和私有声明。
  • Signature(签名):用于验证JWT的完整性,确保数据在传输过程中没有被篡改。签名是基于头部和负载,使用一个密钥(秘密或公开的)进行加密生成的。

JWT 的作用包括:

  • 身份验证:JWT可用于验证用户身份,确保请求来自经过身份验证的用户。用户登录后,可以生成JWT并将其存储在客户端,然后在后续请求中使用它来证明身份。
  • 授权:JWT可以包含用户的授权信息,以便在服务器端验证用户是否有权限执行某个操作或访问某个资源。
  • 信息交换:JWT可用于在不同系统之间安全地传递信息,例如在微服务架构中进行服务之间的通信。

流程:

  1. 客户端携带令牌访问资源服务获取资源。
  2. 资源服务远程请求认证服务校验令牌的合法性
  3. 如果令牌合法资源服务向客户端返回资源。

2、鉴权过滤器实现过程

2.1、配置文件修改

修改 nacos 上的配置文件,添加需要鉴权的路径

{"rules": [{"id": "user-private","name": "user-private","paths": ["/user/userInfo"],"prefix": "/user/private","protocol": "http","serviceId": "backend-user-server","filterConfigs": [{"config": {"load_balance": "Random"},"id": "load_balance_filter"},
{"id": "auth_filter","config": {"auth_path": ["/user/userInfo"]}}]},{"id": "user","name": "user","paths": ["/user/login"],"prefix": "/user","protocol": "http","serviceId": "backend-user-server","filterConfigs": [{"config": {"load_balance": "Random"},"id": "load_balance_filter"}]},{"id": "http-server","name": "http-server","paths": ["/http-server/ping"],"prefix": "/http-server","protocol": "http","retryConfig": {"times": 3},"serviceId": "backend-http-server","filterConfigs": [{"config": {"load_balance": "RoundRobin"},"id": "load_balance_filter"},{"id": "auth_filter"}]}]
}

2.2、用户登录

简单模拟登录接口,生成一个包含用户信息的JWT token,将这个token设置为Cookie并返回

@ApiInvoker(path = "/user/login")
@GetMapping("/user/login")
public String login(@RequestParam("phoneNumber") String phoneNumber,@RequestParam("code") String code,HttpServletResponse response) {Map<String, Object> params = new HashMap<>();params.put(FilterConst.TOKEN_USERID_KEY, String.valueOf(phoneNumber + code));String token = JWTUtil.generateToken(params, FilterConst.TOKEN_SECRET);log.info("token:{}", token);response.addCookie(new Cookie(FilterConst.COOKIE_KEY, token));return token;
}

2.3、登录具体实现

首先会根据请求头得到注册到 nacos 的实例信息,和请求路径配对,得到 Rules 配置文件中的过滤器链配置

这里会和 spi 文件存在的过滤器链和 nacos 上的 Rules 过滤器配置进行判断,得到最终的过滤器链

这是nacos 上的 Rules 过滤器配置

这是 spi 的过滤器信息

由于是用户登录,所以是没有鉴权过滤器的

最终过滤器,一般来讲,在大多数网关项目中,负载均衡和路由转发通常是必要的过滤器

  • 负载均衡:当有多个实例提供相同的服务时,负载均衡器可以将请求分发到这些实例中的一个,以确保所有实例的负载均匀,提高系统的可用性和伸缩性。
  • 路由转发:路由转发是将客户端的请求转发到正确的服务实例的过程。在微服务架构中,由于服务实例可能分布在不同的服务器或容器中,因此需要一个路由机制来确定将请求转发到哪个服务实例。

全部执行完后,会将 token 存储在 Cookie 中,鉴权部分会用到

2.4、鉴权功能具体实现

请求结果

简单模拟请求接口

@ApiInvoker(path = "/user/userInfo")
@GetMapping("/user/userInfo")
public UserInfo getUserInfo(@RequestHeader("userId") String userId) {log.info("userId :{}", userId);return UserInfo.builder().id(Integer.parseInt(userId)).name("yu").phoneNumber("1234").build();
}

实现流程和登录差不多,就是多了鉴权过滤器,具体代码

/*** @author yu* @description 鉴权过滤器*/
@Slf4j
@FilterAspect(id= FilterConst.AUTH_FILTER_ID, name = FilterConst.AUTH_FILTER_NAME, order = FilterConst.AUTH_FILTER_ORDER)
public class AuthFilter implements Filter {private final Logger logger = LoggerFactory.getLogger(AuthFilter.class);@Overridepublic void doFilter(GatewayContext ctx) throws Exception {// 遍历所有的过滤器配置for (Rule.FilterConfig config : ctx.getRules().getFilterConfigs()) {// 如果当前的过滤器ID不是我们需要的过滤器ID,那么就跳过这个过滤器配置if (!config.getId().equals(FilterConst.AUTH_FILTER_ID)) {continue;}// 解析过滤器配置,获取到我们需要的认证路径List<String> authPaths = new ArrayList<>();Map<String, List<String>> configMap = new ConcurrentHashMap<>();if (config.getConfig() != null) {configMap = JSON.parseObject(config.getConfig(), Map.class);authPaths = configMap.getOrDefault(FilterConst.AUTH_FILTER_KEY, new ArrayList<>());}// 获取当前请求的路径String curRequestKey = ctx.getRequest().getPath();// 如果当前请求的路径不是我们需要的认证路径,那么就返回,不进行后续的处理if (!authPaths.contains(curRequestKey)) {return;}// 从请求中获取token,如果token不存在,那么就抛出一个未授权的异常String token = Optional.ofNullable(ctx.getRequest().getCookie(FilterConst.COOKIE_KEY)).map(Cookie::value).orElseThrow(() -> new ResponseException(ResponseCode.UNAUTHORIZED));// 对获取到的token进行验证authenticateToken(ctx, token);}}/*** 验证token*/private void authenticateToken(GatewayContext ctx, String token) {try {long tokenUserId = parseUserIdFromToken(token);String headerUserId = ctx.getRequest().getHeaders().get("userId");String pathUserId = ctx.getRequest().getQueryParametersMultiple("userId").get(0);String actualUserId = headerUserId != null ? headerUserId : pathUserId;if (actualUserId == null || Long.parseLong(actualUserId) != tokenUserId) {throw new ResponseException(ResponseCode.USERID_MISMATCH);}ctx.getRequest().setUserId(tokenUserId);log.info("AuthFilter 解析 token 成功, userId {}", tokenUserId);} catch (Exception e) {log.info("AuthFilter 解析 token 失败, 请求路径 {}", ctx.getRequest().getPath());throw new ResponseException(ResponseCode.UNAUTHORIZED);}}/*** 解析token中的载荷——用户ID*/private long parseUserIdFromToken(String token) {// 使用Optional来处理可能为null的值Optional.ofNullable(token).filter(t -> !t.isEmpty()).orElseThrow(() -> new IllegalArgumentException("Token cannot be null or empty."));Jwt jwt = null;try {// 使用静态解析器实例,提高性能jwt = Jwts.parser().setSigningKey(FilterConst.TOKEN_SECRET).parse(token);} catch (SignatureException | ExpiredJwtException e) {throw new RuntimeException("Token 验证错误: ", e);}try {// 验证字符串是否可以转换为long类型,并检查范围DefaultClaims claims = (DefaultClaims) jwt.getBody();String jwtUserId = claims.get("userId", String.class);long userId = Long.parseLong(jwtUserId);if (userId == Long.MIN_VALUE || userId == Long.MAX_VALUE) {throw new IllegalArgumentException("UseId 超出范围.");}return userId;} catch (NumberFormatException e) {logger.error("UserId 解析错误: ", e);throw new IllegalArgumentException("无效 userId 格式");}}
}

匹配鉴权过滤器配置

判断当前请求路径是否需要鉴权的

// 解析过滤器配置,获取到我们需要的认证路径
List<String> authPaths = new ArrayList<>();
Map<String, List<String>> configMap = new ConcurrentHashMap<>();
if (config.getConfig() != null) {configMap = JSON.parseObject(config.getConfig(), Map.class);authPaths = configMap.getOrDefault(FilterConst.AUTH_FILTER_KEY, new ArrayList<>());
}// 获取当前请求的路径
String curRequestKey = ctx.getRequest().getPath();// 如果当前请求的路径不是我们需要的认证路径,那么就返回,不进行后续的处理
if (!authPaths.contains(curRequestKey)) {return;
}

首先对 token 解析,jwt = Jwts.parser().setSigningKey(FilterConst.TOKEN_SECRET).parse(token);

对请求 id,判断请求 id 是否和 token 解析的 id 相同,

long tokenUserId = parseUserIdFromToken(token);String headerUserId = ctx.getRequest().getHeaders().get("userId");
String pathUserId = ctx.getRequest().getQueryParametersMultiple("userId").get(0);
String actualUserId = headerUserId != null ? headerUserId : pathUserId;if (actualUserId == null || Long.parseLong(actualUserId) != tokenUserId) {throw new ResponseException(ResponseCode.USERID_MISMATCH);
}

用户 id 和 token 中的不同或 Cookie 修改会报错

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

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

相关文章

智能化未来:Agent AI智能体的崛起与全球挑战

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

Redis源码学习记录:列表 (ziplist)

ziplist redis 源码版本&#xff1a;6.0.9。ziplist 的代码均在 ziplist.c / ziplist.h 文件中。 定义 ziplist总体布局如下&#xff1a; <zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend> zlbytes&#xff1a;uin…

高德优评项目,一单29.9,拷贝+评价,日入500

项目概述&#xff1a; 下载 地 址 &#xff1a; laoa1.cn/1836.html 本项目的核心非常简单&#xff0c;即在高德地图上撰写评论。每条评论完成后&#xff0c;参与者将获得8元的奖励。 此外&#xff0c;高德地图还会提供视频会员作为奖励。我们可以将这些视频会员进行变现…

LLM 构建Data Multi-Agents 赋能数据分析平台的实践之③:数据分析之二(大小模型协同)

一、概述 随着新一代信息技术在产业数字化中的应用&#xff0c;产生了大量多源多模态信息以及响应的信息处理模式&#xff0c;数据孤岛、模型林立的问题也随之产生&#xff0c;使得业务系统臃肿、信息处理和决策效率低下&#xff0c;面对复杂任务及应用场景问题求解效率低。针…

做外贸如何主动开发外贸客户

在外贸业务中&#xff0c;主动开发客户是至关重要的一步&#xff0c;它能够帮助你扩大市场覆盖范围&#xff0c;建立稳定的客户基础。以下是一些有效的策略和方法&#xff0c;可以帮助你更有效地主动开发外贸客户&#xff1a; 明确目标市场&#xff1a;首先&#xff0c;你需要确…

【快速入门】数据库的增删改查与结构讲解

文章的操作都是基于小皮php study的MySQL5.7.26进行演示 what 数据库是能长期存储在计算机内&#xff0c;有组织的&#xff0c;可共享的大量数据的集合。数据库中的数据按照一定的数据模型存储&#xff0c;具有较小的冗余性&#xff0c;较高的独立性和易扩展性&#xff0c;并为…

【docker】Docker开启远程访问

将构建的镜像自动上传到服务器。 需要开放 Docker 的端口&#xff0c;让我们在本地能连接上服务器的 Docker&#xff0c;这样&#xff0c;才能上传构建的镜像给 Docker。 开启远程访问 首先在服务器打开 Docker 的服务文件 vim /usr/lib/systemd/system/docker.service修改…

LiveGBS user/save 逻辑缺陷漏洞复现(CNVD-2023-72138)

0x01 产品简介 LiveGBS是安徽青柿信息科技有限公司研发的一款国标(GB28181)流媒体服务软件,可提供提供用户管理及Web可视化页面管理,开源的前端页面源码;提供设备状态管理,可实时查看设备是否掉线等信息等。 0x02 漏洞概述 LiveGBS user/save 接口处存在逻辑缺陷漏洞,未…

浅论汽车研发项目数字化管理之道

随着汽车行业竞争不断加剧&#xff0c;汽车厂商能否快速、高质地推出贴合市场需求的新车型已经成为车企竞争的重要手段&#xff0c;而汽车研发具备流程复杂、专业领域多、协作难度大、质量要求高等特点&#xff0c;企业如果缺少科学健全的项目管理体系&#xff0c;将会在汽车研…

java-springmvc 01 补充 javaweb 三大组件Servlet,Filter、Listener(源码都是tomcat8.5项目中的)

01.JavaWeb三大组件指的是&#xff1a;Servlet、Filter、Listener,三者提供不同的功能 这三个在springmvc 运用很多 Servlet 01.Servlet接口&#xff1a; public interface Servlet {/*** 初始化方法* 实例化servlet之后&#xff0c;该方法仅调用一次 * init方法必须执行完…

区块链 | 由外部实体导致的 NFT 安全问题

&#x1f98a;原文&#xff1a; Understanding Security Issues in the NFT Ecosystem &#x1f98a;警告&#xff1a; 本文只记录了原文的第 6 节。 1 问题描述 NFT 所指向的数字资产&#xff08;图片、视频等&#xff09;必须是可以访问的&#xff0c;这样 NFT 才具有意义…

flake8,一个超强的 Python 库!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个超强的 Python 库 - flake8。 Github地址&#xff1a;https://github.com/PyCQA/flake8 Flake8是一个流行的Python库&#xff0c;用于检查代码质量和风格一致性&#xff0c;它集成了PyFlakes、…

powershell 注册全局热键——提升效率小工具

powershell 注册全局热键 01 前言 在处理一些重复工作问题的时候&#xff0c;想搞一个小工具&#xff0c;配合全局快捷键来提高效率。因为是Windows系统&#xff0c;想到C#&#xff0c;但是又不想用VS开发&#xff0c;因为那样不够灵活&#xff0c;没办法随时修改随时用&…

Windows系统下安装Mosquitto的步骤(2)

接前一篇文章&#xff1a;Windows系统下安装Mosquitto的步骤&#xff08;1&#xff09; 本文内容参考&#xff1a; Windows10上安装Mosquitto的步骤(win10、win11 安装mqtt) - IPS99技术分享 MQTT&#xff1a;windows环境下配置MQTT服务器&#xff08;mosquitto&#xff09;_…

2024.阳光能源追光计划暨大陆考察团交流分享会

近日大陆考察团抵达香港&#xff0c;受到了本司热情接待和安排。公司于4月27日下午举办了阳光能源追光计划主题交流会。 会上公司营销部总监张超&#xff0c;分享了阳光能源近几年的能源发展之路及公司新推出的追光计划&#xff0c;得到了大陆考察交流团团长杨国均先生的高度赞…

Vue.js课后练习(登录注册和大小比较)

第一题 请编写登录页面和注册页面&#xff0c;通过动态组件实现动态切换页面中显示的组件&#xff0c;效果如图1和图2所示。 图1 登录页面 图2 注册页面 代码&#xff1a; my.vue代码: <template>登录 </template><script setup> </script><st…

【Go 语言入门专栏】Go 语言的起源与发展

前言 Go 语言是当下最为流行的编程语言之一&#xff0c;大约在 2020、2021 年左右开始于国内盛行&#xff0c;许多大厂很早就将部分 Java 项目迁移到了 Go&#xff0c;足可看出其在性能方面的优越性。 相信各位都知道&#xff0c;在爬虫业务中&#xff0c;并发是一个关键的需…

Notes for the missing semester. Useful and basic knowledge about Linux.

The Shell Contents The first course is to introduce some simple commands. I’ll list some commands that I’m not familiar with: # --silent means dont give log info, # --head means we only want the http head. curl --head --silent bing.com.cn# cut --deli…

【再探】设计模式—抽象工厂及建造者模式

抽象工厂模式和建造者模式都属于创建型模式。两者都能创建对应的对象&#xff0c;而创建者模式更侧重于创建复杂对象&#xff0c;将对象的创建过程封装起来&#xff0c;让客户端不需要知道对象的内部细节。 1 抽象工厂模式 需求&#xff1a; 在使用工厂方法模式时&#xff0…

Java File类

1. File类概述 1.1 什么是File类 File是java.io包下作为文件和目录的类。File类定义了一些与平台无关的方法来操作文件&#xff0c;通过调用File类中的方法可以得到文件和目录的描述信息&#xff0c;包括名称、所在路径、读写性和长度等&#xff0c;还可以对文件和目录进行新建…