修改拦截器
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.anno.IgnoreAuth;
|
||||||
import com.zhangy.skyeye.common.extend.exception.AuthException;
|
import com.zhangy.skyeye.common.extend.exception.AuthException;
|
||||||
import com.zhangy.skyeye.common.extend.util.JsonUtil;
|
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.JwtUtil;
|
||||||
import com.zhangy.skyeye.common.utils.SpringUtil;
|
import com.zhangy.skyeye.common.utils.SpringUtil;
|
||||||
import com.zhangy.skyeye.redis.utils.RedisUtil;
|
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.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.lang.reflect.Method;
|
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
|
@Slf4j
|
||||||
public class AuthInterceptor implements AsyncHandlerInterceptor {
|
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 {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
|
// 放行 CORS 预检请求(OPTIONS),由后续 Filter 统一处理跨域头
|
||||||
// 1. 放行 OPTIONS 请求(CORS 预检请求),让过滤器在响应时添加cors头
|
if (HttpMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())) {
|
||||||
if (HttpMethod.OPTIONS.name().equals(request.getMethod())) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// 放行静态资源请求
|
||||||
// 2. 静态资源直接放行
|
|
||||||
if (handler instanceof ResourceHttpRequestHandler) {
|
if (handler instanceof ResourceHttpRequestHandler) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// 只处理 HandlerMethod(Controller 方法),其他情况一律拒绝
|
||||||
// 3. 非 HandlerMethod 的请求(如默认处理器)拒绝访问
|
|
||||||
if (!(handler instanceof HandlerMethod)) {
|
if (!(handler instanceof HandlerMethod)) {
|
||||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
|
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
||||||
Method method = handlerMethod.getMethod();
|
Method method = handlerMethod.getMethod();
|
||||||
if (method.getAnnotation(IgnoreAuth.class) != null) {
|
// 带有 @IgnoreAuth 注解的方法直接放行(无需登录)
|
||||||
|
if (method.isAnnotationPresent(IgnoreAuth.class)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// 获取并校验 token
|
||||||
String token = SecurityUtil.getToken(request);
|
String token = SecurityUtil.getToken(request);
|
||||||
if (token == null) {
|
if (token == null || token.trim().isEmpty()) {
|
||||||
throw new AuthException("请求未授权");
|
throw new AuthException("请求未授权:缺少 token");
|
||||||
}
|
}
|
||||||
|
// 解析 token 中的用户名信息
|
||||||
// 测试
|
String usernamePayload;
|
||||||
if (ObjectUtil.isNotEmpty(token)) {
|
|
||||||
if (token.equals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.ayJleHAiOjE2NjMyOTQwMzQsInVzZXJuYW1lIjoie1widXNlcklkXCI6MSxcInVzZXJuYW1lXCI6XCJhZG1pblwifSJ9.aFM87d8iLyRUyHwfd8Lt0a7BDNxIyLAZaVgkiJ6cjHg")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String userName = JwtUtil.getUserName(token);
|
|
||||||
if (userName == null) {
|
|
||||||
throw new AuthException("请求未授权");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 校验token是否失效
|
|
||||||
try {
|
try {
|
||||||
UserDTO userDTO = JsonUtil.parseEx(userName, UserDTO.class);
|
usernamePayload = JwtUtil.getUserName(token);
|
||||||
String redisKeyUserToken = userDTO.getTokenKey();
|
} catch (Exception e) {
|
||||||
Object redisToken = redisUtil.get(redisKeyUserToken);
|
log.warn("JWT 解析失败,token: {}", token.substring(0, Math.min(20, token.length())), e);
|
||||||
if(redisToken == null){
|
throw new AuthException("无效的 token,请重新登录");
|
||||||
throw new AuthException("登录状态已失效,请重新登陆");
|
|
||||||
}
|
}
|
||||||
if(!token.equals(redisToken)){
|
if (usernamePayload == null || usernamePayload.trim().isEmpty()) {
|
||||||
throw new AuthException("该账号在另一台设备登录,请重新登陆");
|
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) {
|
} catch (JsonProcessingException e) {
|
||||||
log.error("token解析异常", e);
|
log.error("token 中的用户信息解析失败: {}", usernamePayload, e);
|
||||||
throw new AuthException("请求未授权,请重新登录");
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user