jwt token+threadlocal登录 +@ControllerAdvice统一处理登录异常

思路:
调用登录接口时,使用jwt生成token,前端调用接口时在请求头中传入token

调用请求时通过拦截去拦截,获取请求头里的token进行校验并将用户信息保存到threadlocal中
线程执行完后清除threadloal数据 (threadlocal中数据线程执行完毕后不会自动清空,需手动清除,否则可能会出现数据异常-----web中线程是从线程池中获取,创建一个线程后若此前线程的threadlocal数据未被清除,则可能会使用之前threadlocal中的数据,引起数据异常)

登录:
//根据用户名密码校验用户
@GetMapping(“login”)
public AjaxResult login(User user){
try {
//根据用户名密码校验用户
//获取token
return AjaxResult.success(getToken(user));

    } catch (WxErrorException e) {
        e.printStackTrace();
        return AjaxResult.error(e.getError().getErrorMsg());
    }
}

//生成token

public String getToken(User user) {
String token="";
Date now=new Date();
Calendar cal=Calendar.getInstance();
cal.setTime(now);
cal.add(Calendar.DATE,1);
token= JWT.create(). //token创建
withAudience(String.valueOf(user.getId())). //存入需要保存在token的信息,这里存入userid 也可以使用.withClaim()把用户信息存入claim中
withExpiresAt(cal.getTime()) //设置到期时间
.sign(Algorithm.HMAC256(user.getOpenId())); //签名
return token;
}

//配置拦截器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns(”/**"); //拦截哪些路径 可自定义
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}

//拦截器
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
IUserService userService;

@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
    if ("OPTIONS".equals(httpServletRequest.getMethod())) {              //除了 OPTIONS请求以外, 其它请求应该被JWT检查
        return true;
    }
    String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token
    // 如果不是映射到方法直接通过
    if(!(object instanceof HandlerMethod)){
        return true;
    }
    HandlerMethod handlerMethod=(HandlerMethod)object;
    Method method=handlerMethod.getMethod();
    //检查是否有passtoken注释,有则跳过认证
    if (method.isAnnotationPresent(PassToken.class)) {
        PassToken passToken = method.getAnnotation(PassToken.class);
        if (passToken.required()) {
            return true;
        }
    }
    //检查有没有需要用户权限的注解
    if (token == null) {
        throw new UserNotFountException("无token,请重新登录");
        //UserNotFountException为自定义异常类,通过捕捉此异常返回对应的code
    }
    // 获取 token 中的 user id
    String userId;
    try {
        userId = JWT.decode(token).getAudience().get(0);
    } catch (JWTDecodeException j) {
        throw new UserNotFountException("401");
    }
    User user = userService.selectUserById(Long.valueOf(userId));
    if (user == null) {
        throw new UserNotFountException("用户不存在,请重新登录");
    }
    // 验证 token
    JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getOpenId())).build();
    try {
        jwtVerifier.verify(token);
    }catch (TokenExpiredException e){
        throw new UserNotFountException(e.getMessage());
    } catch(JWTVerificationException e) {
        throw new UserNotFountException("401");
    }
    UserThreadLocal.setUser(user);  //将用户信息存入threadlocal中
    return true;
}

//afterCompletion 当整个请求执行完毕时执行的方法,这里去清除threadlocal的数据
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    UserThreadLocal.cleanUser();
}

}

//UserThreadLocal
public class UserThreadLocal {
private static final ThreadLocal local = new ThreadLocal<>();
//这里也可以存储map类型的threadloca来存储多个属性,
//这里只保存user信息

public static void setUser(User user){
    if(local.get()!=null){
        local.remove();
    }
    local.set(user);
}

public static User getUser(){
    return  local.get();
}

public static void cleanUser(){
    if(local.get()!=null){
        local.remove();
    }
}

}

//UserNotFountException
public class UserNotFountException extends RuntimeException{
private static final long serialVersionUID = 1L;

protected final String message;

public UserNotFountException(String message)
{
    this.message = message;
}

public UserNotFountException(String message, Throwable e)
{
    super(message, e);
    this.message = message;
}

@Override
public String getMessage()
{
    return message;
}

}

//通过@ControllerAdvice捕捉控制层异常并同意返回code
@ControllerAdvice
public class ExceptionConfigController {

@ExceptionHandler(UserNotFountException.class)
@ResponseBody
//捕捉自定义的UserNotFountException 异常
public AjaxResult runtimeExceptionHandler(UserNotFountException e){
	//返回系统所统一的返回值类型,以及自己定义的未登录的code
    return new AjaxResult(AjaxResult.Type.UN_LOGIN,e.getMessage());
}

}

//在线程中获取用户信息
public class UserUtils
{
public static User geUser(){
User user = UserThreadLocal.getUser();
if (user==null){
throw new UserNotFountException(“登录失效”);
}
return user;
}
}
后续:1.目前jwt生成的token为access_token,token有过期时间,后续可能需要引入refresh_token来对token进行刷新
2.由于采用的threadlocal保存的用户信息,无法适应多节点,因此后续考虑引入redis

你可能感兴趣的:(jwt token+threadlocal登录 +@ControllerAdvice统一处理登录异常)