JWT
主要作用:
防止在未登录的情况下,可以访问到其他资源
实现思路:
代码实现:
引入jwt坐标
在存放工具类的utils包下,创建一个JwtUtil类
public class JwtUtil {
private static final String KEY = "itheima";
//接收业务数据,生成token并返回
public static String genToken(Map<String, Object> claims) {
return JWT.create()
.withClaim("claims", claims)//可以用来存储用户名和用户id,存储什么可由自己决定
.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))//登录令牌的有效时间设置,以毫秒为单位
.sign(Algorithm.HMAC256(KEY));//密钥
}
//接收token,验证token,并返回业务数据
public static Map<String, Object> parseToken(String token) {
return JWT.require(Algorithm.HMAC256(KEY))
.build()
.verify(token)
.getClaim("claims")
.asMap();
}
}
具体实现:
与redis一起使用
redis
主要作用:
解决修改密码后旧令牌不会失效的问题
主要实现思路:
登录成功后,给浏览器响应令牌的同时,把该令牌存储到redis中(结合JWT)
//登录成功
//获取令牌token
Map<String,Object> claims = new HashMap<>();
claims.put("id",loginUser.getId());
claims.put("username",loginUser.getUsername());
String token = JwtUtil.genToken(claims);
//把token存储到redis中
ValueOperations<String,String> operations = stringRedisTemplate.opsForValue();
operations.set(token,token,1, TimeUnit.HOURS);//名字,内容,后两个参数是用来设置token在redis的存放时间
return Result.success(token);
LoginInterceptor拦截器中,需要验证浏览器携带的令牌,并同时需要获取到redis中存储的与之相同的令牌
在Interceptor包下,创建一个拦截器类为LoginInterceptor(ps:token会存放在请求头中,Authorization中)
@Component
public class LoginInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//令牌验证
String token = request.getHeader("Authorization");
//验证token
try {
//从redis中获取相同的token
ValueOperations<String,String> operations = stringRedisTemplate.opsForValue();
String redisToken = operations.get(token);//通过名字在redis中获取token
if(redisToken==null){
//Token已经失效了
throw new Exception();
}
Map<String,Object> claims = JwtUtil.parseToken(token);
//将业务数据存到ThreadLocal中
ThreadLocalUtil.set(claims);
return true;
} catch (Exception e) {
//http的响应状态码为401
response.setStatus(401);
//不放行
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//清空ThreadLocalUtillocal中的数据
ThreadLocalUtil.remove();
}
}
同时创建一个config包,里面创建一个名为WebConfig的类,设置不拦截登录和注册页面
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry){
//登录接口和注册接口不拦截
registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");
}
}
当用户修改密码成功后,删除redis中存储的旧令牌
//3.删除redis中对应的token
ValueOperations<String,String> operations= stringRedisTemplate.opsForValue();
operations.getOperations().delete(token);
Thread Local
主要作用:
Thread Local 提供线程局部变量,用来存取数据: set()/get(),在拦截器登录完成之后,就利用工具类的set方法,将token中的claim数据存放进去,方便每次的使用,但用完记得调用remove方法释放。
实现思路:
代码实现:
在存放工具类的utils包下,创建一个Thread Local类
@Component
public class LoginInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//令牌验证
String token = request.getHeader("Authorization");
//验证token
try {
//从redis中获取相同的token
ValueOperations<String,String> operations = stringRedisTemplate.opsForValue();
String redisToken = operations.get(token);//通过名字在redis中获取token
if(redisToken==null){
//Token已经失效了
throw new Exception();
}
Map<String,Object> claims = JwtUtil.parseToken(token);
//将业务数据存到ThreadLocal中
ThreadLocalUtil.set(claims);
return true;
} catch (Exception e) {
//http的响应状态码为401
response.setStatus(401);
//不放行
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//清空ThreadLocalUtillocal中的数据
ThreadLocalUtil.remove();
}
}
总结:
引入jwt坐标
创建jwt工具类
写登录成功后的代码,给浏览器响应令牌的同时,把该令牌存储到redis中
创建拦截器,在拦截器中,获取到redis中存储的与之相同的令牌,将业务数据claim存放到Thread Local中(创建Thread Local工具类),同时配置拦截器不拦截登录和注册页面
当用户修改密码成功后,删除redis中存储的旧令牌