网关鉴权模块-鉴权+登录拦截+jwt

1. 鉴权流程

浏览器发送请求时。请求头会携带键值对"authorization":jwt
网关先解析jwt令牌,做第一次鉴权,鉴权完成后将解析的user对象的id添加到请求头中:user-info = 用户id;
微服务的拦截器会获取请求头中的user-info,然后存入到UserContext(底层基于ThreadLocal),这样后续的业务处理时就能直接从UserContext中获取用户了。

网关鉴权后,微服务为什么还要做鉴权?防止请求越过网关直接发给微服务

在这里插入图片描述

2. 网关鉴权过滤器

在这里插入图片描述

2.1 filter方法

    @Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取请求request信息ServerHttpRequest request = exchange.getRequest();String method = request.getMethodValue();String path = request.getPath().toString();String antPath = method + ":" + path;// 2.判断是否是无需登录的路径if(isExcludePath(antPath)){// 直接放行return chain.filter(exchange);}// 3.尝试获取用户信息 AUTHORIZATION_HEADER --- "authorization"List<String> authHeaders = exchange.getRequest().getHeaders().get(AUTHORIZATION_HEADER);String token = authHeaders == null ? "" : authHeaders.get(0);R<LoginUserDTO> r = authUtil.parseToken(token);// 4.如果用户是登录状态即jwt校验成功,尝试更新请求头,传递用户idif(r.success()){exchange.mutate()// USER_HEADER --- "user-info".request(builder -> builder.header(USER_HEADER, r.getData().getUserId().toString())).build();}// 5.校验权限authUtil.checkAuth(antPath, r);// 6.放行return chain.filter(exchange);}private boolean isExcludePath(String antPath) {for (String pathPattern : authProperties.getExcludePath()) {if(antPathMatcher.match(pathPattern, antPath)){return true;}}return false;}@Overridepublic int getOrder() {// 越大优先级越低return 1000;}

2.2 jwt解析工具方法

    public R<LoginUserDTO> parseToken(String token) {// 1.校验token是否为空if(StringUtils.isBlank(token)){return R.error(INVALID_TOKEN_CODE, INVALID_TOKEN);}JWT jwt = null;try {// cn.hutool.jwt.JWTjwt = JWT.of(token).setSigner(jwtSignerHolder.getJwtSigner());} catch (Exception e) {return R.error(INVALID_TOKEN_CODE, INVALID_TOKEN);}// 2.校验jwt是否有效 cn.hutool.jwt.JWTif (!jwt.verify()) {// 验证失败,返回空return R.error(INVALID_TOKEN_CODE, INVALID_TOKEN);}// 3.校验是否过期 cn.hutool.jwt.JWTValidatortry {JWTValidator.of(jwt).validateDate();} catch (ValidateException e) {return R.error(EXPIRED_TOKEN_CODE, EXPIRED_TOKEN);}// 4.数据格式校验 cn.hutool.jwt.JWT   PAYLOAD_USER_KEY --- "user"Object userPayload = jwt.getPayload(PAYLOAD_USER_KEY);if (userPayload == null) {// 数据为空return R.error(INVALID_TOKEN_CODE, INVALID_TOKEN_PAYLOAD);}// 5.数据解析LoginUserDTO userDTO;try {// cn.hutool.json.JSONuserDTO = ((JSONObject)userPayload).toBean(LoginUserDTO.class);} catch (RuntimeException e) {// token格式有误return R.error(INVALID_TOKEN_CODE, INVALID_TOKEN_PAYLOAD);}// 6.返回return R.ok(userDTO);}public void checkAuth(String antPath, R<LoginUserDTO> r){// 1.判断是否是需要权限的路径String matchPath = findMatchPath(antPath);if(matchPath == null){// 没有权限限制,直接放行return;}// 2.判断是否登录成功if(!r.success()){// 未登录,直接报错throw new UnauthorizedException(r.getCode(), r.getMsg());}// 3.获取当前路径所需权限PrivilegeRoleDTO pathPrivilege = findPathPrivilege(matchPath);// 4.权限判断Set<Long> requiredRoles = pathPrivilege.getRoles();if (!CollectionUtil.contains(requiredRoles, r.getData().getRoleId())) {// 没有访问权限throw new ForbiddenException(FORBIDDEN);}}private String findMatchPath(String antPath){String matchPath = null;for (String pathPattern : paths) {// org.springframework.util.AntPathMatcherif(antPathMatcher.match(pathPattern, antPath)){matchPath = pathPattern;break;}}return matchPath;}private PrivilegeRoleDTO findPathPrivilege(String path){return privileges.get(path);}

3. 微服务拦截器

每个微服务对鉴权都有需求,所以抽取出来放到common中,每个微服务在pom文件中引入该模块。

拦截器包括用户拦截(即鉴权)和登录拦截。
spring会根据当前微服务的bootstrap.yml,决定是否配置登录拦截器,并且配置需要登录的路径和不需要登录的路径。
当UserInfoInterceptor从请求头中取出user-info时,会存入ThreadLocal,再放行。
当网关中判断是无需登录的路径做出放行(鉴权通过)时,UserInfoInterceptor也会放行;然后根据各个微服务的配置判断当前路径是否需要登录。

WebMvcConfigurer,注册拦截器

@Configuration
@EnableConfigurationProperties(ResourceAuthProperties.class)
public class ResourceInterceptorConfiguration implements WebMvcConfigurer {private final ResourceAuthProperties authProperties;@Autowiredpublic ResourceInterceptorConfiguration(ResourceAuthProperties resourceAuthProperties) {this.authProperties = resourceAuthProperties;}@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 1.添加用户信息拦截器registry.addInterceptor(new UserInfoInterceptor()).order(0);// 2.是否需要做登录拦截if(!authProperties.getEnable()){// 无需登录拦截return;}// 2.添加登录拦截器InterceptorRegistration registration = registry.addInterceptor(new LoginAuthInterceptor()).order(1);// 2.1.添加拦截器路径if(CollUtil.isNotEmpty(authProperties.getIncludeLoginPaths())){registration.addPathPatterns(authProperties.getIncludeLoginPaths());}// 2.2.添加排除路径if(CollUtil.isNotEmpty(authProperties.getExcludeLoginPaths())){registration.excludePathPatterns(authProperties.getExcludeLoginPaths());}// 2.3.排除swagger路径registration.excludePathPatterns("/v2/**","/v3/**","/swagger-resources/**","/webjars/**","/doc.html");}
}

各个微服务中,对登录拦截器的需求不同

在这里插入图片描述
用户拦截,实现鉴权

@Slf4j
public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.尝试获取头信息中的用户信息String authorization = request.getHeader(JwtConstants.USER_HEADER);// 2.判断是否为空if (authorization == null) {return true;}// 3.转为用户id并保存try {Long userId = Long.valueOf(authorization);UserContext.setUser(userId);return true;} catch (NumberFormatException e) {log.error("用户身份信息格式不正确,{}, 原因:{}", authorization, e.getMessage());return true;}}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 清理用户信息UserContext.removeUser();}
}

登录拦截

@Slf4j
public class LoginAuthInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.尝试获取用户信息Long userId = UserContext.getUser();// 2.判断是否登录if (userId == null) {response.setStatus(401);response.sendError(401, "未登录用户无法访问!");// 2.3.未登录,直接拦截return false;}// 3.登录则放行return true;}
}

UserContext,底层使用ThreadLocal

public class UserContext {private static final ThreadLocal<Long> TL = new 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();}
}

4. 登录相关接口,jwt生成工具类

    @Overridepublic String login(LoginFormDTO loginDTO, boolean isStaff) {// 1.查询并校验用户信息LoginUserDTO detail = userClient.queryUserDetail(loginDTO, isStaff);if (detail == null) {throw new BadRequestException("登录信息有误");}// 2.基于JWT生成登录token// 2.1.设置记住我标记detail.setRememberMe(loginDTO.getRememberMe());// 2.2.生成tokenString token = generateToken(detail);// 3.计入登录信息表loginRecordService.loginSuccess(loginDTO.getCellPhone(), detail.getUserId());// 4.返回结果return token;}private String generateToken(LoginUserDTO detail) {// 2.2.生成access-tokenString token = jwtTool.createToken(detail);// 2.3.生成refresh-token,将refresh-token的JTI 保存到RedisString refreshToken = jwtTool.createRefreshToken(detail);// 2.4.将refresh-token写入用户cookie,并设置HttpOnly为trueint maxAge = BooleanUtils.isTrue(detail.getRememberMe()) ?(int) JwtConstants.JWT_REMEMBER_ME_TTL.toSeconds() : -1;WebUtils.cookieBuilder().name(detail.getRoleId() == 2 ? JwtConstants.REFRESH_HEADER : JwtConstants.ADMIN_REFRESH_HEADER).value(refreshToken).maxAge(maxAge).httpOnly(true).build();return token;}@Overridepublic void logout() {// 删除jtijwtTool.cleanJtiCache();// 删除cookieWebUtils.cookieBuilder().name(JwtConstants.REFRESH_HEADER).value("").maxAge(0).httpOnly(true).build();}

JwtTool

// public static final Duration JWT_REFRESH_TTL = Duration.ofMinutes(30);
import static com.tianji.auth.common.constants.JwtConstants.JWT_REFRESH_TTL;// public static final Duration JWT_TOKEN_TTL = Duration.ofMinutes(5);
import static com.tianji.auth.common.constants.JwtConstants.JWT_TOKEN_TTL;@Component
public class JwtTool {private final StringRedisTemplate stringRedisTemplate;private final JWTSigner jwtSigner;public JwtTool(StringRedisTemplate stringRedisTemplate, KeyPair keyPair) {this.stringRedisTemplate = stringRedisTemplate;this.jwtSigner = JWTSignerUtil.createSigner("rs256", keyPair);}/*** 创建 access-token** @param userDTO 用户信息* @return access-token*/public String createToken(LoginUserDTO userDTO) {// 1.生成jwsreturn JWT.create().setPayload(JwtConstants.PAYLOAD_USER_KEY, userDTO).setExpiresAt(new Date(System.currentTimeMillis() + JWT_TOKEN_TTL.toMillis())).setSigner(jwtSigner).sign();}/*** 创建刷新token,并将token的JTI记录到Redis中** @param userDetail 用户信息* @return 刷新token*/public String createRefreshToken(LoginUserDTO userDetail) {// 1.生成 JTIString jti = UUID.randomUUID().toString(true);// 2.生成jwt// 2.1.如果是记住我,则有效期7天,否则30分钟Duration ttl = BooleanUtils.isTrue(userDetail.getRememberMe()) ?JwtConstants.JWT_REMEMBER_ME_TTL : JWT_REFRESH_TTL;// 2.2.生成tokenString token = JWT.create().setJWTId(jti).setPayload(JwtConstants.PAYLOAD_USER_KEY, userDetail).setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis())).setSigner(jwtSigner).sign();// 3.缓存jti,有效期与token一致,过期或删除JTI后,对应的refresh-token失效stringRedisTemplate.opsForValue().set(JwtConstants.JWT_REDIS_KEY_PREFIX + userDetail.getUserId(), jti, ttl);return token;}/*** 解析刷新token** @param refreshToken 刷新token* @return 解析刷新token得到的用户信息*/public LoginUserDTO parseRefreshToken(String refreshToken) {// 1.校验token是否为空AssertUtils.isNotNull(refreshToken, AuthErrorInfo.Msg.INVALID_TOKEN);// 2.校验并解析jwtJWT jwt;try {jwt = JWT.of(refreshToken).setSigner(jwtSigner);} catch (Exception e) {throw new BadRequestException(400, AuthErrorInfo.Msg.INVALID_TOKEN, e);}// 2.校验jwt是否有效if (!jwt.verify()) {// 验证失败throw new BadRequestException(400, AuthErrorInfo.Msg.INVALID_TOKEN);}// 3.校验是否过期try {JWTValidator.of(jwt).validateDate();} catch (ValidateException e) {throw new BadRequestException(400, AuthErrorInfo.Msg.EXPIRED_TOKEN);}// 4.数据格式校验Object userPayload = jwt.getPayload(JwtConstants.PAYLOAD_USER_KEY);Object jtiPayload = jwt.getPayload(JwtConstants.PAYLOAD_JTI_KEY);if (jtiPayload == null || userPayload == null) {// 数据为空throw new BadRequestException(400, AuthErrorInfo.Msg.INVALID_TOKEN);}// 5.数据解析LoginUserDTO userDTO;try {userDTO = ((JSONObject) userPayload).toBean(LoginUserDTO.class);} catch (RuntimeException e) {// 数据格式有误throw new BadRequestException(400, AuthErrorInfo.Msg.INVALID_TOKEN);}// 6.JTI校验String jti = stringRedisTemplate.opsForValue().get(JwtConstants.JWT_REDIS_KEY_PREFIX + userDTO.getUserId());if (!StringUtils.equals(jti, jtiPayload.toString())) {// jti不一致throw new BadRequestException(400, AuthErrorInfo.Msg.INVALID_TOKEN);}return userDTO;}/*** 清理刷新refresh-token的jti,本质是refresh-token作废*/public void cleanJtiCache() {stringRedisTemplate.delete(JwtConstants.JWT_REDIS_KEY_PREFIX + UserContext.getUser());}
}

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

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

相关文章

spring boot2.7.x遇到问题

validation报错 高版本已移除了validation以来&#xff0c;需手动添加 <dependency><groupId>jakarta.validation</groupId><artifactId>jakarta.validation-api</artifactId> </dependency>mybatis报错 升级版本 <dependency>&…

基础篇01——SQL的基本语法和分类

MySQL数据库安装与基本使用 安装教程参见&#xff1a;通过zip安装MySQL 通过命令行启动和停止MySQL服务命令 前提&#xff1a;安装MySQL成功之后 启动服务&#xff1a;net start mysql 停止服务&#xff1a;net stop mysql 通过命令行连接mysql 可以通过mysql的客户端命令行…

记录某书请求返回406及响应{“code“:-1,“success“:false}

今天测试某个平台的爬虫时使用requests post请求正常写了个测试脚本把各种参数带上出来以后出现了406情况&#xff0c;和网站数据是完全一样的 以为是 X-S、X-T参接不对&#xff0c;但在postman里测试又是可以的成功&#xff0c;以为是检验了参数顺序&#xff0c;测试发现也没…

SQLAlchemy 模型中数据的错误表示

1. 问题背景 在使用 SQLAlchemy 0.6.0 版本&#xff08;也曾尝试使用 0.6.4 版本&#xff09;的 Pylons 应用程序中遇到了一个 SQLAlchemy ORM 问题。该问题出现在使用 psycopg2 作为数据库驱动程序、连接至 Postgresql 8.2 数据库的环境中。定义了一个 User 模型对象&#xf…

封装了一个仿照抖音评论轮播效果的iOS轮播视图

效果图 原理 就是我们在一个视图里面有两个子视图&#xff0c;一个是currentView, 一个是willShowView,在一次动画过程中&#xff0c;我们改变current View的frame&#xff0c;同时改变willShowView的frame&#xff0c;同时&#xff0c;需要改变currentVIew 的transform.y不然…

Linux操作系统:Redis在虚拟环境下的安装与部署

Redis下载方法 最近部署项目的时候用到了Redis&#xff0c;自己在安装的时候也碰到了一些列问题最终安装成功&#xff0c;记录一下自己的安装历程。前期准备&#xff1a; 服务器Linux版本&#xff1a;Centos8.4 64位&#xff08;http://isoredirect.centos.org/centos/8/isos/…

快速了解JVM机制

1.JVM 简介 JVM 是 Java Virtual Machine 的简称&#xff0c;意为 Java虚拟机。 虚拟机是指通过软件模拟的具有完整硬件功能的、运⾏在⼀个完全隔离的环境中的完整计算机系统。 常⻅的虚拟机&#xff1a;JVM、VMwave、Virtual Box。 JVM 和其他两个虚拟机的区别&#xff1a; V…

Ubuntu有线连接消失,无法联网怎么办!

今天重启 Ubuntu 虚拟机&#xff0c;突然之间发现没有网络&#xff0c;打开设置中的网络看&#xff0c;有线网络竟然消失了 经过一番查阅资料&#xff0c;发现解决问题很简单&#xff1a; 先看原因&#xff0c;输入命令 sudo lshw -c Network 检查所有的网络情况&#xff0c…

每天坚持写java锻炼能力---第一天(6.4)

今天的目标是菜单&#xff1a; B站/马士兵的项目菜单 package java1;import java.util.Scanner;public class Test {public static void main(String[] args) {while(true){ //3.加入死循环&#xff0c;让输入一直有System.out.println();System.out.println("--->项…

Python logging 模块详解

Python 的 logging 模块提供了一个强大而灵活的日志系统。它是 Python 标准库的一部分&#xff0c;因此可以在任何 Python 程序中使用。logging 模块提供了许多有用的功能&#xff0c;包括日志消息的级别设置、日志消息的格式设置、将日志消息输出到不同的目标&#xff0c;以及…

Java1.8 vue版家政服务系统成品源码 家政管家系统源码 家政月嫂系统源码 家政保洁系统源码 在线派单,师傅入驻全套商业源码

Java1.8 vue版家政服务系统成品源码 家政管家系统源码 家政月嫂系统源码 家政保洁系统源码 在线派单&#xff0c;师傅入驻全套商业源码 一、系统定义 家政上门服务系统是一种利用互联网技术&#xff0c;将家政服务需求与专业的家政服务人员进行高效匹配的平台。它允许用户通过…

信息系统项目管理师0146:输入(9项目范围管理—9.3规划范围管理—9.3.1输入)

点击查看专栏目录 文章目录 9.3 规划范围管理9.3.1 输入9.3 规划范围管理 规划范围管理是为了记录如何定义、确认和控制项目范围及产品范围,而创建范围管理计划的过程。本过程的主要作用是在整个项目期间对如何管理范围提供指南和方向。本过程仅开展一次或仅在项目的预定义点开…

20240606在Toybrick的TB-RK3588开发板的Android12下确认HDMI的驱动

20240606在Toybrick的TB-RK3588开发板的Android12下确认HDMI的驱动 2024/6/6 9:48 【原文是在RK3328的Android7.1下写的。我将它升级成为RK3588的Android12了】 RK平台主要采用 FB 和 DRM 两种显示框架。与此相对应&#xff0c; HDMI 也有两套驱动。 FB&#xff1a; LINUX 3.10…

ctfshow-web入门-命令执行(web29)五种解法绕过文件名检测

命令执行&#xff0c;需要严格的过滤 进入 php 代码审计了&#xff1a; 第一题代码很简单&#xff0c;就是对 preg_match 绕过&#xff0c;只要提交的参数值不出现 flag 就行 先看一下当前目录下的文件&#xff0c;构造 payload&#xff1a; ?csystem(ls); 可以看到 flag 就…

《魔法与科技的融合:SpringBoot运维的现代传说》

揭开了SpringBoot应用部署的神秘面纱。从云平台的选型到Docker的容器化魔法&#xff0c;再到Kubernetes的集群力量&#xff0c;每一步都充满了奇幻色彩。文章以轻松幽默的笔触&#xff0c;带领读者穿梭于现代应用部署的各个角落&#xff0c;探索自动化部署的奥秘&#xff0c;学…

Spring-Cloud-Gateway--源码分析及应用

文章目录 一、简介1.1 术语 1.3 特性1.4 Spring Cloud Gateway与Spring Cloud ZuulSpring Cloud ZuulWebflux模型 1.5 如何集成Gateway 二、工作原理2.2 Gateway类图 三、配置路由谓词工厂和网关过滤工厂3.1 两种不同的配置路由方式通过yml文件来配置通过Java Bean来配置 3.2 R…

经典的泡泡龙游戏源码免费下载

源码介绍 HTML5泡泡龙冒险小游戏是一款休闲网页游戏&#xff0c;游戏玩法是玩家从下方中央的弹珠发射台射出彩珠&#xff0c;多于3个同色珠相连则会消失。 源码下载 经典的泡泡龙游戏源码免费下载

搜索与图论:有向图的拓扑序列

搜索与图论&#xff1a;有向图的拓扑序列 题目描述参考代码 题目描述 输入样例 3 3 1 2 2 3 1 3输出样例 1 2 3 参考代码 #include <cstring> #include <iostream> #include <algorithm>using namespace std;const int N 100010;int n, m; int h[N], e…

【Qt知识】部分QWidget属性表格

QWidget是Qt库中所有图形用户界面组件的基类&#xff0c;它提供了大量属性以供自定义和配置控件的行为和外观。下面列出了一些主要的QWidget属性及其作用。 属性 作用 accessibleName 控件的辅助技术名称&#xff0c;用于无障碍访问。 accessibleDescription 控件的辅助技…

前端开发之性能优化

本文章 对各大学习技术论坛知识点&#xff0c;进行总结、归纳自用学习&#xff0c;共勉&#x1f64f; 文章目录 1. [CDN](https://www.bootcdn.cn/)2.懒加载3.缓存4.图片压缩5.图片分割6.sprite7.Code Splitting8.gzip9.GPU加速10.Ajax11.Tree Shaking12.Resource Hints 1. CD…