企业级SpringBoot单体项目模板 —— 使用 AOP + JWT实现登陆鉴权 布满荆棘的人生 2024-04-23 12:57 84阅读 0赞 > * ?**作** **者**:[是江迪呀][Link 1] > * ✒️**本文关键词**:`SpringBoot`、`企业级`、`项目模板` > * ☀️**每日 一言**:`没学会走就学跑从来都不是问题,要问问自己是不是天才,如果不是,那就要一步步来` #### 文章目录 #### * 使用JWT实现登录鉴权的流程 * 一、AOP * * 1.1 AOP依赖: * 1.2 AOP实现代码: * 二、JWT * * 2.1 JWT的工作流程 * 2.2 依赖: * 2.3 JWT工具类代码: * 三、ThreadLocal * * 3.1 用户上下文代码: * 四、测试 * * 4.1 登陆生成token * 4.2 请求业务代码 > 上回我们完成了[代码管理][Link 2],现在终于可以也一些功能性的代码了,今天我聊一下如何使用`JWT`\+`AOP`\+`ThreadLocal`实现一个在企业中比较常用的登陆鉴权的功能。 ## 使用JWT实现登录鉴权的流程 ## ![在这里插入图片描述][b999e86af67c41d09c6820ab2b66add5.png_pic_center] ## 一、AOP ## 使用AOP拦截业务接口,来做鉴权,并将用户信息放入到`ThreadLocal`中去。 ### 1.1 AOP依赖: ### <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ### 1.2 AOP实现代码: ### package com.shijiangdiya.config; import com.auth0.jwt.interfaces.DecodedJWT; import com.shijiangdiya.common.UserContent; import com.shijiangdiya.entity.user.User; import com.shijiangdiya.utils.JWTUtil; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; @Aspect @Component public class JWTAOP { @Around("execution(* com.shijiangdiya.controller.user.*Controller.*(..))")//拦截com.shijiangdiya.controller.user文件下面的所有以Controller结尾的接口里面的所有方法。 public Object interceptController(ProceedingJoinPoint joinPoint) throws Throwable { // 获取HttpServletRequest ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 获取JWT token String token = request.getHeader("token"); // 解密JWT token并获取用户信息 DecodedJWT decodedJWT = JWTUtil.decodeToken(token); String userId =decodedJWT.getClaim("userId").asString(); String userName = decodedJWT.getClaim("userName").asString(); //将用户信息放入到ThreadLocal User user = new User(); user.setId(Long.valueOf(userId)); user.setName(userName); UserContent.setUserContext(user); try { // 继续处理请求 return joinPoint.proceed(); } finally { // 请求结束后移除ThreadLocal中的信息 UserContent.removeUserContext(); } } } 这里一定要切记手动移除`ThreadLocal`中的值,原因是: * 如果你向`ThreadLocal`中存储了一个对象,这个对象将一直存在于`ThreadLocal`中,不会被垃圾回收。长时间运行的应用程序中,多次存储大量对象可能导致内存泄漏问题。 * 某些情况下,存储在`ThreadLocal`中的数据可能包含敏感信息,如果不手动移除,这些信息可能被其他线程访问,导致数据泄露风险。\\ 这也是面试的一个问题点。 ## 二、JWT ## `JWT`全称为`JsonWebToken`,是一种无状态的,与传统的`session`相比它不会占用服务器的内存,在**扩展**、**安全**、**跨平台**方面要优于session。 ### 2.1 JWT的工作流程 ### ### 2.2 依赖: ### <!--引入jwt--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency> ### 2.3 JWT工具类代码: ### package com.shijiangdiya.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTCreator; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import com.shijiangdiya.config.BusinessException; import java.util.Calendar; import java.util.HashMap; import java.util.Map; public class JWTUtil { // 替换为自己的密钥 private static final String SECRET_KEY = "shijiangdiya"; /** * 生成token * @param map * @return */ public static String generateToken(Map<String, String> map) { JWTCreator.Builder builder = JWT.create(); map.forEach((k, v) -> { builder.withClaim(k, v); }); Calendar instance = Calendar.getInstance(); instance.add(Calendar.HOUR, 1); builder.withExpiresAt(instance.getTime()); return builder.sign(Algorithm.HMAC256(SECRET_KEY)); } /** * 解密token * @param token * @return */ public static DecodedJWT decodeToken(String token) { try { Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY); JWTVerifier verifier = JWT.require(algorithm).build(); return verifier.verify(token); } catch (Exception e) { throw new BusinessException("Token验证失败!"); } } } ## 三、ThreadLocal ## `ThreadLocal`是一个线程本地变量,它允许每个线程都有自己独立的变量副本。`ThreadLocal`通常用于在多线程环境中存储和访问线程相关的数据,以避免线程间的数据竞争和并发问题。 一般在企业级中都会使用一个`UserInfo`的基础类,然后需要使用到当前登录用户信息的类要继承`UserInfo`,这样做代码耦合度太高。而使用`Threadlocal`存放用户信息,就可以避免这样的情况,不需要继承可以随处可以使用,并且线程之间相互隔离,比较方便。 ### 3.1 用户上下文代码: ### package com.shijiangdiya.common; import com.shijiangdiya.entity.user.User; public class UserContent { private static final ThreadLocal<User> userInfo = new ThreadLocal(); /** * 获取用户信息 * @return */ public static User getUserContext(){ return userInfo.get(); } /** * 设置用户信息 * @return */ public static void setUserContext(User userContext){ userInfo.set(userContext); } /** * 清除用户信息 * @return */ public static void removeUserContext(){ userInfo.remove(); } } ## 四、测试 ## ### 4.1 登陆生成token ### 切记登陆的接口要绕过`AOP`的拦截。 package com.shijiangdiya.controller.login; import com.shijiangdiya.config.AbstractController; import com.shijiangdiya.config.Response; import com.shijiangdiya.entity.user.User; import com.shijiangdiya.model.user.LoginUserQO; import com.shijiangdiya.service.user.IUserService; import com.shijiangdiya.utils.JWTUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @RestController @RequestMapping("/user") public class LoginController extends AbstractController { @Autowired private IUserService userService; @PostMapping("/login") public Response login(@RequestBody @Valid LoginUserQO qo){ //查询数据的人员信息 List<User> userInfoByName = userService.getUserByName(qo.getUserName()); if(CollectionUtils.isEmpty(userInfoByName)){ return new Response("401","用户不存在!",null); } List<User> collect = userInfoByName.stream().filter(user -> StringUtils.equals(user.getPassword(), qo.getPassword())).collect(Collectors.toList()); if(CollectionUtils.isEmpty(collect)){ return new Response("401","用户密码错误!",null); } //获取token Map<String,String> userInfo = new HashMap<>(); userInfo.put("userId",String.valueOf(collect.get(0).getId())); userInfo.put("userName",collect.get(0).getName()); String token = JWTUtil.generateToken(userInfo); return new Response("200","登陆成功!",token); } } ![在这里插入图片描述][834c43fb009d415683ef8a23850fd2ea.png] ### 4.2 请求业务代码 ### package com.shijiangdiya.controller.user; import com.shijiangdiya.common.UserContent; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/test") public class TestController { @GetMapping("/test1") public void test1(){ Long id = UserContent.getUserContext().getId(); System.out.println("用户id:"+id); String userName = UserContent.getUserContext().getName(); System.out.println("用户名称:"+userName); } } ![在这里插入图片描述][b2c0deb85772481a8c87c8ba34c4c9b2.png] 控制台输出: 用户id:2 用户名称:hubayi [Link 1]: https://blog.csdn.net/qq_42785250?spm=1011.2124.3001.5343 [Link 2]: https://blog.csdn.net/qq_42785250/article/details/133954402 [b999e86af67c41d09c6820ab2b66add5.png_pic_center]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/23/a123857a98f24c00b0a6d4543a6774cd.png [834c43fb009d415683ef8a23850fd2ea.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/23/d4d0f4a676bf4fa8b716b28cefa02ab1.png [b2c0deb85772481a8c87c8ba34c4c9b2.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/23/06010b9f7dc94445b4e6189f9d89db50.png
相关 【企业级SpringBoot单体项目模板】 —— 全局配置 手把手教你从0到1搭建一个企业级SpringBoot单体项目。 雨点打透心脏的1/2处/ 2024年04月23日 16:00/ 0 赞/ 70 阅读
相关 【企业级SpringBoot单体项目模板】 ——整合MySQL和Mybatis-plus 手把手教你从0到1搭建一个企业级SpringBoot单体项目。 我不是女神ヾ/ 2024年04月23日 16:00/ 0 赞/ 68 阅读
相关 【企业级SpringBoot单体项目模板 】—— 项目代码管理 手把手教你从0到1搭建一个企业级SpringBoot单体项目。 缺乏、安全感/ 2024年04月23日 12:57/ 0 赞/ 78 阅读
相关 企业级SpringBoot单体项目模板 —— 使用 AOP + JWT实现登陆鉴权 手把手教你从0到1搭建一个企业级单体项目。 布满荆棘的人生/ 2024年04月23日 12:57/ 0 赞/ 85 阅读
相关 【企业级SpringBoot单体项目模板 】——Mybatis-plus自动代码生成 手把手教你从0到1搭建一个企业级SpringBoot单体项目。 拼搏现实的明天。/ 2024年04月23日 12:57/ 0 赞/ 80 阅读
相关 【企业级SpringBoot单体项目模板 】—— 一些开发规范 手把手教你从0到1搭建一个企业级SpringBoot单体项目。 末蓝、/ 2024年04月23日 12:56/ 0 赞/ 94 阅读
相关 SpringBoot集成JWT登录鉴权 什么是JWT? Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).定义了一种简洁的,自包 青旅半醒/ 2023年07月25日 06:27/ 0 赞/ 27 阅读
相关 JWT鉴权 jwt是什么? JWTs是JSON对象的编码表示。JSON对象由零或多个名称/值对组成,其中名称为字符串,值为任意JSON值。JWT有助于在clear(例如在URL中)发 我不是女神ヾ/ 2022年09月11日 05:11/ 0 赞/ 276 阅读
相关 JWT鉴权 涉及到3个微服务: geteway网关微服务、用户授权中心微服务、用户微服务; > 用户登录、请求进来, > ![3][] [3]: /images/202204 末蓝、/ 2022年04月10日 05:31/ 0 赞/ 575 阅读
还没有评论,来说两句吧...