- 😜作 者:是江迪呀
- ✒️本文关键词:
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 {
// 获取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);
}
}
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