个人博客:无奈何杨(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
以上环境表示本次登录详解针对的是普通登录(非SSO
、OAuth
场景),集成redisson
存储satoken
数据。
登录应该如何设计
如果让我们来设计权限认证框架,或是说,让我们对使用过的权限认证框架进行抽象,使之能面对多种不同的登录鉴权场景,我们应该如何设计?
已知我登录送的用户id是1
,下面这张图展示的是satoken
在一次登录成功后在redis
中产生的k-v
数据。
对了,要结合一下我之前给出的satoken
配置。
############## 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
在讲登录流程前,一定要提到StpUtil
和StpLogic
。
官网:Sa-Token StpUtil - 鉴权工具类,StpUtil 是 Sa-Token 整体功能的核心,大多数功能均由此工具类提供。
可以看到StpUtil
只有一个私有构造器,私有构造器常用于哪些场景呢?
- 单例模式
- 工具类
- 不可实例化的抽象基类
- 枚举类型
- 安全性考虑
从StpUtil
的命名其实就可以确定了,工具类嘛!其中这个StpLogic
是Sa-Token 的核心,框架大多数功能均由此类提供具体逻辑实现。StpLogic
有三个子类用于集成JWT
,可参考官网Sa-Token 和 jwt 集成。
区别与选择,以下copy
于官网Sa-Token分布式Session会话解决方案。
解决方案
要怎么解决这个问题呢?目前的主流方案有四种:
- Session同步:只要一个节点的数据发生了改变,就强制同步到其它所有节点
- Session粘滞:通过一定的算法,保证一个用户的所有请求都稳定的落在一个节点之上,对这个用户来讲,就好像还是在访问一个单机版的服务
- 建立会话中心:将Session存储在专业的缓存中间件上,使每个节点都变成了无状态服务,例如:Redis
- 颁发无状态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会话对象
那么satoken
具体是怎么做的?如何实现上面的Session
模型的呢?当然后面详解登录会讲,这里介绍一下最重要的两个类SaSession
和TokenSign
。
SaSession
会话对象,属性列表如下,看到tokenSignList
属性就应该明白了两者的关系。
TokenSign
Token
签名,我们实际使用的token
值就是这里的value
属性。
自定义登录
自定义登录如下,在登录之后StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
表示在当前tokenSession
中存储LoginUser
登录信息,方便后面直接通过缓存获取用户权限。
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
接口来缓存用户信息(包含角色、权限)的。
获取登录用户权限直接查缓存就好。
/**
* @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