思路:
调用登录接口时,使用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
//这里只保存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