修改拦截器
This commit is contained in:
parent
e7e12bc6a1
commit
b9bf191c8b
@ -4,7 +4,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.zhangy.skyeye.common.extend.anno.IgnoreAuth;
|
||||
import com.zhangy.skyeye.common.extend.exception.AuthException;
|
||||
import com.zhangy.skyeye.common.extend.util.JsonUtil;
|
||||
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
|
||||
import com.zhangy.skyeye.common.utils.JwtUtil;
|
||||
import com.zhangy.skyeye.common.utils.SpringUtil;
|
||||
import com.zhangy.skyeye.redis.utils.RedisUtil;
|
||||
@ -19,70 +18,88 @@ import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* JWT + Redis 双重校验的权限拦截器
|
||||
* 主要功能:
|
||||
* 1. 放行OPTIONS预检请求
|
||||
* 2. 放行静态资源
|
||||
* 3. 跳过标注了 @IgnoreAuth 的接口
|
||||
* 4. 校验 JWT token 的合法性
|
||||
* 5. 校验 token 是否在 Redis 中存在(实现单点登录/踢出其他设备登录)
|
||||
* 6. 续期 Redis 中的 token 有效期
|
||||
*/
|
||||
@Slf4j
|
||||
public class AuthInterceptor implements AsyncHandlerInterceptor {
|
||||
|
||||
private RedisUtil redisUtil = SpringUtil.getBean(RedisUtil.class);
|
||||
private final RedisUtil redisUtil = SpringUtil.getBean(RedisUtil.class);
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
|
||||
// 1. 放行 OPTIONS 请求(CORS 预检请求),让过滤器在响应时添加cors头
|
||||
if (HttpMethod.OPTIONS.name().equals(request.getMethod())) {
|
||||
// 放行 CORS 预检请求(OPTIONS),由后续 Filter 统一处理跨域头
|
||||
if (HttpMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. 静态资源直接放行
|
||||
// 放行静态资源请求
|
||||
if (handler instanceof ResourceHttpRequestHandler) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. 非 HandlerMethod 的请求(如默认处理器)拒绝访问
|
||||
// 只处理 HandlerMethod(Controller 方法),其他情况一律拒绝
|
||||
if (!(handler instanceof HandlerMethod)) {
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
|
||||
return false;
|
||||
}
|
||||
|
||||
HandlerMethod handlerMethod = (HandlerMethod)handler;
|
||||
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
||||
Method method = handlerMethod.getMethod();
|
||||
if (method.getAnnotation(IgnoreAuth.class) != null) {
|
||||
// 带有 @IgnoreAuth 注解的方法直接放行(无需登录)
|
||||
if (method.isAnnotationPresent(IgnoreAuth.class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取并校验 token
|
||||
String token = SecurityUtil.getToken(request);
|
||||
if (token == null) {
|
||||
throw new AuthException("请求未授权");
|
||||
if (token == null || token.trim().isEmpty()) {
|
||||
throw new AuthException("请求未授权:缺少 token");
|
||||
}
|
||||
|
||||
// 测试
|
||||
if (ObjectUtil.isNotEmpty(token)) {
|
||||
if (token.equals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.ayJleHAiOjE2NjMyOTQwMzQsInVzZXJuYW1lIjoie1widXNlcklkXCI6MSxcInVzZXJuYW1lXCI6XCJhZG1pblwifSJ9.aFM87d8iLyRUyHwfd8Lt0a7BDNxIyLAZaVgkiJ6cjHg")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
String userName = JwtUtil.getUserName(token);
|
||||
if (userName == null) {
|
||||
throw new AuthException("请求未授权");
|
||||
}
|
||||
|
||||
// 校验token是否失效
|
||||
// 解析 token 中的用户名信息
|
||||
String usernamePayload;
|
||||
try {
|
||||
UserDTO userDTO = JsonUtil.parseEx(userName, UserDTO.class);
|
||||
String redisKeyUserToken = userDTO.getTokenKey();
|
||||
Object redisToken = redisUtil.get(redisKeyUserToken);
|
||||
if(redisToken == null){
|
||||
throw new AuthException("登录状态已失效,请重新登陆");
|
||||
usernamePayload = JwtUtil.getUserName(token);
|
||||
} catch (Exception e) {
|
||||
log.warn("JWT 解析失败,token: {}", token.substring(0, Math.min(20, token.length())), e);
|
||||
throw new AuthException("无效的 token,请重新登录");
|
||||
}
|
||||
if(!token.equals(redisToken)){
|
||||
throw new AuthException("该账号在另一台设备登录,请重新登陆");
|
||||
if (usernamePayload == null || usernamePayload.trim().isEmpty()) {
|
||||
throw new AuthException("token 内容异常,请重新登录");
|
||||
}
|
||||
redisUtil.expire(redisKeyUserToken, JwtUtil.EXPIRE_TIME / 1000);
|
||||
// 尝试将 payload 解析为 UserDTO
|
||||
UserDTO userDTO;
|
||||
try {
|
||||
userDTO = JsonUtil.parseEx(usernamePayload, UserDTO.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("token解析异常", e);
|
||||
throw new AuthException("请求未授权,请重新登录");
|
||||
log.error("token 中的用户信息解析失败: {}", usernamePayload, e);
|
||||
throw new AuthException("token 格式错误,请重新登录");
|
||||
}
|
||||
//log.debug("请求来源:" + request.getRemoteAddr());
|
||||
// 获取该用户在 Redis 中存储的 token
|
||||
String redisKey = userDTO.getTokenKey();
|
||||
if (redisKey == null || redisKey.trim().isEmpty()) {
|
||||
throw new AuthException("用户信息异常,请重新登录");
|
||||
}
|
||||
Object storedToken = redisUtil.get(redisKey);
|
||||
// Redis 中不存在 → 登录已失效
|
||||
if (storedToken == null) {
|
||||
throw new AuthException("登录状态已失效,请重新登录");
|
||||
}
|
||||
// Redis 中的 token 与当前请求不一致 → 多设备登录,被踢
|
||||
if (!Objects.equals(token, storedToken.toString())) {
|
||||
log.warn("检测到多端登录冲突,用户: {}, 当前token: {}, redisToken: {}",
|
||||
usernamePayload, token.substring(0, 10) + "...",
|
||||
storedToken.toString().substring(0, 10) + "...");
|
||||
throw new AuthException("该账号已在其他设备登录,请重新登录");
|
||||
}
|
||||
// 校验通过 → 续期 Redis token 有效时间
|
||||
redisUtil.expire(redisKey, JwtUtil.EXPIRE_TIME / 1000);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user