权限框架之Sa-Token基础讲解

1 Sa-Token

1.1 引言

之前进行鉴权、授权都要写一大堆代码。如果使用像Spring Security这样的框架,又要花好多时间学习,拿过来一用,好多配置项也不知道是干嘛用的,又不想了解。要是不用Spring Security,token的生成、校验、刷新,权限的验证分配,又全要自己写,想想都头大。
Spring Security太重而且配置繁琐。自己实现所有的点必须又要顾及到,更是麻烦。

点击了解 SpringSecurity和JWT实现认证和授权
新旧版本SpringSecurity使用对比

最近看到一个权限认证框架,真是够简单高效。这里分享一个使用Sa-Tokengateway鉴权demo。

1.2 简介

Sa-TokenSimple & All)是一个基于 Java 的权限认证框架,用于在 Web 和普通 Java 应用中进行身份认证和权限控制。它的设计理念是简单易用、功能全面,适用于各种场景。
官方地址:https://sa-token.cc/
Sa-Token 的一些主要特性:

  • 简单易用: 提供了简洁的 API,易于学习和使用。
  • 全面功能: 支持身份认证、权限控制、单点登录、会话管理等多种功能。
  • 支持多种存储方式: 可以选择内存存储、数据库存储等多种方式存储会话信息。
  • 适用于 Web 和非 Web 环境: 既可以在 Web 框架(如 Spring Boot、Spring MVC)中使用,也可以在普通 Java 应用中使用。
  • 支持多种身份验证方式: 支持账号密码登录、Token 登录、微信登录等多种身份验证方式。

1.3 简单操作

示例以springboot集成

1.3.1 pom.xml

<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.37.0</version>
</dependency>

注意:如果使用的是 SpringBoot 3.x,只需要将 sa-token-spring-boot-starter 修改为 sa-token-spring-boot3-starter 即可。

1.3.2 配置文件

server:
    # 端口
    port: 8081
    
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
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: true
    # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
    token-style: uuid
    # 是否输出操作日志 
    is-log: true

1.3.3 controller层面

@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();
    }
    
}

1.4 登录注销相关方法

// 当前会话注销登录
StpUtil.logout();
// 获取当前会话是否已经登录,返回true=已登录,false=未登录
StpUtil.isLogin();
// 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.checkLogin();

会话查询

// 获取当前会话账号id, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.getLoginId();
// 类似查询API还有:
StpUtil.getLoginIdAsString();    // 获取当前会话账号id, 并转化为`String`类型
StpUtil.getLoginIdAsInt();       // 获取当前会话账号id, 并转化为`int`类型
StpUtil.getLoginIdAsLong();      // 获取当前会话账号id, 并转化为`long`类型
// ---------- 指定未登录情形下返回的默认值 ----------
// 获取当前会话账号id, 如果未登录,则返回 null 
StpUtil.getLoginIdDefaultNull();
// 获取当前会话账号id, 如果未登录,则返回默认值 (`defaultValue`可以为任意类型)
StpUtil.getLoginId(T defaultValue);

token 查询

// 获取当前会话的 token 值
StpUtil.getTokenValue();
// 获取当前`StpLogic`的 token 名称
StpUtil.getTokenName();
// 获取指定 token 对应的账号id,如果未登录,则返回 null
StpUtil.getLoginIdByToken(String tokenValue);
// 获取当前会话剩余有效期(单位:s,返回-1代表永久有效)
StpUtil.getTokenTimeout();
// 获取当前会话的 token 信息参数
StpUtil.getTokenInfo();

1.5 权限认证

1.5.1 思路

所谓权限认证,核心逻辑就是判断一个账号是否拥有指定权限:
有,就让你通过。
没有?那么禁止访问!
深入到底层数据中,就是每个账号都会拥有一组权限码集合,框架来校验这个集合中是否包含指定的权限码。

例如:当前账号拥有权限码集合 [“user-add”, “user-delete”, “user-get”],这时候我来校验权限 “user-update”,则其结果就是:验证失败,禁止访问。
所以现在问题的核心就是两个:

如何获取一个账号所拥有的权限码集合?
本次操作需要验证的权限码是哪个?

1.5.2 获取当前账号权限码集合

因为每个项目的需求不同,其权限设计也千变万化,因此 [ 获取当前账号权限码集合 ] 这一操作不可能内置到框架中, 所以 Sa-Token 将此操作以接口的方式暴露给你,以方便你根据自己的业务逻辑进行重写。

新建一个类,实现 StpInterface接口,例如以下代码:

/**
 * 自定义权限加载接口实现类
 */
@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;
    }

}

1.5.3 权限校验

使用以下 api 来鉴权了

// 获取:当前账号所拥有的权限集合
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");    

1.5.4 角色校验

在 Sa-Token 中,角色和权限可以分开独立验证

// 获取:当前账号所拥有的角色集合
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.5.5 权限通配符

Sa-Token允许根据通配符指定泛权限,例如当一个账号拥有art.*的权限时,art.add、art.delete、art.update都将匹配通过

// 当拥有 art.* 权限时
StpUtil.hasPermission("art.add");        // true
StpUtil.hasPermission("art.update");     // true
StpUtil.hasPermission("goods.add");      // false

// 当拥有 *.delete 权限时
StpUtil.hasPermission("art.delete");      // true
StpUtil.hasPermission("user.delete");     // true
StpUtil.hasPermission("user.update");     // false

// 当拥有 *.js 权限时
StpUtil.hasPermission("index.js");        // true
StpUtil.hasPermission("index.css");       // false
StpUtil.hasPermission("index.html");      // false

1.6 踢人下线

所谓踢人下线,核心操作就是找到指定 loginId 对应的 Token,并设置其失效。

强制注销

StpUtil.logout(10001);                    // 强制指定账号注销下线 
StpUtil.logout(10001, "PC");              // 强制指定账号指定端注销下线 
StpUtil.logoutByTokenValue("token");      // 强制指定 Token 注销下线 

踢人下线

StpUtil.kickout(10001);                    // 将指定账号踢下线 
StpUtil.kickout(10001, "PC");              // 将指定账号指定端踢下线
StpUtil.kickoutByTokenValue("token");      // 将指定 Token 踢下线

强制注销 和 踢人下线 的区别在于:

  • 强制注销等价于对方主动调用了注销方法,再次访问会提示Token无效。
  • 踢人下线不会清除Token信息,而是将其打上特定标记,再次访问会提示Token已被踢下线。

1.7 注解鉴权

1.7.1 介绍

  • @SaCheckLogin: 登录校验 —— 只有登录之后才能进入该方法。
  • @SaCheckRole("admin"): 角色校验 —— 必须具有指定角色标识才能进入该方法。
  • @SaCheckPermission("user:add"): 权限校验 —— 必须具有指定权限才能进入该方法。
  • @SaCheckSafe: 二级认证校验 —— 必须二级认证之后才能进入该方法。
  • @SaCheckBasic: HttpBasic校验 —— 只有通过 Basic 认证后才能进入该方法。
  • @SaIgnore:忽略校验 —— 表示被修饰的方法或类无需进行注解鉴权和路由拦截器鉴权。
    • 修饰方法时代表这个方法可以被游客访问,修饰类时代表这个类中的所有接口都可以游客访问。
    • 具有最高优先级,当 @SaIgnore 和其它鉴权注解一起出现时,其它鉴权注解都将被忽略。
    • 同样可以忽略掉 Sa-Token 拦截器中的路由鉴权
  • @SaCheckDisable("comment"):账号服务封禁校验 —— 校验当前账号指定服务是否被封禁。

注意:以上注解都可以加在类上,代表为这个类所有方法进行鉴权

Sa-Token 使用全局拦截器完成注解鉴权功能,为了不为项目带来不必要的性能负担,拦截器默认处于关闭状态,使用拦截器模式,只能在Controller层进行注解鉴权 ,因此,为了使用注解鉴权,必须手动将 Sa-Token 的全局拦截器注册到项目中

1.7.2 注册拦截器

以SpringBoot2.0为例,新建配置类SaTokenConfigure.java

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    // 注册 Sa-Token 拦截器,打开注解式鉴权功能 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器,打开注解式鉴权功能 
        registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");    
    }
}

注意:使用拦截器模式,只能在Controller层进行注解鉴权

1.7.3 设定校验模式

@SaCheckRole@SaCheckPermission注解可设置校验模式,例如:

// 注解式鉴权:只要具有其中一个权限即可通过校验 
@RequestMapping("atJurOr")
@SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)        
public SaResult atJurOr() {
    return SaResult.data("用户信息");
}

mode有两种取值:

  • SaMode.AND:标注一组权限,会话必须全部具有才可通过校验。
  • SaMode.OR:标注一组权限,会话只要具有其一即可通过校验。

1.7.4 角色权限双重 or校验

假设有以下业务场景:一个接口在具有权限 user.add 或角色 admin 时可以调通。怎么写?

// 角色权限双重 “or校验”:具备指定权限或者指定角色即可通过校验
@RequestMapping("userAdd")
@SaCheckPermission(value = "user.add", orRole = "admin")        
public SaResult userAdd() {
    return SaResult.data("用户信息");
}

orRole 字段代表权限校验未通过时的次要选择,两者只要其一校验成功即可进入请求方法,其有三种写法:

  • rRole = "admin":代表需要拥有角色 admin 。
  • orRole = {"admin", "manager", "staff"}:代表具有三个角色其一即可。
  • orRole = {"admin, manager, staff"}:代表必须同时具有三个角色。

1.7.5 批量注解鉴权

使用 @SaCheckOr 表示批量注解鉴权:

// 在 `@SaCheckOr` 中可以指定多个注解,只要当前会话满足其中一个注解即可通过验证,进入方法。
@SaCheckOr(
        login = @SaCheckLogin,
        role = @SaCheckRole("admin"),
        permission = @SaCheckPermission("user.add"),
        safe = @SaCheckSafe("update-password"),
        basic = @SaCheckBasic(account = "sa:123456"),
        disable = @SaCheckDisable("submit-orders")
)
@RequestMapping("test")
public SaResult test() {
    // ... 
    return SaResult.ok(); 
}

每一项属性都可以写成数组形式,例如:

// 当前客户端只要有 [ login 账号登录] 或者 [user 账号登录] 其一,就可以通过验证进入方法。
// 注意:`type = "login"` 和 `type = "user"` 是多账号模式章节的扩展属性,此处你可以先略过这个知识点。
@SaCheckOr(
    login = { @SaCheckLogin(type = "login"), @SaCheckLogin(type = "user") }
)
@RequestMapping("test")
public SaResult test() {
    // ... 
    return SaResult.ok(); 
}

疑问:既然有了 @SaCheckOr,为什么没有与之对应的 @SaCheckAnd 呢?
因为当写多个注解时,其天然就是 and 校验关系,例如:

// 当你在一个方法上写多个注解鉴权时,其默认就是要满足所有注解规则后,才可以进入方法,只要有一个不满足,就会抛出异常
@SaCheckLogin
@SaCheckRole("admin")
@SaCheckPermission("user.add")
@RequestMapping("test")
public SaResult test() {
    // ... 
    return SaResult.ok(); 
}

1.7.6 关闭注解校验

SaInterceptor 只要注册到项目中,默认就会打开注解校验,如果要关闭此能力,需要:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(
        new SaInterceptor(handle -> {
            SaRouter.match("/**").check(r -> StpUtil.checkLogin());
        }).isAnnotation(false)  // 指定关闭掉注解鉴权能力,这样框架就只会做路由拦截校验了 
    ).addPathPatterns("/**");
}

1.8 路由拦截

假设我们有如下需求:

项目中所有接口均需要登录认证,只有 “登录接口” 本身对外开放
我们怎么实现呢?给每个接口加上鉴权注解?手写全局拦截器?似乎都不是非常方便。

在这个需求中我们真正需要的是一种基于路由拦截的鉴权模式,那么在Sa-Token怎么实现路由拦截鉴权呢?

1.8.1 注册 Sa-Token 路由拦截器

以SpringBoot2.0为例,新建配置类SaTokenConfigure.java

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    // 注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                .addPathPatterns("/**")
                .excludePathPatterns("/user/doLogin"); 
    }
}

以上代码,我们注册了一个基于 StpUtil.checkLogin() 的登录校验拦截器,并且排除了/user/doLogin接口用来开放登录(除了/user/doLogin以外的所有接口都需要登录才能访问)

1.8.2 校验函数详解

自定义认证规则:new SaInterceptor(handle -> StpUtil.checkLogin()) 是最简单的写法,代表只进行登录校验功能。
我们可以往构造函数塞一个完整的 lambda 表达式,来定义详细的校验规则,例如:

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器,定义详细认证规则 
        registry.addInterceptor(new SaInterceptor(handler -> {
            // 指定一条 match 规则
            SaRouter
                .match("/**")    // 拦截的 path 列表,可以写多个 */
                .notMatch("/user/doLogin")        // 排除掉的 path 列表,可以写多个 
                .check(r -> StpUtil.checkLogin());        // 要执行的校验动作,可以写完整的 lambda 表达式
                
            // 根据路由划分模块,不同模块不同鉴权 
            SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
            SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
            SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
            SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
            SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
            SaRouter.match("/comment/**", r -> StpUtil.checkPermission("comment"));
        })).addPathPatterns("/**");
    }
}

SaRouter.match() 匹配函数有两个参数:要匹配的path路由,要执行的校验函数。
在校验函数内不只可以使用 StpUtil.checkPermission("xxx") 进行权限校验,还可以写任意代码,例如:

// 甚至你可以随意的写一个打印语句
SaRouter.match("/**", r -> System.out.println("----啦啦啦----"));

// 连缀写法
SaRouter.match("/**").check(r -> System.out.println("----啦啦啦----"));

1.8.3 匹配特征详解

除了上述示例的 path 路由匹配,还可以根据很多其它特征进行匹配,以下是所有可匹配的特征:

// 基础写法样例:匹配一个path,执行一个校验函数 
SaRouter.match("/user/**").check(r -> StpUtil.checkLogin());

// 根据 path 路由匹配   ——— 支持写多个path,支持写 restful 风格路由 
// 功能说明: 使用 /user , /goods 或者 /art/get 开头的任意路由都将进入 check 方法
SaRouter.match("/user/**", "/goods/**", "/art/get/{id}").check( /* 要执行的校验函数 */ );

// 根据 path 路由排除匹配 
// 功能说明: 使用 .html , .css 或者 .js 结尾的任意路由都将跳过, 不会进入 check 方法
SaRouter.match("/**").notMatch("*.html", "*.css", "*.js").check( /* 要执行的校验函数 */ );

// 根据请求类型匹配 
SaRouter.match(SaHttpMethod.GET).check( /* 要执行的校验函数 */ );

// 根据一个 boolean 条件进行匹配 
SaRouter.match( StpUtil.isLogin() ).check( /* 要执行的校验函数 */ );

// 根据一个返回 boolean 结果的lambda表达式匹配 
SaRouter.match( r -> StpUtil.isLogin() ).check( /* 要执行的校验函数 */ );

// 多个条件一起使用 
// 功能说明: 必须是 Get 请求 并且 请求路径以 `/user/` 开头 
SaRouter.match(SaHttpMethod.GET).match("/user/**").check( /* 要执行的校验函数 */ );

// 可以无限连缀下去 
// 功能说明: 同时满足 Get 方式请求, 且路由以 /admin 开头, 路由中间带有 /send/ 字符串, 路由结尾不能是 .js 和 .css
SaRouter
    .match(SaHttpMethod.GET)
    .match("/admin/**")
    .match("/**/send/**") 
    .notMatch("/**/*.js")
    .notMatch("/**/*.css")
    // ....
    .check( /* 只有上述所有条件都匹配成功,才会执行最后的check校验函数 */ );

1.8.4 提前退出匹配链

使用 SaRouter.stop() 可以提前退出匹配链,例:

registry.addInterceptor(new SaInterceptor(handler -> {
    SaRouter.match("/**").check(r -> System.out.println("进入1"));
    SaRouter.match("/**").check(r -> System.out.println("进入2")).stop();
    SaRouter.match("/**").check(r -> System.out.println("进入3"));
    SaRouter.match("/**").check(r -> System.out.println("进入4"));
    SaRouter.match("/**").check(r -> System.out.println("进入5"));
})).addPathPatterns("/**");

如上示例,代码运行至第2条匹配链时,会在stop函数处提前退出整个匹配函数,从而忽略掉剩余的所有match匹配
除了stop()函数,SaRouter还提供了 back() 函数,用于:停止匹配,结束执行,直接向前端返回结果

// 执行back函数后将停止匹配,也不会进入Controller,而是直接将 back参数 作为返回值输出到前端
SaRouter.match("/user/back").back("要返回到前端的内容");

stop() 与 back() 函数的区别在于:

  • SaRouter.stop():会停止匹配,进入Controller。
  • SaRouter.back():会停止匹配,直接返回结果到前端。

1.8.5 使用free打开一个独立的作用域

// 进入 free 独立作用域 
SaRouter.match("/**").free(r -> {
    SaRouter.match("/a/**").check(/* --- */);
    SaRouter.match("/b/**").check(/* --- */).stop();
    SaRouter.match("/c/**").check(/* --- */);
});

// 执行 stop() 函数跳出 free 后继续执行下面的 match 匹配 
SaRouter.match("/**").check(/* --- */);

free() 的作用是:打开一个独立的作用域,使内部的 stop() 不再一次性跳出整个 Auth 函数,而是仅仅跳出当前 free 作用域。

1.8.6 使用注解忽略掉路由拦截校验

我们可以使用 @SaIgnore 注解,忽略掉路由拦截认证:
在 Controller 里添加了忽略校验的注解

@SaIgnore
@RequestMapping("/user/getList")
public SaResult getList() {
    System.out.println("------------ 访问进来方法"); 
    return SaResult.ok(); 
}

请求将会跳过拦截器的校验,直接进入 Controller 的方法中
注意:此注解的忽略效果只针对 SaInterceptor拦截器 和 AOP注解鉴权 生效,对自定义拦截器与过滤器不生效。

1.9 Session

1.9.1 简介

Session 是会话中专业的数据缓存组件,通过 Session 我们可以很方便的缓存一些高频读写数据,提高程序性能,例如:

// 在登录时缓存 user 对象 
StpUtil.getSession().set("user", user);

// 然后我们就可以在任意处使用这个 user 对象
SysUser user = (SysUser) StpUtil.getSession().get("user");

Sa-Token 中,Session 分为三种,分别是:

  • Account-Session: 指的是框架为每个 账号id 分配的 Session
  • Token-Session: 指的是框架为每个 token 分配的 Session
  • Custom-Session: 指的是以一个 特定的值 作为SessionId,来分配的 Session

1.9.2 Account-Session

有关 账号-Session 的 API 如下:

// 获取当前账号 id 的 Account-Session (必须是登录后才能调用)
StpUtil.getSession();
// 获取当前账号 id 的 Account-Session, 并决定在 Session 尚未创建时,是否新建并返回
StpUtil.getSession(true);
// 获取账号 id 为 10001 的 Account-Session
StpUtil.getSessionByLoginId(10001);
// 获取账号 id 为 10001 的 Account-Session, 并决定在 Session 尚未创建时,是否新建并返回
StpUtil.getSessionByLoginId(10001, true);
// 获取 SessionId 为 xxxx-xxxx 的 Account-Session, 在 Session 尚未创建时, 返回 null 
StpUtil.getSessionBySessionId("xxxx-xxxx");
复制到剪贴板错误复制成功
Token-Session
有关 令牌-Session 的 API 如下:
// 获取当前 Token 的 Token-Session 对象
StpUtil.getTokenSession();
// 获取指定 Token 的 Token-Session 对象
StpUtil.getTokenSessionByToken(token);

1.9.3 Custom-Session

自定义 Session 指的是以一个特定的值作为 SessionId 来分配的Session, 借助自定义Session,可以为系统中的任意元素分配相应的session
例如以商品 id 作为 key 为每个商品分配一个Session,以便于缓存和商品相关的数据,其相关API如下:

// 查询指定key的Session是否存在
SaSessionCustomUtil.isExists("goods-10001");
// 获取指定key的Session,如果没有,则新建并返回
SaSessionCustomUtil.getSessionById("goods-10001");
// 获取指定key的Session,如果没有,第二个参数决定是否新建并返回  
SaSessionCustomUtil.getSessionById("goods-10001", false);   
// 删除指定key的Session
SaSessionCustomUtil.deleteSessionById("goods-10001");

1.9.4 在 Session 上存取值

以上三种 Session 均为框架设计概念上的区分,实际上在获取它们时,返回的都是 SaSession 对象,你可以使用以下 API 在 SaSession 对象上存取值:

// 写值 
session.set("name", "zhang"); 
// 写值 (只有在此key原本无值的时候才会写入)
session.setDefaultValue("name", "zhang");
// 取值
session.get("name");
// 取值 (指定默认值)
session.get("name", "<defaultValue>"); 
// 取值 (若无值则执行参数方法, 之后将结果保存到此键名下,并返回此结果   若有值则直接返回, 无需执行参数方法)
session.get("name", () -> {
            return ...;
        });
// ---------- 数据类型转换: ----------
session.getInt("age");         // 取值 (转int类型)
session.getLong("age");        // 取值 (转long类型)
session.getString("name");     // 取值 (转String类型)
session.getDouble("result");   // 取值 (转double类型)
session.getFloat("result");    // 取值 (转float类型)
session.getModel("key", Student.class);     // 取值 (指定转换类型)
session.getModel("key", Student.class, <defaultValue>);  // 取值 (指定转换类型, 并指定值为Null时返回的默认值)
// 是否含有某个key (返回 true 或 false)
session.has("key"); 
// 删值 
session.delete('name');          
// 清空所有值 
session.clear();                 
// 获取此 Session 的所有key (返回Set<String>)
session.keys();      

1.10 框架配置

可以零配置启动框架,但同时也可以通过一定的参数配置,定制性使用框架,Sa-Token支持多种方式配置框架信息

1.10.1 配置

1.10.1.1 yml配置
yaml 风格
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
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: true
    # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
    token-style: uuid
    # 是否输出操作日志 
    is-log: true
1.10.1.2 通过代码配置

模式 1

/**
 * Sa-Token 配置类
 */
@Configuration
public class SaTokenConfigure {
    // Sa-Token 参数配置,参考文档:https://sa-token.cc
    // 此配置会覆盖 application.yml 中的配置
    @Bean
    @Primary
    public SaTokenConfig getSaTokenConfigPrimary() {
        SaTokenConfig config = new SaTokenConfig();
        config.setTokenName("satoken");             // token 名称(同时也是 cookie 名称)
        config.setTimeout(30 * 24 * 60 * 60);       // token 有效期(单位:秒),默认30天,-1代表永不过期 
        config.setActiveTimeout(-1);              // token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
        config.setIsConcurrent(true);               // 是否允许同一账号多地同时登录(为 true 时允许一起登录,为 false 时新登录挤掉旧登录)
        config.setIsShare(true);                    // 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token,为 false 时每次登录新建一个 token)
        config.setTokenStyle("uuid");               // token 风格
        config.setIsLog(false);                     // 是否输出操作日志 
        return config;
    }
}

模式 2

/**
 * Sa-Token 配置类
 */
@Configuration
public class SaTokenConfigure {
    // Sa-Token 参数配置,参考文档:https://sa-token.cc
    // 此配置会与 application.yml 中的配置合并 (代码配置优先)
    @Autowired
    public void configSaToken(SaTokenConfig config) {
        config.setTokenName("satoken");             // token 名称(同时也是 cookie 名称)
        config.setTimeout(30 * 24 * 60 * 60);       // token 有效期(单位:秒),默认30天,-1代表永不过期 
        config.setActiveTimeout(-1);              // token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
        config.setIsConcurrent(true);               // 是否允许同一账号多地同时登录(为 true 时允许一起登录,为 false 时新登录挤掉旧登录)
        config.setIsShare(true);                    // 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token,为 false 时每次登录新建一个 token)
        config.setTokenStyle("uuid");               // token 风格
        config.setIsLog(false);                     // 是否输出操作日志 
    }
}

两者的区别在于:

  • 模式 1 会覆盖 application.yml 中的配置。
  • 模式 2 会与 application.yml 中的配置合并(代码配置优先)。

1.10.2 配置项

参数名称类型默认值说明
tokenNameStringsatokenToken 名称 (同时也是 Cookie 名称、数据持久化前缀)
timeoutlong2592000Token 有效期(单位:秒),默认30天,-1代表永不过期
activeTimeoutlong-1Token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结(例如可以设置为1800代表30分钟内无操作就冻结)
dynamicActiveTimeoutBooleanfalse是否启用动态 activeTimeout 功能,如不需要请设置为 false,节省缓存请求次数
isConcurrentBooleantrue是否允许同一账号并发登录 (为 true 时允许一起登录,为 false 时新登录挤掉旧登录)
isShareBooleantrue在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token,为 false 时每次登录新建一个 token)
maxLoginCountint12同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true,isShare=false 时此配置才有效)
maxTryTimesint12在每次创建 Token 时的最高循环次数,用于保证 Token 唯一性(-1=不循环重试,直接使用)
isReadBodyBooleantrue是否尝试从 请求体 里读取 Token
isReadHeaderBooleantrue是否尝试从 header 里读取 Token
isReadCookieBooleantrue是否尝试从 cookie 里读取 Token,此值为 false 后,StpUtil.login(id) 登录时也不会再往前端注入Cookie
isWriteHeaderBooleanfalse是否在登录后将 Token 写入到响应头
tokenStyleStringuuid token风格
dataRefreshPeriodint30默认数据持久组件实现类中,每次清理过期数据间隔的时间 (单位: 秒) ,默认值30秒,设置为-1代表不启动定时清理
tokenSessionCheckLoginBooleantrue获取 Token-Session 时是否必须登录 (如果配置为true,会在每次获取 Token-Session 时校验是否登录)
autoRenewBooleantrue是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作)
tokenPrefixStringnulltoken前缀,例如填写 Bearer 实际传参 satoken: Bearer xxxx-xxxx-xxxx-xxxx
isPrintBooleantrue 是否在初始化配置时打印版本字符画
isLogBooleanfalse是否打印操作日志
logLevelStringtrace日志等级(trace、debug、info、warn、error、fatal),此值与 logLevelInt 联动
logLevelIntint1 日志等级 int 值(1=trace、2=debug、3=info、4=warn、5=error、6=fatal),此值与 logLevel 联动
isColorLogBooleannull是否打印彩色日志,true=打印彩色日志,false=打印黑白日志,null=框架根据运行终端自行判断是否打印彩色日志
jwtSecretKeyStringnulljwt秘钥 (只有集成 sa-token-temp-jwt 模块时此参数才会生效)
sameTokenTimeoutlong 86400Same-Token的有效期 (单位: 秒)
basicString“”Http Basic 认证的账号和密码
currDomainStringnull配置当前项目的网络访问地址
checkSameTokenBooleanfalse是否校验Same-Token(部分rpc插件有效)
cookieObjectnew SaCookieConfig()Cookie配置对象

Cookie相关配置:

参数名称类型默认值说明
domainStringnull作用域(写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景)
pathString/路径,默认写在域名根路径下
secureBooleanfalse是否只在 https 协议下有效
httpOnlyBooleanfalse是否禁止 js 操作 Cookie
sameSiteStringLax第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)

Cookie 配置示例:

# Sa-Token 配置
sa-token: 
    # Cookie 相关配置 
    cookie: 
        domain: stp.com
        path: /
        secure: false
        httpOnly: true
        sameSite: Lax
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值