springboot+拦截器+本地线程实现token的解析及用户信息上下文使用
1、先创建一个本地线程内的用户对象AccountInfo
package com.nuoyi.study.common.thread.accountthread;
import com.nuoyi.study.common.sql.SqlLogInfo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* @desc: 用户信息
* @Author: nuoyi
* @Date: 2024/01/25 11:06
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AccountInfo implements Serializable {
/**
* 用户Id
*/
private Integer id;
/**
* 姓名
*/
private String name;
/**
* 手机号
*/
private String mobile;
/**
* 角色ID
*/
private Integer roleId;
/**
* 角色名称
*/
private String roleName;
/**
* 是否启用 1 启用 0 禁用
*/
private Integer state;
/**
* 请求开始时间戳 用于计算请求时长
*/
private Long reqTimestamp;
/**
* 请求体,请求参数
*/
private String requestBody;
/**
* 头像
*/
private String avatar;
/**
* sql记录集合
*/
private Map<String, SqlLogInfo> sqlList;
}
2、创建一个本地用户线程AccountThreadLocal
package com.nuoyi.study.common.thread.accountthread;
import cn.hutool.core.util.ObjectUtil;
import com.nuoyi.study.common.sql.SqlLogInfo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @desc: 用户本地线程
* @Author: nuoyi
* @Date: 2024/01/25 11:06
*/
public class AccountThreadLocal {
private AccountThreadLocal() {
}
private static final ThreadLocal<AccountInfo> LOCAL = new ThreadLocal<>();
public static void set(AccountInfo accountInfo) {
LOCAL.set(accountInfo);
}
public static AccountInfo get() {
AccountInfo accountInfo = LOCAL.get();
if (ObjectUtil.isNull(accountInfo)) {
return new AccountInfo();
}
return accountInfo;
}
/**
* 设置请求体
*
* @param jsonStr 请求体
*/
public static void setRequestBody(String jsonStr) {
AccountInfo accountInfo = LOCAL.get();
if (ObjectUtil.isNotNull(accountInfo)) {
accountInfo.setRequestBody(jsonStr);
}
}
/**
* 获取用户id
*/
public static Integer getAccountId() {
return LOCAL.get() == null ? 0 : LOCAL.get().getId();
}
/**
* 移除本地线程
*/
public static void remove() {
LOCAL.remove();
}
/**
* 设置sql日志
*
* @param sqlMap 接口所有sql相关信息
*/
public static void setSqlLog(Map<String, SqlLogInfo> sqlMap) {
AccountInfo accountInfo = LOCAL.get();
if (ObjectUtil.isNull(accountInfo)) {
accountInfo = new AccountInfo();
}
accountInfo.setSqlList(sqlMap);
}
/**
* 获取sql日志
*/
public static Map<String, SqlLogInfo> getSqlLog() {
AccountInfo accountInfo = LOCAL.get();
if (ObjectUtil.isNull(accountInfo)) {
return new HashMap<>(1);
} else {
return accountInfo.getSqlList();
}
}
}
3、创建一个拦截器AuthTokenIntercept用户校验用户token相关功能 内部的枚举异常码ApiCodeEnum可自定义
package com.nuoyi.study.handle.intercept;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.nuoyi.study.common.config.GlobalConfiguration;
import com.nuoyi.study.common.enums.RedisKeyEnum;
import com.nuoyi.study.common.thread.accountthread.AccountInfo;
import com.nuoyi.study.common.thread.accountthread.AccountThreadLocal;
import com.nuoyi.study.common.thread.sqlthread.SqlLogThreadLocal;
import com.nuoyi.study.dao.mapper.RoleMapper;
import com.nuoyi.study.dao.mapper.UserMapper;
import com.nuoyi.study.dao.po.Role;
import com.nuoyi.study.dao.po.User;
import com.nuoyi.tool.entity.BaseResponse;
import com.nuoyi.tool.enums.ApiCodeEnum;
import com.nuoyi.tool.exception.ServiceException;
import com.nuoyi.tool.utils.AesUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.Optional;
/**
* @author nuoyi
* @date 2024/1/25 11:08
* @description token访问权限拦截器
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class AuthTokenIntercept implements HandlerInterceptor {
private final RedisTemplate<String, String> redisTemplate;
private final GlobalConfiguration configuration;
private final RoleMapper roleMapper;
private final UserMapper userMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求头中获取token
String token = request.getHeader("token");
printUrLLog(request.getRequestURI(), token);
if (StrUtil.isEmpty(token)) {
returnResponse(response, ApiCodeEnum.TokenDeletion.getCode(), ApiCodeEnum.TokenDeletion.getDescription());
return false;
}
try {
// 解析token
String tokenStr = AesUtil.decrypt(URLDecoder.decode(token,"UTF-8"), configuration.getSecretKey());
User user = JSON.parseObject(tokenStr, User.class);
String redisToken = redisTemplate.opsForValue().get(RedisKeyEnum.Token.getKey() + ":" + user.getId());
if(StrUtil.isBlank(redisToken)){
returnResponse(response, ApiCodeEnum.TokenExpire.getCode(), ApiCodeEnum.TokenExpire.getDescription());
return false;
}
AccountThreadLocal.set(userMapper.getAccountInfo(user.getId()));
} catch (Exception e) {
e.printStackTrace();
returnResponse(response, ApiCodeEnum.TokenError.getCode(), ApiCodeEnum.TokenError.getDescription());
return false;
}
return true;
}
/**
* 打印url日志
*
* @param url 请求路径
* @param token 请求token
*/
private void printUrLLog(String url, String token) {
log.info("url>>>>>{},token>>>>>>{}",url,token);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception
ex) throws Exception {
// 移除用户信息
AccountThreadLocal.remove();
}
/**
* 拦截器返回值
*
* @param response
* @param code
* @param description
*/
private void returnResponse(HttpServletResponse response, Integer code, String description) {
try {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().print(JSON.toJSONString(new BaseResponse(code, description)));
} catch (IOException e) {
throw new ServiceException(ApiCodeEnum.AppError.getCode(), e.getMessage());
}
}
}
4、即可在项目上下文中自由使用用户信息啦
AccountThreadLocal.getAccountId();
AccountInfo accountInfo = AccountThreadLocal.get();
AccountThreadLocal.get().getName();//可以获取用户各种属性