鉴权失败需追溯异常链底层原因:Token解析失败、Redis连接异常、自定义StpInterface抛错或Token续期失败。应递归打印getCause()、开启DEBUG日志、避免掩盖异常。
在 sa-token 中鉴权失败时,不能只看最外层抛出的异常(比如 notloginexception 或 nopermissionexception),真正的原因往往藏在异常链(getcause())里。关键是要顺着 cause 一层层往上查,直到找到最底层的触发点——比如 token 解析失败、redis 连接异常、自定义 stpinterface 抛错等。
一、捕获并展开异常链
在全局异常处理器(如 @ControllerAdvice)中,不要只打印 e.toString(),而要递归遍历 getCause():
// 示例:打印完整异常链
private void printCauseChain(Throwable e) {
int level = 0;
while (e != null) {
System.err.println("[" + level + "] " + e.getClass().getSimpleName() + ": " + e.getMessage());
e = e.getCause();
level++;
}
}
这样能清晰看到:最顶层是 NoPermissionException → 中间是 StpUtil.checkPermission() 调用 → 底层可能是 RedisConnectionException 或你 getPermissionList() 方法里手动 throw 的 RuntimeException。
二、重点关注几类典型底层原因
鉴权失败的“表象”统一,但根因差异大,常见底层源头有:
立即学习“Java免费学习笔记(深入)”;
-
Token 解析失败:比如 JWT 签名不匹配、过期时间被篡改、密钥配置错误(
sa-token.jwt-secret不一致),此时底层抛InvalidClaimException或SignatureException -
Redis 通信异常:Sa-Token 默认依赖 Redis 存 Token 和权限数据,若 Redis 宕机或连接池耗尽,
StpUtil.getLoginId()可能因JedisConnectionException失败,进而导致后续鉴权全部 fallback 为未登录 -
自定义接口实现出错:重写了
StpInterface.getPermissionList()或getRoleList(),但方法内访问数据库超时、SQL 报错、空指针未处理,会包装成RuntimeException塞进 cause 链 -
Token 续约/刷新失败:开启
is-share或token-style=jwt时,自动续期逻辑若出错(如 Redis setex 失败),可能静默导致下次请求 Token 已失效
三、配合日志与调试快速定位
Sa-Token 默认日志较简略,建议主动增强可观测性:
- 开启 Sa-Token debug 日志:
logging.level.cn.dev33.satoken=DEBUG,它会在关键路径(如StpLogic.checkPermission())打印决策日志 - 在自定义
StpInterface方法开头加log.debug("getPermissionList for: {}", loginId),确认是否执行到、参数是否正确、耗时是否异常 - 在 Filter 或 Interceptor 中提前调用
StpUtil.isLogin()并捕获异常,比等到 Controller 层再报错更早暴露问题
四、避免掩盖真实异常的写法
以下做法会让排查变困难,需规避:
- 在
getPermissionList()中 catch 所有异常后 return empty list —— 这会让鉴权“看似成功”,实则权限为空,且无任何报错日志 - 全局异常处理器对
NotLoginException统一返回“请登录”,却不记录原始 cause —— 丢失 Redis 超时、JWT 解析失败等关键线索 - 使用
@SaCheckPermission("x")注解时,没配ignoreException=true却又没声明 throws,导致编译不通过或异常被 Spring 框架吞掉


















