使用JWT、拦截器与ThreadLocal实现在任意位置获取Token中的信息,并结合自定义注解实现对方法的鉴权
1. 简介
1.1 JWT
JWT,即JSON Web Token,是一种用于在网络上传递声明的开放标准(RFC 7519)。JWT 可以在用户和服务器之间传递安全可靠的信息,通常用于身份验证和信息交换。
- 声明(Claims): JWT 包含一组称为声明的信息,声明描述了一些数据。有三种类型的声明:
- 注册声明(Registered Claims):这是一些预定义的声明,包括标准的声明,例如"iss"(签发者)、"sub"(主题)和"exp"(过期时间)等。
- 公共声明(Public Claims):这些声明是由使用 JWT 的双方定义的,并且必须遵守一定的规定,以防止冲突。
- 私有声明(Private Claims):这些是自定义的声明,用于在双方之间共享信息。
- 编码结构: JWT 由三部分组成,使用点号(.)分隔开:
- Header(头部):包含了令牌的元数据,例如算法和令牌类型。
- Payload(载荷):包含了声明,即实际传输的数据。
- Signature(签名):用于验证发送方的身份以及确保消息的完整性。签名由前两部分的内容和密钥组成,以防篡改。
- 编码方法: JWT 可以使用不同的编码方法,包括:
- Base64 URL encoding:用于编码头部和载荷。
- HMACSHA256:用于生成签名,以确保消息完整性。
- 使用场景:
- 身份验证:用户登录后,服务器生成一个包含用户信息的JWT,并将其发送给客户端。客户端在后续请求中将JWT包含在请求头中,服务器验证JWT以确保请求的合法性。
- 信息交换:JWT还可以包含其他信息,例如用户的角色、访问权限等,这些信息可以在不需要查询数据库的情况下进行快速访问。
1.2 拦截器
拦截器(Interceptor)是一种用于处理请求的机制,可以让你在请求的处理过程中进行预处理和后处理。拦截器类似于过滤器(Filter),但相比过滤器,拦截器更加专注于处理控制器层面的请求,可以对处理器的执行过程进行更加细粒度的控制。
使用场景:
- 身份验证和授权:拦截器可以用于检查用户是否已经登录,是否具有足够的权限访问某个资源。
- 日志记录:拦截器可以用于记录请求和响应的日志信息,方便调试和监控。
- 性能监控:通过拦截器,你可以记录请求的处理时间,帮助进行性能监控。
- 执行顺序:拦截器可以配置多个,它们的执行顺序由配置时的顺序决定。
- 异步请求:拦截器也能处理异步请求,需要实现
AsyncHandlerInterceptor接口。
1.3 ThreadLocal
ThreadLocal 是 Java 中的一个类,主要用于提供了线程本地变量。 ThreadLocal 创建的变量只能被当前线程访问,其他线程无法直接访问或修改它。ThreadLocal 主要用于保持线程封闭性,即每个线程都拥有自己独立的变量副本,不同线程之间不会相互干扰。
应用场景:
- 线程安全的数据传递:在多线程环境中,通过
ThreadLocal可以轻松地将数据在方法调用间传递,而无需将数据作为参数传递。 - 数据库连接管理:在数据库连接的管理中,可以使用
ThreadLocal来存储每个线程的数据库连接,确保每个线程使用的是自己的连接。 - 事务管理:
ThreadLocal可以用于事务管理,确保事务的一致性。
注意:
- ThreadLocal可以在虚拟线程环境下使用
ThreadLocal应该谨慎使用,避免滥用。过多的使用可能导致代码难以理解和维护。- 避免在
ThreadLocal中存储大对象,以防止内存泄漏。- 在使用线程池时,需要注意清理
ThreadLocal,以防止线程复用时出现数据污染。
2. 代码实战
2.1 引入依赖
<!-- java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${jwt.version}</version>
</dependency>
2.2 获取token的函数
推荐使用@Value的方式从application.yml中获取密钥和过期时间
// refresh-token密钥
@Value("${refresh-token.secret}")
private String REFRESH_TOKEN_SECRET;
// refresh-token过期时间
@Value("${refresh-token.expire-time}")
private int REFRESH_TOKEN_EXPIRE_TIME;
// refresh-token密钥
@Value("${access-token.secret}")
private String ACCESS_TOKEN_SECRET;
// refresh-token过期时间
@Value("${access-token.expire-time}")
private int ACCESS_TOKEN_EXPIRE_TIME;
/**
* 获取access_token
* @param userId
* @param userType
* @return
*/
private String getAccessToken(int userId, int userType){
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR, ACCESS_TOKEN_EXPIRE_TIME);
return JWT.create()
.withClaim("userId", userId)
.withClaim("userType", userType)
.withExpiresAt(calendar.getTime())
.sign(Algorithm.HMAC512(ACCESS_TOKEN_SECRET));
}
/**
* 获取refresh_token
* @param userId
* @param userType
* @return
*/
private String getRefreshToken(int userId, int userType){
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR, REFRESH_TOKEN_EXPIRE_TIME);
return JWT.create()
.withClaim("userId", userId)
.withClaim("userType", userType)
.withExpiresAt(calendar.getTime())
.sign(Algorithm.HMAC512(REFRESH_TOKEN_SECRET));
}
2.3 ThreadLocal工具类
/**
* <p>
* 用户SessionVO
* </p>
*
* @author jonil
* @since 2023/11/10 16:03
*/
@Data
public class UserSessionVO {
// 用户id
Integer userId;
// 用户类型
Integer userType;
}
/**
* <p>
* 用户session上下文
* </p>
*
* @author jonil
* @since 2023/11/10 16:02
*/
public class UserSessionContext {
private static ThreadLocal<UserSessionVO> userSessionVOThreadLocal = new ThreadLocal<>();
public static void set(UserSessionVO userSessionVO) {
userSessionVOThreadLocal.set(userSessionVO);
}
public static UserSessionVO get() {
return userSessionVOThreadLocal.get();
}
public static void remove() {
userSessionVOThreadLocal.remove();
}
}
2.4 拦截器
此处拦截器为处理HTTP请求前 中 后各阶段需要做的操作。HTTP请求进来的时候将JWT解析的数据放进ThreadLocal,出去的时候需要将数据从ThreadLocal移除,否则会造成内存泄漏。
/**
* <p>
* JWT拦截器
* </p>
*
* @author jonil
* @since 2023/11/10 15:55
*/
public class JWTInterceptor implements HandlerInterceptor {
// refresh-token密钥
@Value("${access-token.secret}")
private String ACCESS_TOKEN_SECRET;
/**
* 将用户基本信息添加到ThreadLocal
* @param request
* @param response
* @param handler
* @return
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String accessToken = request.getHeader("Authorization");
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC512(ACCESS_TOKEN_SECRET)).build();
DecodedJWT decodedJWT = jwtVerifier.verify(accessToken);
Integer userId = decodedJWT.getClaim("userId").asInt();
Integer userType = decodedJWT.getClaim("userType").asInt();
UserSessionVO userSessionVO = new UserSessionVO();
userSessionVO.setUserId(userId);
userSessionVO.setUserType(userType);
UserSessionContext.set(userSessionVO);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) {
}
/**
* 将用户的基本信息从ThreadLocal移除
* @param request
* @param response
* @param handler
* @param ex
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) {
UserSessionContext.remove();
}
}
2.5 使用
在需要用到JWT内容的地方,使用以下代码即可获取对应的内容
UserSessionVO userSessionVO = UserSessionContext.get();
Integer userId = userSessionVO.getUserId();
3. 进阶
3.1 结合自定义注解实现对方法的鉴权
角色枚举类
public enum UserType {
systemAdmin(0),
userAdmin(1),
maintenancePersonnel(2),
vipUser(3),
user(4),
none(5);
UserType(int code) {
this.code = code;
}
private int code;
public int getCode() {
return code;
}
}
自定义注解类
/**
* <p>
* 自定义校验注解
* </p>
*
* @author jonil
* @since 2023/11/13 20:05
*/
@Documented
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {PermissionValidator.class})
public @interface Permission {
UserType type() default UserType.none;
/**
* 是否强制校验
* @return
*/
boolean required() default true;
/**
* 校验不通过时的报错信息
* @return
*/
String message() default "权限不足!";
/**
* 分组
* @return
*/
Class<?>[] groups() default {};
/**
* bean的负载
* @return
*/
Class<? extends Payload>[] payload() default {};
}
自定义注解实现类
/**
* <p>
* 自定义注解实现
* </p>
*
* @author jonil
* @since 2023/11/13 20:05
*/
public class PermissionValidator implements ConstraintValidator<Permission, UserType> {
@Override
public void initialize(Permission constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
/**
* 判断当前是否有权限
* @param userType object to validate
* @param context context in which the constraint is evaluated
*
* @return
*/
@Override
public boolean isValid(UserType userType, ConstraintValidatorContext context) {
return UserSessionContext.get().getUserType() <= userType.getCode();
}
}
使用
/**
* 获取信息接口
*/
@Permission(type = UserType.user)
@GetMapping("/info")
public R getInfo() {
return R.success(service.getInfo());
}
注解会从ThreadLocal获取当前用户的类型,然后进行比对,当然你的判断逻辑可以比这个更加复杂,只需要符合业务实现就好了。
4. 注意
倘若你是在微服务环境或分布式环境下使用这一套逻辑,需要注意在网关(流量网关、业务网关)和OpenFeign中携带原生的Header,否则获取不到该用户的信息。
使用JWT、拦截器与ThreadLocal实现在任意位置获取Token中的信息,并结合自定义注解实现对方法的鉴权的更多相关文章
- 关于springmvc 方法注解拦截器的解决方案,多用于方法的鉴权
最近在用SpringMvc写项目的时候,遇到一个问题,就是方法的鉴权问题,这个问题弄了一天了终于解决了,下面看下解决方法 项目需求:需要鉴权的地方,我只需要打个标签即可,比如只有用户登录才可以进行的操 ...
- JWT拦截器与跨域问题
本文参考: https://blog.csdn.net/csdn_x_w/article/details/108027940 我发现走的都是OPTIONS协议,然后JWT 却把OPTIONS拦截了,于 ...
- 拦截器HandlerInterceptorAdapter的postHandle和afterCompletion无法获取response返回值问题
缘起 有一个需求,在进入controller之前验证调用次数是否超过限制,在响应之后判断是否正常返回,对调用次数进行+1,发现带@RestController的类和带@ResponseBody的方法在 ...
- Android开发 获取视频中的信息(例如预览图或视频时长) MediaMetadataRetriever媒体元数据检索器
前言 在Android里获取视频的信息主要依靠MediaMetadataRetriever实现 获取最佳视频预览图 所谓的最佳就是MediaMetadataRetriever自己计算的 /** * 获 ...
- angular之interceptors拦截器
<!DOCTYPE html> <html ng-app="nickApp"> <head> <meta charset="UT ...
- JavaEE权限管理系统的搭建(六)--------使用拦截器实现菜单URL的跳转权限验证和页面的三级菜单权限按钮显示
本小结讲解,点击菜单进行页面跳转,看下图,点击管理员列表后会被认证拦截器首先拦截,验证用户是否登录,如果登录就放行,紧接着会被权限验证拦截器再次拦截,拦截的时候,会根据URL地址上找到对应的方法,然后 ...
- springboot + 注解 + 拦截器 + JWT 实现角色权限控制
1.关于JWT,参考: (1)10分钟了解JSON Web令牌(JWT) (2)认识JWT (3)基于jwt的token验证 2.JWT的JAVA实现 Java中对JWT的支持可以考虑使用JJWT开源 ...
- Spring AOP 源码分析 - 拦截器链的执行过程
1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...
- java框架之Struts2(4)-拦截器&标签库
拦截器 概述 Interceptor (拦截器):起到拦截客户端对 Action 请求的作用. Filter:过滤器,过滤客户端向服务器发送的请求. Interceptor:拦截器,拦截的是客户端对 ...
- 08springMVC拦截器
u 概述 u 拦截器接口 u 拦截器适配器 u 运行流程图 u 拦截器HelloWorld u 常见应用之性能监控 1 概述 1.1 简介 Spring Web M ...
随机推荐
- linux cp command
参考:http://c.biancheng.net/view/746.html [root@localhost ~]# cp [选项] 源文件 目标文件 -a:相当于 -d.-p.-r 选项的集合,这 ...
- Unity UGUI的PhysicsRaycaster (物理射线检测)组件的介绍及使用
Unity UGUI的PhysicsRaycaster (物理射线检测)组件的介绍及使用 1. 什么是PhysicsRaycaster组件? PhysicsRaycaster是Unity UGUI中的 ...
- 趣图|代码重构前vs重构后
前言 很多程序员对自己写的代码平时很随心所欲,但当有一天让他维护他人的代码,他就会抓狂,很容易激发他体内重构的瘾.(大多数程序员审阅完别人代码后,先会忍不住吐槽一番,然后会忍不住想重构一把,) 在我看 ...
- BugKu:文件包含+php伪协议
这道题一进去发现一个超连接点击后发现跳转到了如下页面url如下/index.php?file=show.php,觉得这道题应该是一个php伪协议的应用 1 php://filter php://fil ...
- 注意!JAVA中的值传递
前言:今天在解决一个问题时,程序总是不能输出正确值,分析逻辑思路没问题后,发现原来是由于函数传递导致了这个情况. LeetCode 113 问题:给你二叉树的根节点root和一个整数目标和target ...
- 基于C#的窗体阴影效果方案 - 开源研究系列文章
最近在研究C#的Winform窗体的效果,上次介绍了窗体动画效果的博文( 基于C#的无边框窗体动画效果的完美解决方案 - 开源研究系列文章 ),这次将窗体阴影效果的方案进行一个介绍. 找了一下度娘,具 ...
- 《深入理解Java虚拟机》读书笔记:HotSpot虚拟机对象探秘
基于实用优先的原则,以常用的虚拟机HotSpot和常用的内存区域Java堆为例,深入探讨HotSpot虚拟机在Java堆中对象分配.布局和访问的全过程.以下是本节内容的脑图. HotSpot虚拟机对象 ...
- 微服务项目使用RabbitMQ
微服务项目使用RabbitMQ 很久未用RabbitMQ了,根据网上的Demo,大多数是一个服务包含所有的生产者和消费者和配置,当自己去搭建服务的时候,还需要一些思考各种包的划分.无法无脑CV大法,所 ...
- selenium + python自动化环境搭建
Selenium是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firef ...
- 记一次MySql灾难性事件
2023年8月8日,本来系一个风和日丽的夏天中的平凡一天,但这种平凡,注定住佢一定唔平凡,唉...现在回忆起都阵阵咁痛!!! 重要嘅事情讲三次,唔好手贱,唔好手贱,唔好手贱 事日,如常上班,本人系一名 ...