fix(auth): 踢下线未登录异常返回401并按原因区分提示

SaExceptionHandler新增sa-token NotLoginException处理, 修复原500兜底致前端401拦截失效; auth.json补充6种未登录文案
This commit is contained in:
DaxPay Dev
2026-07-12 10:57:24 +08:00
parent 5b51da3e6a
commit a60bb39671
3 changed files with 40 additions and 2 deletions

View File

@@ -2,6 +2,7 @@ package cn.daxpay.open.platform.capability.auth.handler;
import cn.daxpay.open.platform.common.i18n.util.I18nUtil;
import cn.daxpay.open.platform.core.code.CommonCode;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.rest.Res;
import cn.daxpay.open.platform.core.rest.result.Result;
import cn.daxpay.open.platform.capability.auth.exception.NotLoginException;
@@ -37,6 +38,31 @@ public class SaExceptionHandler {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);
}
/// 处理 Sa-Token 抛出的未登录异常(被踢下线/顶下线/过期等)
///
/// Sa-Token 自身的 [cn.dev33.satoken.exception.NotLoginException] 与本项目自定义的
/// [NotLoginException] 同名但不同包, 互无继承关系; 前者继承自 [SaTokenException],
/// 若不在此单独处理, 会落入下方 [SaTokenException] 兜底而返回 500, 前端 401 拦截无法感知。
/// 按异常 type 映射不同 i18n 文案, 返回 401 以便前端识别并跳转登录页。
@ExceptionHandler(cn.dev33.satoken.exception.NotLoginException.class)
public ResponseEntity<Result<Void>> handleSaTokenNotLoginException(cn.dev33.satoken.exception.NotLoginException ex) {
String type = ex.getType();
// 按未登录原因映射 i18n 消息 key
String messageKey = switch (type) {
case cn.dev33.satoken.exception.NotLoginException.NOT_TOKEN -> "error.auth.notToken";
case cn.dev33.satoken.exception.NotLoginException.INVALID_TOKEN -> "error.auth.invalidToken";
case cn.dev33.satoken.exception.NotLoginException.TOKEN_TIMEOUT -> "error.auth.tokenTimeout";
case cn.dev33.satoken.exception.NotLoginException.BE_REPLACED -> "error.auth.beReplaced";
case cn.dev33.satoken.exception.NotLoginException.KICK_OUT -> "error.auth.kickOut";
case cn.dev33.satoken.exception.NotLoginException.TOKEN_FREEZE -> "error.auth.tokenFreeze";
default -> "error.auth.notLogin";
};
String message = I18nUtil.get(messageKey);
log.info("Sa-Token 未登录 type={}, key={}", type, messageKey);
Result<Void> result = Res.response(CommonErrorCode.AUTHENTICATION_FAIL, message, MDC.get(CommonCode.TRACE_ID));
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);
}
/// 路径无权访问
@ExceptionHandler(RouterCheckException.class)
public ResponseEntity<Result<Void>> handleBusinessException(RouterCheckException ex) {

View File

@@ -20,5 +20,11 @@
"twoFactorRequired": "Two-factor authentication required",
"twoFactorCodeError": "Incorrect verification code or backup code",
"twoFactorPreAuthExpired": "Pre-auth token is invalid or expired, please log in again",
"loginRetryLock": "Too many failed login attempts, please try again in {0} minutes"
"loginRetryLock": "Too many failed login attempts, please try again in {0} minutes",
"notToken": "No authentication credential provided",
"invalidToken": "Invalid authentication credential",
"tokenTimeout": "Session expired, please log in again",
"beReplaced": "Account signed in on another device",
"kickOut": "Session no longer valid, please log in again",
"tokenFreeze": "Authentication credential frozen"
}

View File

@@ -20,5 +20,11 @@
"twoFactorRequired": "需要双因素认证",
"twoFactorCodeError": "动态码或备用码错误",
"twoFactorPreAuthExpired": "预认证令牌无效或已过期,请重新登录",
"loginRetryLock": "登录失败次数过多,请{0}分钟后重试"
"loginRetryLock": "登录失败次数过多,请{0}分钟后重试",
"notToken": "未提供登录凭证",
"invalidToken": "登录凭证无效",
"tokenTimeout": "登录已过期,请重新登录",
"beReplaced": "账号已在其他设备登录",
"kickOut": "登录状态已失效,请重新登录",
"tokenFreeze": "登录凭证已被冻结"
}