一:Sa-Token介绍
Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证 、权限认证 、单点登录 、OAuth2.0 、分布式Session会话 、微服务网关鉴权 等一系列权限相关问题。

Sa-Token 的轻量级就体现在可以用极少量的代码帮你完成想要的功能:
java
// 会话登录,参数填登录人的账号id
StpUtil.login(10001);
无需实现任何接口,无需创建任何配置文件,只需要这一句静态代码的调用,便可以完成会话登录认证。
如果一个接口需要登录后才能访问,我们只需调用以下代码:
java
// 校验当前客户端是否已经登录,如果未登录则抛出 `NotLoginException` 异常
StpUtil.checkLogin();
权限认证:
java
// 注解鉴权:只有具备 `user:add` 权限的会话才可以进入方法
@SaCheckPermission("user:add")
public String insert(SysUser user) {
// ...
return "用户增加";
}
知道了Sa-Token的强大,接下来就让我们一起学习它吧。本篇先介绍Sa-Token的基础用法。
二:在SpringBoot中集成Sa-Token
1、创建项目
在 IDE 中新建一个 SpringBoot 项目。
2、添加依赖
java
<!-- Sa-Token 权限认证 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.45.0</version>
</dependency>
- 如果你使用的
SpringBoot 3.x,请引入sa-token-spring-boot3-starter。 - 如果你使用的
SpringBoot 4.x,请引入sa-token-spring-boot4-starter。
3、配置文件
你可以零配置启动项目 ,但同时你也可以在 application.yml 中增加如下配置,定制性使用框架:
java
server:
# 端口
port: 8081
############## Sa-Token 配置 ##############
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true
4、创建启动类
java
@SpringBootApplication
public class SaTokenDemoApplication {
public static void main(String[] args) throws JsonProcessingException {
SpringApplication.run(SaTokenDemoApplication.class, args);
System.out.println("启动成功,Sa-Token 配置如下:" + SaManager.getConfig());
}
}
5、创建测试Controller
java
@RestController
@RequestMapping("/user/")
public class UserController {
// 测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456
@RequestMapping("doLogin")
public String doLogin(String username, String password) {
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
if("zhang".equals(username) && "123456".equals(password)) {
StpUtil.login(10001);
return "登录成功";
}
return "登录失败";
}
// 查询登录状态,浏览器访问: http://localhost:8081/user/isLogin
@RequestMapping("isLogin")
public String isLogin() {
return "当前会话是否登录:" + StpUtil.isLogin();
}
}
6、运行
启动代码,从浏览器依次访问上述测试接口:

三:登录认证
1、开始登录
无论用户采用何种登录方式,本质上都是通过提交一定的认证信息,使系统可以定位到 Ta 的唯一标识 ------ userId。
当我们拿到 userId 后,便可以调用框架提供的 API 进行登录:
java
// 会话登录:参数填写要登录的账号id,建议的数据类型:long | int | String, 不可以传入复杂类型,如:User、Admin 等等
StpUtil.login(Object userId);
只此一句代码,便可以使会话登录成功。实际上,Sa-Token 在背后做了大量的工作,包括但不限于:
- 检查此账号是否之前已有登录;
- 为账号生成 Token 凭证与 Session 会话;
- 记录 Token 活跃时间;
- 通知全局侦听器,xx 账号登录成功;
- 检查此账号登录数量是否已达上限;
- 将 Token 注入到请求上下文;
- 等等其它工作......
你暂时不需要完整了解完整过程,你只需要记住关键一点:Sa-Token 为这个账号创建了一个 token 凭证,且通过 Cookie 上下文返回给了前端。
所以一般情况下,我们的登录接口代码,会大致类似如下:
java
// 会话登录接口
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
// 第一步:比对前端提交的账号名称、密码
if("zhang".equals(name) && "123456".equals(pwd)) {
// 第二步:根据账号id,进行登录
StpUtil.login(10001);
return SaResult.ok("登录成功");
}
return SaResult.error("登录失败");
}
如果你对以上代码阅读没有压力,你可能会注意到略显奇怪的一点:此处仅仅做了会话登录,但并没有主动向前端返回 token 信息。为什么呢??
因为StpUtil.login(id) 方法利用了 Cookie 自动注入的特性,省略了你手写返回 token 的代码。
- Cookie 可以从后端控制往浏览器中写入 token 值。
- Cookie 会在前端每次发起请求时自动提交 token 值。
因此,在 Cookie 功能的加持下,我们可以仅靠 StpUtil.login(id) 一句代码就完成登录认证。
2、校验登录
使用以下方法判断当前会话是否已登录:
java
// 判断当前会话是否已经登录,返回 true=已登录,false=未登录
StpUtil.isLogin();
// 检验当前会话是否已经登录, 如果已登录代码会安全通过,未登录则抛出异常:`NotLoginException`
StpUtil.checkLogin();
配合全局异常处理器,统一返回固定格式数据到前端:
java
@RestControllerAdvice
public class GlobalException {
@ExceptionHandler(NotLoginException.class)
public SaResult handlerException(NotLoginException e) {
return SaResult.error(e.getMessage());
}
}
3、会话注销
java
// 当前会话注销登录
StpUtil.logout();
四:权限认证
1、设计思路
权限认证的最终目的在于:规定哪些用户可以访问哪些 接口/页面/资源。
例如对于同一个页面:
- 管理员账号访问:正常返回数据。
- 普通账号访问:权限不足,拒绝访问。
那么框架是如何判断,一个账号是否有权限访问某个接口的呢?
从底层数据的角度来讲,每个账号都会拥有一组权限码集合,框架要做的就是校验这个集合中是否包含指定的权限码。
- 有,就让你通过。
- 没有?那么禁止访问!
所以现在问题的核心就是两个:
- 如何定义一个账号所拥有的权限码集合?
- 本次操作需要校验的权限码是哪个?
2、获取当前账号权限码集合
在进行具体的权限校验之前,你需要实现 StpInterface接口,告诉框架指定账号拥有的权限码集合是哪些:
java
/**
* 自定义权限加载接口实现类
*/
@Component // 保证此类被 SpringBoot 扫描,完成 Sa-Token 的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 本 list 仅做模拟,实际项目中要根据具体业务逻辑来查询权限
List<String> list = new ArrayList<String>();
list.add("101");
list.add("user.add");
list.add("user.update");
list.add("user.get");
// list.add("user.delete");
list.add("art.*");
return list;
}
/**
* 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 本 list 仅做模拟,实际项目中要根据具体业务逻辑来查询角色
List<String> list = new ArrayList<String>();
list.add("admin");
list.add("super-admin");
return list;
}
}
参数解释:
- loginId:账号id,即你在调用
StpUtil.login(id)时写入的唯一标识值。 - loginType:账号体系标识,此处可以暂时忽略,在 下个章节会对这个概念做详细的解释。
3、权限校验
java
// 获取:当前账号所拥有的权限集合
StpUtil.getPermissionList();
// 判断:当前账号是否含有指定权限, 返回 true 或 false
StpUtil.hasPermission("user.add");
// 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
StpUtil.checkPermission("user.add");
// 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
StpUtil.checkPermissionAnd("user.add", "user.delete", "user.get");
// 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");
4、角色校验
java
// 获取:当前账号所拥有的角色集合
StpUtil.getRoleList();
// 判断:当前账号是否拥有指定角色, 返回 true 或 false
StpUtil.hasRole("super-admin");
// 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
StpUtil.checkRole("super-admin");
// 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
StpUtil.checkRoleAnd("super-admin", "shop-admin");
// 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
StpUtil.checkRoleOr("super-admin", "shop-admin");
五:踢人下线
1、强制注销
java
StpUtil.logout(10001); // 强制指定账号注销下线
StpUtil.logout(10001, "PC"); // 强制指定账号指定端注销下线
StpUtil.logoutByTokenValue("token"); // 强制指定 Token 注销下线
2、踢人下线
java
StpUtil.kickout(10001); // 将指定账号踢下线
StpUtil.kickout(10001, "PC"); // 将指定账号指定端踢下线
StpUtil.kickoutByTokenValue("token"); // 将指定 Token 踢下线
强制注销 和 踢人下线 的区别在于:
- 强制注销等价于对方主动调用了注销方法,再次访问会提示:Token无效。
- 踢人下线不会清除Token信息,而是将其打上特定标记,再次访问会提示:Token已被踢下线。
六:注解鉴权
注解鉴权 ------ 优雅的将鉴权与业务代码分离!
@SaCheckLogin: 登录校验 ------ 只有登录之后才能进入该方法。@SaCheckRole("admin"): 角色校验 ------ 必须具有指定角色标识才能进入该方法。@SaCheckPermission("user:add"): 权限校验 ------ 必须具有指定权限才能进入该方法。@SaCheckSafe: 二级认证校验 ------ 必须二级认证之后才能进入该方法。@SaCheckHttpBasic: HttpBasic校验 ------ 只有通过 HttpBasic 认证后才能进入该方法。@SaCheckHttpDigest: HttpDigest校验 ------ 只有通过 HttpDigest 认证后才能进入该方法。@SaCheckDisable("comment"):账号服务封禁校验 ------ 校验当前账号指定服务是否被封禁。@SaCheckSign:API 签名校验 ------ 用于跨系统的 API 签名参数校验。@SaIgnore:忽略校验 ------ 表示被修饰的方法或类无需进行注解鉴权和路由拦截器鉴权。
Sa-Token 使用全局拦截器完成注解鉴权功能,为了不为项目带来不必要的性能负担,拦截器默认处于关闭状态。 因此,为了使用注解鉴权,你必须手动将 Sa-Token 的全局拦截器注册到你项目中。
1、注册拦截器
java
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}
2、使用注解鉴权
java
// 登录校验:只有登录之后才能进入该方法
@SaCheckLogin
@RequestMapping("info")
public String info() {
return "查询用户信息";
}
// 角色校验:必须具有指定角色才能进入该方法
@SaCheckRole("super-admin")
@RequestMapping("add")
public String add() {
return "用户增加";
}
// 权限校验:必须具有指定权限才能进入该方法
@SaCheckPermission("user-add")
@RequestMapping("add")
public String add() {
return "用户增加";
}
// 二级认证校验:必须二级认证之后才能进入该方法
@SaCheckSafe()
@RequestMapping("add")
public String add() {
return "用户增加";
}
// Http Basic 校验:只有通过 Http Basic 认证后才能进入该方法
@SaCheckHttpBasic(account = "sa:123456")
@RequestMapping("add")
public String add() {
return "用户增加";
}
// Http Digest 校验:只有通过 Http Digest 认证后才能进入该方法
@SaCheckHttpDigest(value = "sa:123456")
@RequestMapping("add")
public String add() {
return "用户增加";
}
// 校验当前账号是否被封禁 comment 服务,如果已被封禁会抛出异常,无法进入方法
@SaCheckDisable("comment")
@RequestMapping("send")
public String send() {
return "查询用户信息";
}
3、设定校验模式
@SaCheckRole与@SaCheckPermission注解可设置校验模式,例如:
java
// 注解式鉴权:只要具有其中一个权限即可通过校验
@RequestMapping("atJurOr")
@SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)
public SaResult atJurOr() {
return SaResult.data("用户信息");
}
mode有两种取值:
SaMode.AND,标注一组权限,会话必须全部具有才可通过校验。SaMode.OR,标注一组权限,会话只要具有其一即可通过校验。
4、忽略认证
使用 @SaIgnore 可表示一个接口忽略认证:
java
@SaCheckLogin
@RestController
public class TestController {
// ... 其它方法
// 此接口加上了 @SaIgnore 可以游客访问
@SaIgnore
@RequestMapping("getList")
public SaResult getList() {
// ...
return SaResult.ok();
}
}
如上代码表示:TestController 中的所有方法都需要登录后才可以访问,但是 getList 接口可以匿名游客访问。
- @SaIgnore 修饰方法时代表这个方法可以被游客访问,修饰类时代表这个类中的所有接口都可以游客访问。
- @SaIgnore 具有最高优先级,当 @SaIgnore 和其它鉴权注解一起出现时,其它鉴权注解都将被忽略。
- @SaIgnore 同样可以忽略掉 Sa-Token 拦截器中的路由鉴权,在下面的 [路由拦截鉴权] 章节中我们会讲到。