java中,可能会用到JWT令牌校验,
这时,大概率会用到 HttpServletRequest,和 HttpServletResponse。
若为 JDK8,SpringBoot 2.7.3 的版本则引入:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
若是 JDK17, SpringBoot 3.4.0 或更高的版本,则为:
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
我出现这个问题的时候,是从哔哩哔哩上找了个项目,打算学习一下,但视频中用的是JDK8,而我用的JDK17,所以在这里记录一下。
拦截器
import cn.hutool.core.util.ObjectUtil;
import com.Jwt工具类.JwtUtil;
import com.ThreadLocal工具类.ThreadLocalUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.common.exception.ServiceException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Map;
/**
* 拦截器
*/
@RequiredArgsConstructor
@Component //注入到IOC容器里
public class LoginInterceptor implements HandlerInterceptor {
private final StringRedisTemplate stringRedisTemplate;
//preHandle : 在……之前处理
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//令牌验证
String token = request.getHeader("Authorization");
//验证 token
try {
//从redis中获取相同的token
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
//根据redis键值对的键,获取对应的值
String redisToken = operations.get(token);
if (ObjectUtil.isNull(redisToken)){
//token失效了
throw new RuntimeException();
}
Map<String, Object> map = JwtUtil.paresToken(token);
//把业务数据存储到ThreadLocal中
ThreadLocalUtil.set(map);
//放行
return true;
} catch (Exception e) {
//http响应状态码401
response.setStatus(401);
//不放行
throw new ServiceException("登录状态已失效,请重新登录");
}
}
//afterCompletion 在……之后处理
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
//清空ThreadLocalUtil中的数据,防止内存泄露
ThreadLocalUtil.remove();
}
}
JWT工具类:
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
private static final String KEY = "event";
//接受业务数据,生成token并返回
public static String genToken(Map<String, Object> claims) {
// 创建 JWT 构建器
return JWT.create()
// 将传入的 claims 放入 token 中,键名为 "claims"
.withClaim("claims", claims)
// 设置过期时间为 当前时间加 3 小时
.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 3))
// 使用 HMAC256 算法,并用 "event" 作为密钥进行签名
.sign(Algorithm.HMAC256(KEY));
}
//接受token, 验证token, 并返回业务数据
public static Map<String, Object> paresToken(String token) {
return JWT.require(Algorithm.HMAC256(KEY))
.build()
.verify(token)
.getClaim("claims")
.asMap();
}
}
ThreadLocal工具类:
ThreadLocal 用来为每个线程提供独立的变量副本,从而实现线程隔离。使每个线程独立地操作自己的变量副本,而不会影响其他线程的副本。
ThreadLocal 的主要作用是解决多线程环境下共享变量的线程安全问题。它通过为每个线程维护一个独立的变量副本,避免了线程之间的竞争和同步问题。
注意事项
避免滥用:ThreadLocal 适用于线程隔离的场景,但不适合存储大量数据,否则可能会导致内存泄漏。
清理资源:在使用线程池时,务必在任务执行完毕后调用 ThreadLocal.remove(),以防止内存泄漏。
线程池中的问题:如果线程池中的线程被复用,且 ThreadLocal 变量没有被清理,可能会导致数据错乱。
public class ThreadLocalUtil {
private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();
//根据键获取值
public static <T> T get(){
return (T) THREAD_LOCAL.get();
}
//存储键值对
public static void set(Object value){
THREAD_LOCAL.set(value);
}
//清除ThreadLocal 防止内存泄露
public static void remove(){
THREAD_LOCAL.remove();
}
}