零、依赖引入
<!-- Sa-Token 权限认证 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.44.0</version>
</dependency>
<!-- Sa-Token 插件:整合SSO -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>1.44.0</version>
</dependency>
<!-- Sa-Token 整合 RedisTemplate -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template</artifactId>
<version>1.44.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Sa-Token插件:权限缓存与业务缓存分离 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-alone-redis</artifactId>
<version>1.44.0</version>
</dependency>一、登录认证
1.1 原理
原文链接:https://blog.csdn.net/weixin_39570751/article/details/121291274↗
所谓登录认证,说白了就是限制某些API接口必须登录后才能访问(例:查询我的账号资料) 那么如何判断一个会话是否登录?
框架会在登录成功后给你做标记,每次登录认证时校验这个标记,有标记者视为已登录,无标记者视为未登录!
Saolder为什么能获取response对象,因为
SaHolder调用了SaManager.getSaTokenContext(),得到了SaTokenContext才可以通过getResponse方法获取SaResponse,而SaTokenContext的真实身份其实是SaTokenContextForSpring,SaTokenContextForSpring重写了getResponse,就是在这个方法去调用Spring的Response
StpUtil.login(user.getId());
// 👇源码逻辑
// 1. 获取当前StpLogic实例(默认是"login"类型)
StpLogic stpLogic = SaManager.getStpLogic("login", false);
// 2、执行登录逻辑,创建 token
// 3、在当前会话写入当前 tokenValue
setTokenValue(tokenValue, loginModel.getCookieTimeout());
// 4、注入 cookie
response.addCookie(getTokenName(), tokenValue, "/", config.getCookieDomain(), cookieTimeout);❓默认情况下,登录类型是什么、如何自定义登录类型
- 默认登录方式是
login - 在springboot下,可以通过自定义
Bean更换默认登录方式👇
@Configuration
public class SaTokenLogicConfig {
@Bean("stpLogicApplet")
@Primary
public StpLogic stpLogicApplet() {
return new StpLogic("appletLoginType");
}
@Bean("stpLogicWeb")
public StpLogic stpLogicWeb() {
return new StpLogic("webLoginType");
}
}❗使用什么类型登录,检验登录、获取用户信息时,必须使用对应的StpLogic
@Component
public class SaTokenUtil {
@Autowired
@Qualifier("stpLogicApplet")
private StpLogic stpLogicApplet;
@Autowired
@Qualifier("stpLogicWeb")
private StpLogic stpLogicWeb;
/**
* 获取当前applet端登录的LoginUser对象
*/
public LoginUser getAppletLoginUser() {
Object obj = stpLogicApplet.getSession().get("userInfo");
if (obj instanceof LoginUser) {
return (LoginUser) obj;
}
return null;
}
/**
* 获取当前web端登录的LoginUser对象
*/
public LoginUser getWebLoginUser() {
Object obj = stpLogicWeb.getSession().get("userInfo");
if (obj instanceof LoginUser) {
return (LoginUser) obj;
}
return null;
}
/**
* 获取当前登录的 wid
*
* @return wid
*/
public Integer getAppletLoginWid() {
return stpLogicApplet.getLoginIdAsInt();
}
/**
* 获取当前登录的 wid
*
* @return wid
*/
public Integer getWebLoginWid() {
return stpLogicWeb.getLoginIdAsInt();
}
}
1.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: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true
@Configuration
public class SaTokenConfigure {
@Autowired
public void configSaToken(SaTokenConfig config) {
config.setTokenName("satoken")
.setTimeout(60 * 60 * 24 * 30)
.setActivityTimeout(1800)
.setIsConcurrent(true)
.setIsShare(true)
.setTokenStyle("uuid")
.setTokenSessionCheckLogin(true)
.setAutoRenew(true)
.setIsLog(true)
.setJwtSecretKey("your-jwt-secret-key-here")
.setIdTokenTimeout(60 * 60 * 24);
}
}1.3 踹人
// 当前会话注销登录
StpUtil.logout();
// 获取当前会话是否已经登录,返回true=已登录,false=未登录
StpUtil.isLogin();
StpUtil.logout(10001); // 强制指定账号注销下线
StpUtil.logout(10001, "PC"); // 强制指定账号指定端注销下线
StpUtil.logoutByTokenValue("token"); // 强制指定 Token 注销下线
StpUtil.kickout(10001); // 将指定账号踢下线
StpUtil.kickout(10001, "PC"); // 将指定账号指定端踢下线
StpUtil.kickoutByTokenValue("token"); // 将指定 Token 踢下线1.4 登录状态查询
// 检验当前会话是否已经登录, 如果未登录,则抛出异常: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 值
StpUtil.getTokenValue();
// 获取当前`StpLogic`的 token 名称
StpUtil.getTokenName();
// 获取指定 token 对应的账号id,如果未登录,则返回 null
StpUtil.getLoginIdByToken(String tokenValue);
// 获取当前会话剩余有效期(单位:s,返回-1代表永久有效)
StpUtil.getTokenTimeout();
// 获取当前会话的 token 信息参数
StpUtil.getTokenInfo();二、鉴权
2.1 权限分配
/**
* 自定义权限加载接口实现类
*/
@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;
}
}2.2 角色校验
// 获取:当前账号所拥有的权限集合
StpUtil.getPermissionList();
// 判断:当前账号是否含有指定权限, 返回 true 或 false
StpUtil.hasPermission("user.add");
// 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
// NotPermissionException 对象可通过 getLoginType() 方法获取具体是哪个 StpLogic 抛出的异常
StpUtil.checkPermission("user.add");
// 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
StpUtil.checkPermissionAnd("user.add", "user.delete", "user.get");
// 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");
// 获取:当前账号所拥有的角色集合
StpUtil.getRoleList();
// 判断:当前账号是否拥有指定角色, 返回 true 或 false
StpUtil.hasRole("super-admin");
// 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
// NotRoleException 对象可通过 getLoginType() 方法获取具体是哪个 StpLogic 抛出的异常
StpUtil.checkRole("super-admin");
// 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
StpUtil.checkRoleAnd("super-admin", "shop-admin");
// 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
StpUtil.checkRoleOr("super-admin", "shop-admin"); 2.3 通配符*
当一个账号拥有 "*" 权限时,他可以验证通过任何权限码 (角色认证同理)2.4 权限精准
前后端分离项目,则:
- 在登录时,把当前账号拥有的所有权限码一次性返回给前端
- 前端将权限码集合保存在
localStorage或其它全局状态管理对象中。 - 在需要权限控制的按钮上,使用 js 进行逻辑判断,例如在
Vue框架中我们可以使用如下写法:
// `arr`是当前用户拥有的权限码数组
// `user.delete`是显示按钮需要拥有的权限码
// `删除按钮`是用户拥有权限码才可以看到的内容。
<button v-if="arr.indexOf('user.delete') > -1">删除按钮</button>三、注解鉴权
@SaCheckLogin: 登录校验 —— 只有登录之后才能进入该方法。@SaCheckRole("admin"): 角色校验 —— 必须具有指定角色标识才能进入该方法。@SaCheckPermission("user:add"): 权限校验 —— 必须具有指定权限才能进入该方法。@SaCheckSafe: 二级认证校验 —— 必须二级认证之后才能进入该方法。@SaCheckHttpBasic: HttpBasic校验 —— 只有通过 HttpBasic 认证后才能进入该方法。@SaCheckHttpDigest: HttpDigest校验 —— 只有通过 HttpDigest 认证后才能进入该方法。@SaCheckDisable("comment"):账号服务封禁校验 —— 校验当前账号指定服务是否被封禁。@SaCheckSign:API 签名校验 —— 用于跨系统的 API 签名参数校验。
@SaIgnore:忽略校验 —— 表示被修饰的方法或类无需进行注解鉴权和路由拦截器鉴权。
Sa-Token 使用全局拦截器完成注解鉴权功能,为了不为项目带来不必要的性能负担,拦截器默认处于关闭状态
因此,为了使用注解鉴权,你必须手动将 Sa-Token 的全局拦截器注册到你项目中
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}3.1 设定校验模式
@SaCheckRole与@SaCheckPermission注解可设置校验模式,mode有两种取值:
SaMode.AND,标注一组权限,会话必须全部具有才可通过校验。SaMode.OR,标注一组权限,会话只要具有其一即可通过校验。
// 注解式鉴权:只要具有其中一个权限即可通过校验
@RequestMapping("atJurOr")
@SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)
public SaResult atJurOr() {
return SaResult.data("用户信息");
}3.2 角色权限or校验
假设有以下业务场景:一个接口在具有权限 user.add 或角色 admin 时可以调通。怎么写?
// 注解式鉴权:只要具有其中一个权限即可通过校验
@RequestMapping("atJurOr")
@SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)
public SaResult atJurOr() {
return SaResult.data("用户信息");
}orRole 字段代表权限校验未通过时的次要选择,两者只要其一校验成功即可进入请求方法,其有三种写法:
- 写法一:
orRole = "admin",代表需要拥有角色 admin 。 - 写法二:
orRole = {"admin", "manager", "staff"},代表具有三个角色其一即可。 - 写法三:
orRole = {"admin, manager, staff"},代表必须同时具有三个角色。
3.3 @SaIgnore
@SaCheckLogin
@RestController
public class TestController {
// ... 其它方法
// 此接口加上了 @SaIgnore 可以游客访问
@SaIgnore
@RequestMapping("getList")
public SaResult getList() {
// ...
return SaResult.ok();
}
}如上代码表示:TestController 中的所有方法都需要登录后才可以访问,但是 getList 接口可以匿名游客访问。
- @SaIgnore 修饰方法时代表这个方法可以被游客访问,修饰类时代表这个类中的所有接口都可以游客访问。
- @SaIgnore 具有最高优先级,当 @SaIgnore 和其它鉴权注解一起出现时,其它鉴权注解都将被忽略。
- @SaIgnore 同样可以忽略掉 Sa-Token 拦截器中的路由鉴权
3.4 批量注解or、and鉴权
// 在 `@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();
}
// 当前客户端只要有 [ login 账号登录] 或者 [user 账号登录] 其一,就可以通过验证进入方法。
// 注意:`type = "login"` 和 `type = "user"` 是多账号模式章节的扩展属性,此处你可以先略过这个知识点
@SaCheckOr(
login = { @SaCheckLogin(type = "login"), @SaCheckLogin(type = "user") }
)
@RequestMapping("test")
public SaResult test() {
// ...
return SaResult.ok();
}
// 其实没有checkAnd
// 当你在一个方法上写多个注解鉴权时,其默认就是要满足所有注解规则后
// 才可以进入方法,只要有一个不满足,就会抛出异常
@SaCheckLogin
@SaCheckRole("admin")
@SaCheckPermission("user.add")
@RequestMapping("test")
public SaResult test() {
// ...
return SaResult.ok();
}
// 测试:只有通过登录校验,或者提供了正确的 ApiKey,才可以进入方法
// 使用 append 字段追加抓取扩展包里的注解
@RequestMapping("/test")
@SaCheckOr(login = @SaCheckLogin, append = { SaCheckApiKey.class })
@SaCheckApiKey
public SaResult test() {
// ...
return SaResult.ok();
}四、路由拦截
4.1 注册拦截器
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// handle -> StpUtil.checkLogin()代表只进行注册拦截器
public void addInterceptors(InterceptorRegistry registry) {
// 只进行登录校验功能
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns( // 排除不需要拦截的路径
"/acc/password", // 你的登录接口
"/user/doLogin", // 示例接口
"/error", // 排除错误页面
"/favicon.ico"
);
}
}
// 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"));
// 甚至你可以随意的写一个打印语句
SaRouter.match("/**", r -> System.out.println("----啦啦啦----"));
// 连缀写法
SaRouter.match("/**").check(r -> System.out.println("----啦啦啦----"));
})).addPathPatterns("/**");
}
} 4.2 自定义异常拦截
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理所有:未登录异常
*/
@ExceptionHandler(NotLoginException.class)
public Result handleNotLoginException(NotLoginException nle) {
// 根据不同子异常类型,可以给出更精确的提示(可选)
// nle.getType() 可以获取异常具体类型,如 NOT_TOKEN、INVALID_TOKEN、TOKEN_TIMEOUT 等
return new Result(UNAUTHORIZED, null, "您尚未登录,请先登录!");
}
/**
* 处理所有:权限异常
*/
@ExceptionHandler(NotPermissionException.class)
public Result handleNotPermissionException(NotPermissionException npe) {
return new Result(FORBIDDEN, null, "抱歉,您没有权限进行此操作!");
}
// ... 可以继续添加 DisableLoginException, NotRoleException 等异常的处理 ...
}4.3 关闭注解校验 && 匹配特征大全
注解 @SaIgnore 的忽略效果只针对 SaInterceptor拦截器 和 AOP注解鉴权 生效,对自定义拦截器与过滤器不生效。
SaInterceptor 只要注册到项目中,默认就会打开注解校验,如果要关闭此能力,需要指定 isAnnotation 为 false:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new SaInterceptor(handle -> {
SaRouter.match("/**").check(r -> StpUtil.checkLogin());
}).isAnnotation(false) // 指定关闭掉注解鉴权能力,这样框架就只会做路由拦截校验了
).addPathPatterns("/**");
}
// 使用 setBeforeAuth 注册认证前置函数:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(handle -> {
System.out.println(1);
})
.setBeforeAuth(handle -> {
System.out.println(2);
})
).addPathPatterns("/**");
}
// 如上代码,先执行 2,再执行注解鉴权,再执行 1,
// 如果 beforeAuth 里包含 SaRouter.stop() 将跳过后续的注解鉴权和 auth 认证环节。stop() 与 back() 函数的区别在于:
SaRouter.stop()会停止匹配,进入Controller。SaRouter.back()会停止匹配,直接返回结果到前端。- free() 的作用是:打开一个独立的作用域,使内部的 stop() 不再一次性跳出整个 Auth 函数,而是仅仅跳出当前 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(/* --- */);五、redis集成
5.0 依赖
<!-- Sa-Token 整合 RedisTemplate -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template</artifactId>
<version>1.44.0</version>
</dependency>
<!-- 提供 Redis 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>5.1 token前缀
{"satoken": "Bearer xxxx-xxxx-xxxx-xxxx"}
// 此时后端如果不做任何特殊处理,框架将会把Bearer视为token的一部分,
// 无法正常读取token信息,导致鉴权失败。
sa-token:
# 指定 token 提交时的前缀
token-prefix: Bearer
# 注:Token前缀 与 Token值 之间必须有一个空格由于Cookie中无法存储空格字符,所以配置 Token 前缀后,Cookie 模式将会失效,无法成功提交带有前缀的 token。
如果需要在这种场景下仍然使用 Cookie 模式验证 token,可以使用 cookieAutoFillPrefix 配置项打开 Cookie 模式自动填充前缀:
sa-token:
# 指定 Cookie 模式下自动填充 token 提交前缀
cookie-auto-fill-prefix: true5.3 同端互斥登录
// 指定`账号id`和`设备类型`进行登录
StpUtil.login(10001, "PC");
// 调用此方法登录后,同设备的会被顶下线(不同设备不受影响),
// 再次访问系统时会抛出 NotLoginException 异常,场景值=-4
// 指定`账号id`和`设备类型`进行强制注销
StpUtil.logout(10001, "PC");
// 如果第二个参数填写null或不填,代表将这个账号id所有在线端强制注销,
// 被踢出者再次访问系统时会抛出 NotLoginException 异常,场景值=-2
// 返回当前token的登录设备类型
StpUtil.getLoginDevice();
// 获取指定loginId指定设备类型端的tokenValue
StpUtil.getTokenValueByLoginId(10001, "APP"); 5.4 登录与注销参数
// 指定`账号id`和`设备类型`进行登录
StpUtil.login(10001, "PC");
// 设置登录账号 id 为 10001,并指定是否为 “记住我” 模式
StpUtil.login(10001, false);
StpUtil.login(10001, new SaLoginParameter()
.setDeviceType("PC") // 此次登录的客户端设备类型, 一般用于完成 [同端互斥登录] 功能
.setDeviceId("xxxxxxxxx") // 此次登录的客户端设备ID, 登录成功后该设备将标记为可信任设备
.setIsLastingCookie(true) // 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
.setTimeout(60 * 60 * 24 * 7) // 指定此次登录 token 的有效期, 单位:秒,-1=永久有效
.setActiveTimeout(60 * 60 * 24 * 7) // 指定此次登录 token 的最低活跃频率, 单位:秒,-1=不进行活跃检查
.setIsConcurrent(true) // 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
.setIsShare(false) // 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个token, 为 false 时每次登录新建一个 token)
.setMaxLoginCount(12) // 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置项才有意义)
.setMaxTryTimes(12) // 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用)
.setExtra("key", "value") // 记录在 Token 上的扩展参数(只在 jwt 模式下生效)
.setToken("xxxx-xxxx-xxxx-xxxx") // 预定此次登录的生成的Token
.setIsWriteHeader(false) // 是否在登录后将 Token 写入到响应头
.setTerminalExtra("key", "value")// 本次登录挂载到 SaTerminalInfo 的自定义扩展数据
.setReplacedRange(SaReplacedRange.CURR_DEVICE_TYPE) // 顶人下线的范围: CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端
.setOverflowLogoutMode(SaLogoutMode.LOGOUT) // 溢出 maxLoginCount 的客户端,将以何种方式注销下线: LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线
.setRightNowCreateTokenSession(true) // 是否立即创建对应的 Token-Session (true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建)
.setupCookieConfig(cookie->{ // 设置 Cookie 配置项
cookie.setDomain("sa-token.cc"); // 设置:作用域
cookie.setPath("/shop"); // 设置:路径 (一般只有当你在一个域名下部署多个项目时才会用到此值。)
cookie.setSecure(true); // 设置:是否只在 https 协议下有效
cookie.setHttpOnly(true); // 设置:是否禁止 js 操作 Cookie
cookie.setSameSite("Lax"); // 设置:第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
cookie.addExtraAttr("aa", "bb"); // 设置:额外扩展属性
}
);
// 当前客户端注销
StpUtil.logout(new SaLogoutParameter()
// 注销范围: TOKEN=只注销当前 token 的会话,ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话
// 此参数只在调用 StpUtil.logout() 时有效
.setRange(SaLogoutRange.TOKEN)
);
// 指定 token 注销
StpUtil.logoutByTokenValue("xxxxxxxxxxxxxxxxxxxxxxx", new SaLogoutParameter()
// 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API)(默认 false)
// 此参数只在调用 StpUtil.[logout/kickout/replaced]ByTokenValue("token") 时有效
.setIsKeepFreezeOps(false)
// 是否保留此 token 的 Token-Session 对象(默认 false)
.setIsKeepTokenSession(true)
);
// 指定 loginId 注销
StpUtil.logout(10001, new SaLogoutParameter()
.setDeviceType("PC") // 设置注销的设备类型 (如果不指定,则默认注销所有客户端)
.setIsKeepTokenSession(true) // 是否保留对应 token 的 Token-Session 对象(默认 false)
.setMode(SaLogoutMode.REPLACED) // 设置注销模式:LOGOUT=注销登录、KICKOUT=踢人下线,REPLACED=顶人下线(默认LOGOUT)
);5.5 二级认证
// 在当前会话 开启二级认证,时间为120秒
StpUtil.openSafe(120);
// 在当前会话 开启二级认证,业务标识为client,时间为600秒
StpUtil.openSafe("client", 600);
// 获取:当前会话是否处于二级认证时间内
StpUtil.isSafe();
// 获取:当前会话是否已完成指定业务的二级认证,下方参数也可以传递"client"
StpUtil.isSafe("client");
// 检查当前会话是否已通过二级认证,如未通过则抛出异常
StpUtil.checkSafe();
// 获取当前会话的二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证)
StpUtil.getSafeTime();
// 在当前会话 结束二级认证
StpUtil.closeSafe(); 在一个方法上使用 @SaCheckSafe 注解,可以在代码进入此方法之前进行一次二级认证校验
// 二级认证:必须二级认证之后才能进入该方法
@SaCheckSafe
@RequestMapping("add")
public String add() {
return "用户增加";
}
// 指定业务类型,进行二级认证校验
@SaCheckSafe("art")
@RequestMapping("add2")
public String add2() {
return "文章增加";
}5.6 封号斗罗
// 先踢下线
StpUtil.kickout(10001);
// 再封禁账号
StpUtil.disable(10001, 86400); 旧版本在 StpUtil.login() 时会自动校验账号是否被封禁,v1.31.0 之后将 校验封禁 和 登录 两个动作分离成两个方法,不再自动校验。
// 校验指定账号是否已被封禁,如果被封禁则抛出异常 `DisableServiceException`
StpUtil.checkDisable(10001);
// 通过校验后,再进行登录:
StpUtil.login(10001);
// 封禁指定账号
StpUtil.disable(10001, 86400);
// 获取指定账号是否已被封禁 (true=已被封禁, false=未被封禁)
StpUtil.isDisable(10001);
// 校验指定账号是否已被封禁,如果被封禁则抛出异常 `DisableServiceException`
StpUtil.checkDisable(10001);
// 获取指定账号剩余封禁时间,单位:秒,如果该账号未被封禁,则返回-2
StpUtil.getDisableTime(10001);
// 解除封禁
StpUtil.untieDisable(10001); 