Sa-Token基础篇

一: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 在背后做了大量的工作,包括但不限于:

  1. 检查此账号是否之前已有登录;
  2. 为账号生成 Token 凭证与 Session 会话;
  3. 记录 Token 活跃时间;
  4. 通知全局侦听器,xx 账号登录成功;
  5. 检查此账号登录数量是否已达上限;
  6. 将 Token 注入到请求上下文;
  7. 等等其它工作......

你暂时不需要完整了解完整过程,你只需要记住关键一点: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、设计思路

权限认证的最终目的在于:规定哪些用户可以访问哪些 接口/页面/资源。

例如对于同一个页面:

  • 管理员账号访问:正常返回数据。
  • 普通账号访问:权限不足,拒绝访问。

那么框架是如何判断,一个账号是否有权限访问某个接口的呢?

从底层数据的角度来讲,每个账号都会拥有一组权限码集合,框架要做的就是校验这个集合中是否包含指定的权限码。

  • 有,就让你通过。
  • 没有?那么禁止访问!

所以现在问题的核心就是两个:

  1. 如何定义一个账号所拥有的权限码集合?
  2. 本次操作需要校验的权限码是哪个?

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 拦截器中的路由鉴权,在下面的 [路由拦截鉴权] 章节中我们会讲到。
相关推荐
2301_816374331 小时前
Nginx下构建PC站点
java·运维·nginx
无所事事O_o1 小时前
JAVA应用不定时卡顿问题排查过程记录
java·优化
覆东流1 小时前
第10天:python元组
开发语言·后端·python
万事大吉CC1 小时前
【5】Django 的模板语言:页面架构设计
后端·python·django
蝎子莱莱爱打怪2 小时前
用好CC,事半功倍!Claude Code 命令大全,黄金命令推荐、多模型配置、实践指南、Hooks 和踩坑记录大全
前端·人工智能·后端
幸福巡礼2 小时前
【LangChain 1.2 实战(八)】Agent Middleware 实战 —— 动态路由、监控、安全与容错
java·安全·langchain
Byron__2 小时前
Java JVM核心知识点复习笔记
java·jvm·笔记
星栈2 小时前
我用 Rust 给订单系统上了事件溯源
后端
程序员小白条2 小时前
别盲目卷算法!2026 程序员\&大学生,最稳的 AI 技术进阶路线全梳理
java·网络·人工智能·网络协议·http·面试