当 Token 失效 / 错误时,程序会在「鉴权逻辑执行之前」就触发异常并被全局异常处理器捕获,直接返回错误响应,不会走到后续的权限校验(如 getPermissionList/getRoleList)步骤。
基于 Spring Cloud Gateway + Sa-Token 的架构为例,Token 异常的执行链路如下: 
核心关键点:
- Token 异常 :在
StpUtil.checkLogin()/checkRole()/checkPermission()执行时触发,直接抛异常,不会调用getRoleList/getPermissionList; - Token 有效但权限不足 :会调用
getRoleList/getPermissionList从 Redis 读权限 / 角色,再判断是否匹配,不匹配则抛异常;
typescript
@Component
public class StpInterfaceImpl implements StpInterface {
@Resource
private RedisUtil redisUtil;
private final String authPermissionPrefix = "auth.permission";
private final String authRolePrefix = "auth.role";
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
return getAuth(loginId.toString(),authPermissionPrefix);
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
return getAuth(loginId.toString(),authRolePrefix);
}
private List<String> getAuth(String loginId,String prefix){
String authKey = redisUtil.buildKey(prefix,loginId);
String authValue = redisUtil.get(authKey);
if (StringUtils.isBlank(authValue)){
return Collections.emptyList();
}
List<String> list = new Gson().fromJson(authValue, List.class);
return list;
}
}
- 异常最终兜底 :所有 Sa-Token 异常都会被
GatewayExceptionHandler捕获,返回 JSON 响应。
ini
import cn.dev33.satoken.exception.SaTokenException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lee.club.gateway.entity.Result;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public Mono<Void> handle(ServerWebExchange serverWebExchange, Throwable throwable) {
ServerHttpRequest request = serverWebExchange.getRequest();
ServerHttpResponse response = serverWebExchange.getResponse();
Integer code = 200;
String message = "";
if (throwable instanceof SaTokenException){
code = 401;
message = "用户无权限";
}else {
code = 500;
message = "系统繁忙";
}
Result result = Result.fail(code, message);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return response.writeWith(Mono.fromSupplier(()->{
DataBufferFactory dataBufferFactory = response.bufferFactory();
byte[] bytes = null;
try {
bytes = objectMapper.writeValueAsBytes(result);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return dataBufferFactory.wrap(bytes);
}));
}
}
逐节点拆解执行逻辑
1. 入口:SaReactorFilter 拦截请求
- 触发条件 :所有前端请求都会被
SaReactorFilter拦截(因为配置了addInclude("/**")); - 核心动作 :进入
setAuth方法,执行你定义的所有SaRouter鉴权规则。
less
/**
* 权限认证的配置器
*/
@Configuration
public class SaTokenConfigure {
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
// 拦截地址
.addInclude("/**")
// 鉴权方法:每次访问进入
.setAuth(obj -> {
System.out.println("-------- 前端访问path:" + SaHolder.getRequest().getRequestPath());
// 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
SaRouter.match("/auth/**", "/auth/user/doLogin", r -> StpUtil.checkRole("user1"));
SaRouter.match("/oss/**", r -> StpUtil.checkLogin());
SaRouter.match("/subject/subject/add", r -> StpUtil.checkPermission("subject:add"));
SaRouter.match("/subject/**", r -> StpUtil.checkLogin());
});
}
}
2. 前置:Token 有效性校验
-
触发时机 :执行任何
StpUtil.checkRole()/checkPermission()/checkLogin()时,Sa-Token 会先自动校验 Token; -
结果分支:
- Token 失效 / 错误:直接抛
NotLoginException,跳过后续所有鉴权逻辑; - Token 有效:继续执行路由匹配和权限校验。
- Token 失效 / 错误:直接抛
3. 核心:路由规则匹配
SaRouter 会按你配置的规则,判断请求路径是否匹配:
| 匹配路径 | 执行动作 | 核心目的 |
|---|---|---|
/auth/** |
StpUtil.checkRole("user1") |
校验用户是否有 user1 角色 |
/oss/** |
StpUtil.checkLogin() |
仅校验 Token 有效,不校验角色 / 权限 |
/subject/subject/add |
StpUtil.checkPermission("subject:add") |
校验用户是否有 subject:add 权限 |
/subject/**(非 add) |
StpUtil.checkLogin() |
仅校验 Token 有效 |
| 其他路径 | 直接放行 | 无鉴权要求 |
4. 数据支撑:从 Redis 读取角色 / 权限
- 触发条件 :Token 有效 + 执行
checkRole()/checkPermission(); - 核心动作 :Sa-Token 自动调用你实现的
StpInterfaceImpl.getRoleList()/getPermissionList(),从 Redis 读取该用户的角色 / 权限列表; - 兜底逻辑:Redis 中无数据 → 返回空列表 → 权限校验失败。
5. 决策:角色 / 权限是否匹配
-
角色校验 :判断 Redis 读取的角色列表是否包含
"user1"; -
权限校验 :判断 Redis 读取的权限列表是否包含
"subject:add"; -
结果分支:
- 不匹配:抛
NotRoleException/NotPermissionException; - 匹配:放行请求到后端服务。
- 不匹配:抛
6. 兜底:异常统一处理
所有 Sa-Token 异常(NotLoginException/NotRoleException/NotPermissionException)都会被 GatewayExceptionHandler 捕获:
NotLoginException→ 返回 401 + "Token 失效或未登录";NotRoleException/NotPermissionException→ 返回 403 + "用户无权限";- 其他异常 → 返回 500 + "系统繁忙"。