Sa-Token登录pre

个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview


原本标题是:“Sa-Token登录详解”,写着写着,发现字数兜不住了,无奈还是拆开吧😂

前文讲了Sa-Token组件介绍,基本上重要的satoken组件都过了一遍,最后也简单说明了一下组件注册管理机制。本文就satoken登录前必须了解的进行说明。

环境声明:

Sa-Token官网,版本1.37.0

依赖:

sa-token-spring-boot-starter

sa-token-redisson-jackson

以上环境表示本次登录详解针对的是普通登录(非SSOOAuth场景),集成redisson存储satoken数据。

登录应该如何设计

如果让我们来设计权限认证框架,或是说,让我们对使用过的权限认证框架进行抽象,使之能面对多种不同的登录鉴权场景,我们应该如何设计?

已知我登录送的用户id是1,下面这张图展示的是satoken在一次登录成功后在redis中产生的k-v数据。

登录成功后redis的数据

对了,要结合一下我之前给出的satoken配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: Authorization
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 3600
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: 1800
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: false
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: true
# 是否尝试从header里读取token
is-read-header: true
# 是否尝试从cookie里读取token
is-read-cookie: false
# token前缀
token-prefix: "Bearer"
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: tik
# 是否输出操作日志
is-log: true

StpUtil与StpLogic

在讲登录流程前,一定要提到StpUtilStpLogic

官网:Sa-Token StpUtil - 鉴权工具类,StpUtil 是 Sa-Token 整体功能的核心,大多数功能均由此工具类提供。

StpUtil

可以看到StpUtil只有一个私有构造器,私有构造器常用于哪些场景呢?

  1. 单例模式
  2. 工具类
  3. 不可实例化的抽象基类
  4. 枚举类型
  5. 安全性考虑

StpUtil的命名其实就可以确定了,工具类嘛!其中这个StpLogic是Sa-Token 的核心,框架大多数功能均由此类提供具体逻辑实现。StpLogic有三个子类用于集成JWT,可参考官网Sa-Token 和 jwt 集成

区别与选择,以下copy于官网Sa-Token分布式Session会话解决方案

解决方案

要怎么解决这个问题呢?目前的主流方案有四种:

  1. Session同步:只要一个节点的数据发生了改变,就强制同步到其它所有节点
  2. Session粘滞:通过一定的算法,保证一个用户的所有请求都稳定的落在一个节点之上,对这个用户来讲,就好像还是在访问一个单机版的服务
  3. 建立会话中心:将Session存储在专业的缓存中间件上,使每个节点都变成了无状态服务,例如:Redis
  4. 颁发无状态token:放弃Session机制,将用户数据直接写入到令牌本身上,使会话数据做到令牌自解释,例如:jwt

本文使用方式3,集成Redis,所以jwt相关不多讲了。

前文也讲过SaManager管理着Sa-Token所有全局组件,其最重要体现就在这两个类中,往下看就对了。

Session会话

这里补充一个知识Sa-Token 中的 Session会话 模型详解,建议把原文看一下,思考一下为什么要这样设计,有没有其他设计思路。

  • Account-Session 以账号 id 为主,只要 token 指向的账号 id 一致,那么对应的Session对象就一致
  • Token-Session 以token为主,只要token不同,那么对应的Session对象就不同
  • Custom-Session 以特定的key为主,不同key对应不同的Session对象,同样的key指向同一个Session对象

在写后面内容时发现这个必须要再强调一下,一定要把这个理解一下,很重要!

SaSession会话对象

官网:SaSession-会话对象

那么satoken具体是怎么做的?如何实现上面的Session模型的呢?当然后面详解登录会讲,这里介绍一下最重要的两个类SaSessionTokenSign

SaSession

会话对象,属性列表如下,看到tokenSignList属性就应该明白了两者的关系。

sasession属性

TokenSign

Token签名,我们实际使用的token值就是这里的value属性。

TokenSign

自定义登录

自定义登录如下,在登录之后StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);表示在当前tokenSession中存储LoginUser登录信息,方便后面直接通过缓存获取用户权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void login(LoginUser loginUser, DeviceTypeEnum deviceEnum) {
SaLoginModel model = new SaLoginModel();
if (ObjectUtil.isNotNull(deviceEnum)) {
model.setDevice(deviceEnum.getDevice());
}
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
UserTypeEnum userType = UserTypeEnum.valueOf(loginUser.getType());
if (userType == UserTypeEnum.PC) {
model.setTimeout(1800).setActiveTimeout(600);
} else if (userType == UserTypeEnum.APP) {
model.setTimeout(86400).setActiveTimeout(1800);
}
StpUtil.login(loginUser.getId(), model);
StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
}

缓存权限数据

前提:系统的权限架构是RBAC模型。建议查看参考:将权限数据放在缓存里,不看官网看下面也行🧐。

思路一:缓存 登录用户id-权限集合

用户登录成功后,缓存登录用户的权限集合。上面自定义登录的LoginUser就是实现如下的Login接口来缓存用户信息(包含角色、权限)的。

获取登录用户权限直接查缓存就好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* @author wnhyang
* @date 2024/1/5
**/
public interface Login {

/**
* 获取用户ID
*
* @return 用户id
*/
Long getId();

/**
* 获取用户账号
*
* @return 用户账号
*/
String getUsername();

/**
* 获取用户类型
*
* @return 用户类型
*/
Integer getType();

/**
* 获取用户角色
*
* @return 用户角色
*/
Set<String> getRoleValues();

/**
* 获取用户权限
*
* @return 用户权限
*/
Set<String> getPermissions();

}

思路二:缓存 登录用户id-角色集合-权限集合

用户登录成功后,缓存登录用户的角色集合。

获取登录用户权限需要:1、查缓存获取登录用户角色;2、通过角色缓存获取角色对应的权限集合。

那么角色缓存如何管理呢?以下仅供参考,写代码时再思考一下如何保证缓存一致性吧?

set角色-权限集合

a、登录任何用户时,set角色-权限集合

b、项目启动,set所有角色-权限集合,多服务情况下避免多次重复set就行

c、其他时刻,如管理角色时

update角色-权限集合

a、通常在修改或删除角色对应权限,删除缓存/更新缓存

思路二同样可以使用上面的接口,只不过,getPermissions不再是直接从登录用户id的缓存取了,而是通过角色缓存中获取。

区别

  • 用户:U1,U2,U3。。。
  • 角色:R1,R2,R3。。。
  • 权限:P1,P2,P3。。。

思路一,直接取用户权限缓存

  • 优点:直接简单;
  • 缺点:在RABC模型下不存在用户-权限直接关联,只存在用户-角色-权限关联,也就是说在角色-权限发生变动时,比较麻烦。1、更新权限保证最新,重新查询用户-角色-权限,更新缓存。但这个有可能会导致缓存雪崩,因为角色-权限更改了,那么所有登录用户们是被更改的角色时,都需要更新缓存,这是有一定风险的。2、权限不实时更新,只有下次登录权限才变化;

思路二,通过角色间接取权限缓存

  • 优点:不同于思路一,在角色-权限发生变动时,只需要更新角色-权限缓存就行。
  • 缺点:查权限稍微麻烦一点,维护用户-角色角色-权限缓存需要一定设计。

剩下的留给下篇文章吧!

写在最后

拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。


个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview