
if (currentUser == null) {
return Result.fail("请先登录");
}
if (!currentUser.hasRole("admin")) {
return Result.fail("无权限");
}
if (currentUser.isDisabled()) {
return Result.fail("账号已被封禁");
}
// 真正的业务逻辑从这里才开始...改个权限规则?得把十几个方法翻一遍。漏改一个?线上事故。
后来我把项目的鉴权层换成了 Sa-Token 的注解模式, 上面那坨代码直接变成方法头上一个 @SaCheckRole("admin"),说实话改完那一刻确实爽到了。
今天就把 Sa-Token 注解鉴权这块整理一下,我自己踩过的坑也一并说了。
这一步我必须放在最前面说,因为我当时就栽在这里——注解写得整整齐齐,一测试发现谁都能访问,排查了半小时才发现拦截器没注册。
Sa-Token 的注解鉴权依赖全局拦截器,但它默认是关的。你得手动加一个配置类:
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}就这么几行,但没它注解全是摆设。另外注意这个类要能被 Spring 扫到,如果你项目的包结构比较奇怪(比如配置类不在启动类同级或子包下),记得检查一下 @ComponentScan。

直接列一下,后面挨个说:
@SaCheckLogin —— 登录校验@SaCheckRole("admin") —— 角色校验@SaCheckPermission("user:add") —— 权限校验@SaCheckSafe —— 二级认证校验@SaCheckHttpBasic —— HttpBasic 认证@SaCheckHttpDigest —— HttpDigest 认证@SaCheckDisable("comment") —— 账号服务封禁校验@SaCheckSign —— API 签名校验@SaIgnore —— 忽略校验看着多,其实日常用得最频繁的就前三个加一个 @SaIgnore,其余的看业务需要。
// 登录才能访问
@SaCheckLogin
@RequestMapping("info")
public String info() {
return "查询用户信息";
}
// 必须有 super-admin 角色
@SaCheckRole("super-admin")
@RequestMapping("add")
public String add() {
return "用户增加";
}
// 必须有 user-add 权限
@SaCheckPermission("user-add")
@RequestMapping("add")
public String add() {
return "用户增加";
}
// 二级认证(比如修改密码前要再验证一次身份)
@SaCheckSafe()
@RequestMapping("updatePwd")
public String updatePwd() {
return "修改密码";
}
// Http Basic 认证,适合内部管理接口
@SaCheckHttpBasic(account = "sa:123456")
@RequestMapping("dashboard")
public String dashboard() {
return "管理后台";
}
// Http Digest 认证
@SaCheckHttpDigest(value = "sa:123456")
@RequestMapping("report")
public String report() {
return "报表数据";
}
// 校验账号是否被封禁了 comment 服务
@SaCheckDisable("comment")
@RequestMapping("send")
public String send() {
return "发表评论";
}这些注解都可以直接加在类上,效果是该类所有方法统一生效。我一般会在 Controller 类上加 @SaCheckLogin,然后个别不需要登录的方法单独用 @SaIgnore 放行,省事。
实际项目里权限很少是单一的。比如"有 user-add 或者 user-all 权限的人都能新增用户",这时候用 SaMode.OR:
@RequestMapping("atJurOr")
@SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)
public SaResult atJurOr() {
return SaResult.data("用户信息");
}默认模式是 SaMode.AND,就是必须全部具备。说实话大多数场景用 OR 更多一些,AND 反而少,不过这个看具体的权限模型设计。
这是我觉得 Sa-Token 设计得比较巧的一个点。
实际业务里经常遇到这种需求:普通用户需要 user.add 权限才能操作,但 admin 角色直接放行。以前我得在代码里写 if hasPermission || hasRole,现在一个 orRole 搞定:
@RequestMapping("userAdd")
@SaCheckPermission(value = "user.add", orRole = "admin")
public SaResult userAdd() {
return SaResult.data("用户信息");
}先校验权限,没过的话再看角色,有一个满足就放行。
orRole 有个细节容易搞混,我单独说一下:
orRole = "admin" —— 单个角色orRole = {"admin", "manager", "staff"} —— 数组写法,有其中一个角色就行orRole = {"admin, manager, staff"} —— 注意这是一个字符串里用逗号分隔,意思变成了必须同时具备这三个角色第三种写法我第一次看源码才搞明白,感觉不太直观,但确实能覆盖到"必须同时拥有多个角色"的场景,算是一种约定吧。
大部分 Controller 我会整体打上 @SaCheckLogin,但总有几个接口要对外开放,比如健康检查、公开查询之类的:
@SaCheckLogin
@RestController
public class TestController {
// 这些方法都需要登录...
// 但这个接口游客也能访问
@SaIgnore
@RequestMapping("getList")
public SaResult getList() {
// ...
return SaResult.ok();
}
}有几点需要注意的:
@SaIgnore 优先级最高。就算你同时写了 @SaCheckLogin 和 @SaIgnore,结果是不鉴权。我有个同事在方法上同时写了这俩,然后困惑为什么登录校验不生效,查了挺久。当你需要"满足以下任意一种条件就放行"这种复杂逻辑时,@SaCheckOr 就登场了:
@SaCheckOr(
login = @SaCheckLogin,
role = @SaCheckRole("admin"),
permission = @SaCheckPermission("user.add"),
safe = @SaCheckSafe("update-password"),
httpBasic = @SaCheckHttpBasic(account = "sa:123456"),
disable = @SaCheckDisable("submit-orders")
)
@RequestMapping("test")
public SaResult test() {
return SaResult.ok();
}上面这个例子比较夸张,实际很少会同时写这么多。但它表达的意思很清楚:这些条件里随便满足一个就能进来。
如果你的项目有多账号体系(比如后台账号和用户账号分开维护),可以这么写:
@SaCheckOr(
login = { @SaCheckLogin(type = "login"), @SaCheckLogin(type = "user") }
)
@RequestMapping("test")
public SaResult test() {
return SaResult.ok();
}后台登录了能访问,普通用户登录了也能访问,两套账号体系任一通过即可。
有人可能会问:有 OR 为什么没有 AND? 其实不需要,你直接在方法上叠多个注解就是 AND 效果:
@SaCheckLogin
@SaCheckRole("admin")
@SaCheckPermission("user.add")
@RequestMapping("test")
public SaResult test() {
return SaResult.ok();
}三个注解必须同时满足,天然就是 AND。这个设计我觉得挺合理的,没搞多余的东西。
另外 @SaCheckOr 还有个 append 字段,可以塞扩展包里的注解:
@RequestMapping("/test")
@SaCheckOr(login = @SaCheckLogin, append = { SaCheckApiKey.class })
@SaCheckApiKey
public SaResult test() {
return SaResult.ok();
}这个我用得少,但如果你自己扩展了鉴权注解,这个口子是留好了的。
用了注解鉴权之后最直观的感受就是——Controller 代码终于可以只关注业务了。权限规则一目了然地挂在方法头上,改权限不用深入方法体,code review 的时候也能快速看出每个接口的访问控制策略。
当然注解鉴权不是银弹。如果你的鉴权逻辑特别动态(比如权限规则从数据库实时查询、带各种条件分支),纯注解可能不够用,还是得配合编程式鉴权一起来。
我自己项目里用得最多的是 @SaCheckLogin + @SaCheckPermission,基本能覆盖 80% 的场景。@SaIgnore 作为白名单补充,偶尔用用 @SaCheckOr 处理复杂情况。
如果想更深入了解,这两个方向可以看看:
[1] AOP 注解鉴权: https://sa-token.cc/doc.html#/plugin/aop-at
[2] 自定义注解: https://sa-token.cc/doc.html#/fun/custom-annotations
如果这篇文章帮到了你,不妨点个分享给同样需要的朋友吧! 你的每一次支持,都是我持续创作的动力!💪