|
@@ -75,7 +75,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|
|
boolean isHeartbeat = "/api/auth/heartbeat".equals(path);
|
|
boolean isHeartbeat = "/api/auth/heartbeat".equals(path);
|
|
|
|
|
|
|
|
// 尽早从 token 中提取 userId 注入 MDC(宽容模式,过期 token 也能提取)
|
|
// 尽早从 token 中提取 userId 注入 MDC(宽容模式,过期 token 也能提取)
|
|
|
- extractUidForMdc(token);
|
|
|
|
|
|
|
+ Claims claims1 = extractUidForMdcAndSetRequestAttr(token, request);
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
if (token != null && !token.isEmpty() && SecurityContextHolder.getContext().getAuthentication() == null) {
|
|
if (token != null && !token.isEmpty() && SecurityContextHolder.getContext().getAuthentication() == null) {
|
|
@@ -84,7 +84,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|
|
Claims claims;
|
|
Claims claims;
|
|
|
if (isLenientPath) {
|
|
if (isLenientPath) {
|
|
|
// 宽容模式:一次解析 JWT,避免重复调用 parseTokenLenient
|
|
// 宽容模式:一次解析 JWT,避免重复调用 parseTokenLenient
|
|
|
- claims = jwtUtil.parseTokenLenient(token);
|
|
|
|
|
|
|
+ claims = claims1;
|
|
|
} else {
|
|
} else {
|
|
|
// 严格模式:一次解析 JWT,避免重复调用 parseToken
|
|
// 严格模式:一次解析 JWT,避免重复调用 parseToken
|
|
|
claims = jwtUtil.parseToken(token);
|
|
claims = jwtUtil.parseToken(token);
|
|
@@ -96,14 +96,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|
|
String loginSource = claims.get("loginSource", String.class);
|
|
String loginSource = claims.get("loginSource", String.class);
|
|
|
boolean isAdmin = "ADMIN".equals(role) || "SUPER_ADMIN".equals(role);
|
|
boolean isAdmin = "ADMIN".equals(role) || "SUPER_ADMIN".equals(role);
|
|
|
|
|
|
|
|
- if (userId != null) {
|
|
|
|
|
- MDC.put("uid", String.valueOf(userId));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // userId 和 loginSource 已经在 extractUidForMdcAndSetRequestAttr 中设置过了
|
|
|
if ("access".equals(tokenType)) {
|
|
if ("access".equals(tokenType)) {
|
|
|
- request.setAttribute("userId", userId);
|
|
|
|
|
- request.setAttribute("loginSource", loginSource);
|
|
|
|
|
-
|
|
|
|
|
if (!isTokenVersionValid(userId, loginSource, tokenVer, role)) {
|
|
if (!isTokenVersionValid(userId, loginSource, tokenVer, role)) {
|
|
|
log.info("{} {} 的Token版本号不匹配(已在其他设备登录),拒绝访问",
|
|
log.info("{} {} 的Token版本号不匹配(已在其他设备登录),拒绝访问",
|
|
|
isAdmin ? "管理员" : "用户", userId);
|
|
isAdmin ? "管理员" : "用户", userId);
|
|
@@ -162,7 +156,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|
|
*/
|
|
*/
|
|
|
private boolean isTokenVersionValid(Long id, String loginSource,
|
|
private boolean isTokenVersionValid(Long id, String loginSource,
|
|
|
Long tokenVersion, String role) {
|
|
Long tokenVersion, String role) {
|
|
|
- if (id == null) return false;
|
|
|
|
|
|
|
+ if (id == null) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
long tokenVer = tokenVersion != null ? tokenVersion : 0L;
|
|
long tokenVer = tokenVersion != null ? tokenVersion : 0L;
|
|
|
boolean isAdmin = "ADMIN".equals(role) || "SUPER_ADMIN".equals(role);
|
|
boolean isAdmin = "ADMIN".equals(role) || "SUPER_ADMIN".equals(role);
|
|
|
|
|
|
|
@@ -170,17 +166,23 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|
|
if (currentVersion == null) {
|
|
if (currentVersion == null) {
|
|
|
if (isAdmin) {
|
|
if (isAdmin) {
|
|
|
com.xuekairui.user.entity.Admin admin = adminMapper.selectById(id);
|
|
com.xuekairui.user.entity.Admin admin = adminMapper.selectById(id);
|
|
|
- if (admin == null) return false;
|
|
|
|
|
|
|
+ if (admin == null) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
currentVersion = admin.getTokenVersion() != null ? admin.getTokenVersion() : 0L;
|
|
currentVersion = admin.getTokenVersion() != null ? admin.getTokenVersion() : 0L;
|
|
|
} else {
|
|
} else {
|
|
|
User user = userMapper.selectById(id);
|
|
User user = userMapper.selectById(id);
|
|
|
- if (user == null) return false;
|
|
|
|
|
|
|
+ if (user == null) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
currentVersion = user.getTokenVersion() != null ? user.getTokenVersion() : 0L;
|
|
currentVersion = user.getTokenVersion() != null ? user.getTokenVersion() : 0L;
|
|
|
}
|
|
}
|
|
|
tokenVersionCache.putVersion(id, isAdmin, currentVersion);
|
|
tokenVersionCache.putVersion(id, isAdmin, currentVersion);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (isAdmin) return tokenVer <= currentVersion && (currentVersion - tokenVer) < 5;
|
|
|
|
|
|
|
+ if (isAdmin) {
|
|
|
|
|
+ return tokenVer <= currentVersion && (currentVersion - tokenVer) < 5;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// 小程序端:范围匹配,最多 2 台,不踢 PC
|
|
// 小程序端:范围匹配,最多 2 台,不踢 PC
|
|
|
if ("MINIAPP".equals(loginSource)) {
|
|
if ("MINIAPP".equals(loginSource)) {
|
|
@@ -234,18 +236,27 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** 从 token 中提取 userId 注入 MDC,便于整个请求链路的日志带上 uid */
|
|
|
|
|
- private void extractUidForMdc(String token) {
|
|
|
|
|
|
|
+ /** 从 token 中提取 userId 注入 MDC,并设置到 request attribute(宽容模式,过期 token 也能提取) */
|
|
|
|
|
+ private Claims extractUidForMdcAndSetRequestAttr(String token, HttpServletRequest request) {
|
|
|
|
|
+ Claims claims = null;
|
|
|
if (token == null || token.isEmpty()) {
|
|
if (token == null || token.isEmpty()) {
|
|
|
- return;
|
|
|
|
|
|
|
+ return claims;
|
|
|
}
|
|
}
|
|
|
try {
|
|
try {
|
|
|
- Long uid = jwtUtil.parseTokenLenient(token).get("userId", Long.class);
|
|
|
|
|
|
|
+ claims = jwtUtil.parseTokenLenient(token);
|
|
|
|
|
+ Long uid = claims.get("userId", Long.class);
|
|
|
|
|
+ String loginSource = claims.get("loginSource", String.class);
|
|
|
if (uid != null) {
|
|
if (uid != null) {
|
|
|
MDC.put("uid", String.valueOf(uid));
|
|
MDC.put("uid", String.valueOf(uid));
|
|
|
|
|
+ // 同时设置到 request attribute,确保 RequestLoggingFilter 能拿到
|
|
|
|
|
+ request.setAttribute("userId", uid);
|
|
|
|
|
+ if (loginSource != null) {
|
|
|
|
|
+ request.setAttribute("loginSource", loginSource);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
} catch (Exception ignored) {
|
|
} catch (Exception ignored) {
|
|
|
// token 完全无法解析,忽略
|
|
// token 完全无法解析,忽略
|
|
|
}
|
|
}
|
|
|
|
|
+ return claims;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|