企业级SpringBoot单体项目模板 —— 使用 AOP + JWT实现登陆鉴权

  • 😜           :是江迪呀
  • ✒️本文关键词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 请求业务代码

上回我们完成了代码管理,现在终于可以也一些功能性的代码了,今天我聊一下如何使用JWT+AOP+ThreadLocal实现一个在企业中比较常用的登陆鉴权的功能。

使用JWT实现登录鉴权的流程

在这里插入图片描述

一、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 {// 获取HttpServletRequestServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 获取JWT tokenString token = request.getHeader("token");// 解密JWT token并获取用户信息DecodedJWT decodedJWT = JWTUtil.decodeToken(token);String userId =decodedJWT.getClaim("userId").asString();String userName = decodedJWT.getClaim("userName").asString();//将用户信息放入到ThreadLocalUser 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 {@Autowiredprivate 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);}//获取tokenMap<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);}
}

在这里插入图片描述

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);}
}

在这里插入图片描述
控制台输出:

用户id:2
用户名称:hubayi

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

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

相关文章

python用cv2画图(line, rectangle, text等)

Python做图像图形研究的时候&#xff0c;通常需要画很多辅助几何形状&#xff08;比如bounding box等&#xff09;。基于opencv的几何图形绘制具有易用性&#xff0c;而且天然能和numpy数组交互。 本文总结了几种常用的cv2画几何图形的方法&#xff0c;当一个简易的手册使用&a…

飞书开发学习笔记(三)-利用python开发调试云文档和电子表格

飞书开发学习笔记(三)-利用python开发调试云文档和电子表格 一.建立Python飞书开发环境 首先还是进入开放平台下的API调试台 飞书开放平台&#xff1a;https://open.feishu.cn/app?langzh-CN 以获取"我的空间"下的文件清单为例&#xff0c;通过获取飞书API调试台提…

canvas实现刮奖功能

canvas刮奖原理很简单&#xff0c;就是在刮奖区添加两个canvas&#xff0c;第一个canvas用于显示刮开后显示的内容&#xff0c;可以是一张图片或一个字符串&#xff0c;第二个canvas用于显示涂层&#xff0c;可以用一张图片或用纯色填充&#xff0c;第二个canvas覆盖在第一个ca…

【JMeter】后置处理器的分类以及场景介绍

1.常用后置处理器的分类 Json提取器 针对响应体的返回结果是json格式的会自动生成新的变量名为【提取器中变量名_MatchNr】,取到的个数由jsonpath expression取到的个数决定 可以当作普通变量调用,调用语法:${提取器中变量名_MatchNr}正则表达式提取器 返回结果是任何数据格…

win10 + cmake3.17 编译 giflib5.2.1

所有源文件已经打包上传csdn&#xff0c;大家可自行下载。 1. 下载giflib5.2.1&#xff0c;解压。 下载地址&#xff1a;GIFLIB - Browse Files at SourceForge.net 2. 下载CMakeLists.txt 及其他依赖的文件 从github上的osg-3rdparty-cmake项目&#xff1a; https://github.…

第五部分:Tomcat

5.1&#xff1a;JavaWeb 5.1.1&#xff1a;JavaWeb的概念 ①什么是JavaWeb? JavaWeb是指所有通过Java语言编写可以通过浏览器访问的程序的总称 JavaWeb是基于请求和响应来开发的 ②什么是请求&#xff1f; 请求是指客户端给服务器发送数据&#xff0c;叫请求Request ③什么是…

谁说 Linux 不能玩游戏?

在上个世纪最早推出视频游戏的例子是托马斯戈德史密斯&#xff08;Thomas T. Goldsmith Jr.&#xff09;于1947年开发的“「Cathode Ray Tube Amusement Device」”&#xff0c;它已经显着发展&#xff0c;并且已成为人类生活中必不可少的一部分。 通过美国游戏行业的统计数据&…

String的几个常见面试题及其解析

String s3 new String("a") new String("b")会不会在常量池中创建对象&#xff1f; 答案&#xff1a;不会&#xff0c;首先需要解释“”字符串拼接的理解。 采用 运算符拼接字符串时&#xff1a; 如果拼接的都是字符串直接量&#xff0c;则在编译时编…

【软件逆向】如何逆向Unity3D+il2cpp开发的安卓app【IDA Pro+il2CppDumper+DnSpy+AndroidKiller】

教程背景 课程作业要求使用反编译技术&#xff0c;在游戏中实现无碰撞。正常情况下碰撞后角色死亡&#xff0c;修改为直接穿过物体不死亡。 需要准备的软件 il2CppDumper。DnSpy。IDA Pro。AndroidKiller。 一、使用il2CppDumper导出程序集 将{my_game}.apk后缀修改为{my_…

rabbitmq的confirm模式获取correlationData为null解决办法

回调函数confirm中的correlationDatanull // 实现confirm回调,发送到和没发送到exchange,都触发 Override public void confirm(CorrelationData correlationData, boolean ack, String cause) {// 参数说明:// correlationData: 相关数据,可以在发送消息时,进行设置该参数// …

Go 方法介绍,理解“方法”的本质

Go 方法介绍&#xff0c;理解“方法”的本质 文章目录 Go 方法介绍&#xff0c;理解“方法”的本质一、认识 Go 方法1.1 基本介绍1.2 声明1.2.1 引入1.2.2 一般声明形式1.2.3 receiver 参数作用域1.2.4 receiver 参数的基类型约束1.2.5 方法声明的位置约束1.2.6 如何使用方法 二…

【网络协议】聊聊CND如何进行提升系统读性能

我们知道对于京东这种仓储来说&#xff0c;其实并不是在北京有一个仓储中心&#xff0c;而是针对全国主要的地方&#xff0c;北京、上海、广州、杭州&#xff0c;郑州等地方都有自己的仓储中心&#xff0c;当用户下单后&#xff0c;就会根据最近的仓储进行发货&#xff0c;不仅…

Kafka基本原理、生产问题总结及性能优化实践 | 京东云技术团队

Kafka是最初由Linkedin公司开发&#xff0c;是一个分布式、支持分区的&#xff08;partition&#xff09;、多副本的&#xff08;replica&#xff09;&#xff0c;基于zookeeper协调的分布式消息系统&#xff0c;它的最大的特性就是可以实时的处理大量数据以满足各种需求场景&a…

【快速解决】Android Studio ERROR: Read timed out

目录 前言 回顾我查到过的解决方案&#xff08;这里是我自己解决时候的经历&#xff0c;赶时间的可以直接跳过看文章最后&#xff0c;快速进行解决&#xff09; 快速解决方案如下 总结 前言 当我们新建一个安卓项目出现Read timed out时候不要慌&#xff0c;这篇文章会打开…

图像置乱加密的破解方法

仅仅通过置乱的方式,是无法对图像进行安全加密的。 针对采用置乱方式加密,可以采用多对(明文、密文)推导出加密时所使用的置乱盒。 step1 :初始化 1、使用I表示明文,E表示密文,彼此间关系如下: 2、为了处理上的方便,把二维转换为一维(这里为了说明方便,实际上,大…

CoT: 思路链提示促进大语言模型的多步推理

CoT 总览摘要1 引言2 Chain-of-Thought Prompting3 算术推理 &#xff08;Arithmetic Reasoning&#xff09;3.1 实验设置3.2 结果3.3 消融实验3.4 CoT的鲁棒性 4 常识推理 &#xff08;Commonsense Reasoning&#xff09;5 符号推理 &#xff08;Symbolic Reasoning&#xff0…

“Java与Redis的默契舞曲:优雅地连接与存储数据“

文章目录 引言1. Java连接上Redis2. Java对Redis进行存储数据2.1 存储set类型数据2.2 存储hash类型数据2.3 存储list类型数据 总结 引言 在现代软件开发中&#xff0c;数据存储和处理是至关重要的一环。Java作为一门强大的编程语言&#xff0c;与Redis这个高性能的内存数据库相…

pytorch笔记 GRUCELL

1 介绍 GRU的一个单元 2 基本使用方法 torch.nn.GRUCell(input_size, hidden_size, biasTrue, deviceNone, dtypeNone) 输入&#xff1a;&#xff08;batch&#xff0c;input_size&#xff09; 输出和隐藏层&#xff1a;&#xff08;batch&#xff0c;hidden_size&#xf…

vue3+element Plus实现弹框的拖拽、可点击底层页面功能

1、template部分 <el-dialog:modal"false"v-model"dialogVisible"title""width"30%"draggable:close-on-click-modal"false"class"message-dialog"> </el-dialog> 必须加的属性 modal:是否去掉遮罩层…

Redis原理到常用语法基础图文讲解

在初期&#xff0c;已经讲述了Redis安装问题。现在正式进入Redis的入门阶段 系统架构的演进 传统单机架构 一台机器运行应用程序、数据库服务器 现在大部分公司的产品都是这种单机架构。因为现在计算机硬件发展速度很快&#xff0c;哪怕只有一台主机&#xff0c;性能也很高…