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

2024-05-01 02:28

本文主要是介绍【自研网关系列】过滤器链 -- 鉴权过滤器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

🌈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.chinasem.cn/article/950503

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

flume系列之:查看flume系统日志、查看统计flume日志类型、查看flume日志

遍历指定目录下多个文件查找指定内容 服务器系统日志会记录flume相关日志 cat /var/log/messages |grep -i oom 查找系统日志中关于flume的指定日志 import osdef search_string_in_files(directory, search_string):count = 0

GPT系列之:GPT-1,GPT-2,GPT-3详细解读

一、GPT1 论文:Improving Language Understanding by Generative Pre-Training 链接:https://cdn.openai.com/research-covers/languageunsupervised/language_understanding_paper.pdf 启发点:生成loss和微调loss同时作用,让下游任务来适应预训

Redis中使用布隆过滤器解决缓存穿透问题

一、缓存穿透(失效)问题 缓存穿透是指查询一个一定不存在的数据,由于缓存中没有命中,会去数据库中查询,而数据库中也没有该数据,并且每次查询都不会命中缓存,从而每次请求都直接打到了数据库上,这会给数据库带来巨大压力。 二、布隆过滤器原理 布隆过滤器(Bloom Filter)是一种空间效率很高的随机数据结构,它利用多个不同的哈希函数将一个元素映射到一个位数组中的多个位置,并将这些位置的值置

Java基础回顾系列-第七天-高级编程之IO

Java基础回顾系列-第七天-高级编程之IO 文件操作字节流与字符流OutputStream字节输出流FileOutputStream InputStream字节输入流FileInputStream Writer字符输出流FileWriter Reader字符输入流字节流与字符流的区别转换流InputStreamReaderOutputStreamWriter 文件复制 字符编码内存操作流(

Java基础回顾系列-第五天-高级编程之API类库

Java基础回顾系列-第五天-高级编程之API类库 Java基础类库StringBufferStringBuilderStringCharSequence接口AutoCloseable接口RuntimeSystemCleaner对象克隆 数字操作类Math数学计算类Random随机数生成类BigInteger/BigDecimal大数字操作类 日期操作类DateSimpleDateForma

Java基础回顾系列-第三天-Lambda表达式

Java基础回顾系列-第三天-Lambda表达式 Lambda表达式方法引用引用静态方法引用实例化对象的方法引用特定类型的方法引用构造方法 内建函数式接口Function基础接口DoubleToIntFunction 类型转换接口Consumer消费型函数式接口Supplier供给型函数式接口Predicate断言型函数式接口 Stream API 该篇博文需重点了解:内建函数式

Java基础回顾系列-第二天-面向对象编程

面向对象编程 Java类核心开发结构面向对象封装继承多态 抽象类abstract接口interface抽象类与接口的区别深入分析类与对象内存分析 继承extends重写(Override)与重载(Overload)重写(Override)重载(Overload)重写与重载之间的区别总结 this关键字static关键字static变量static方法static代码块 代码块String类特