Sa-Token组件介绍
个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang - Overview
前文讲了Sa-Token介绍与SpringBoot环境下使用,但是satoken
最重要的登录鉴权直接略过了,那这篇文章就开讲,😂当然不是啦。看标题就知道这次要讲的是satoken
组件,为什么这么安排呢,是因为我在细致了解satoken
源码后,还是非常想把satoken
讲的比较清楚细致的,包含其中一些设计模式的使用、函数式接口的应用、组件注册的方法等,而要讲好这些,satoken
的组件一定要讲一下吧,废话不多说了,下面开始。
必要声明:文章基于Sa-Token,版本1.37.0
satoken项目结构
下载源码
git clone https://github.com/dromara/Sa-Token.git
项目结构介绍
项目结构组织如下,结构还是相当清晰明了的。我也不可能把所有工程代码详细都讲一下,只能根据satoken
最重要的模块挑一部分细致讲一下。
satoken核心
简单介绍一下satoken
的项目结构,就可以步入本篇文章的正题了——satoken
组件。
sa-token-core
的组织结构如下,包的命名已经能说明一些什么了,接下来到了哪块就细致来说哪块。
SaManager
全类名路径:cn.dev33.satoken.SaManager
以上结构中最突出的就是SaManager
了,官方给他的定义如下,可见其重要性。
/**
* 管理 Sa-Token 所有全局组件,可通过此类快速获取、写入各种全局组件对象
*
* @author click33
* @since 1.18.0
*/
public class SaManager {
...
}
SaManager
的结构如下,其中这些属性就是satoken
最重要的组件了,下面挑出几个细讲一下。
SaTokenConfig
全类名路径:cn.dev33.satoken.config.SaTokenConfig
config
包下除了SaTokenConfig
还有SaSignConfig
、SaCookieConfig
、SaTokenConfigFactory
。前三个属于配置类,最后一个Factory
是用于非IOC
环境下使用的类。
satoken
的配置参考这一节就好Sa-Token,当然这里完全和源码相对应的。
如前文所配置的,都对应着类属性。
############## 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
SaTokenConfig
部分截图如下。
SaTokenDao
全类名路径:cn.dev33.satoken.dao.SaTokenDao
Sa-Token,官方给他的描述是:SaTokenDao
是数据持久层接口,负责所有会话数据的底层写入和读取。
正如次,此接口设计了如下一些操作数据读取与写入的方法,注意其中包含抽象方法、默认实现方法和默认空实现方法。
与SaTokenDao
同dao
包下还有一个默认实现类SaTokenDaoDefaultImpl
,当然除此之外还有很多其他实现在插件工程里。
默认实现SaTokenDaoDefaultImpl
:Sa-Token 持久层接口,默认实现类(基于内存 Map,系统重启后数据丢失)。所以在不引入其他如Redis
需要注意重启丢失的问题。
StpInterface
全类名路径:cn.dev33.satoken.stp.StpInterface
前文讲到自定义权限认证时有讲到,这个接口需要我们自己实现来满足业务要求。
默认实现类StpInterfaceDefaultImpl
也有说明。
/**
* 对 {@link StpInterface} 接口默认的实现类
* <p>
* 如果开发者没有实现 StpInterface 接口,则框架会使用此默认实现类,所有方法都返回空集合,即:用户不具有任何权限和角色。
*
* @author click33
* @since 1.10.0
*/
public class StpInterfaceDefaultImpl implements StpInterface {
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
return new ArrayList<>();
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
return new ArrayList<>();
}
}
同stp
包下还有很重要的StpUtil
、StpLogic
、SaTokenInfo
这次就先不讲了。
SaTokenContext与SaTokenSecondContext
全类名路径:cn.dev33.satoken.context.SaTokenContext/SaTokenSecondContext
Sa-Token,官方描述:上下文处理器封装了当前应用环境的底层操作,是 Sa-Token 对接不同 web 框架的关键。目前 Sa-Token 仅对 SpringBoot、SpringMVC、WebFlux、Solon 等部分 Web 框架制作了 Starter 集成包, 如果我们使用的 Web 框架不在上述列表之中,则需要自定义 SaTokenContext 接口的实现完成整合工作。
core
工程context
包下的结构是这样的,其中SaHolder
是Sa-Token 上下文持有类,你可以通过此类快速获取当前环境下的 SaRequest、SaResponse、SaStorage、SaApplication 对象。关于satoken的三大作用域参考官网这篇文章Sa-Token。
对应上官方所说对接不同web
框架,SaTokenContext
的实现有下,当然对于我们使用最多的可能就是SaTokenContextForString
了。
SaLog
全类名路径:cn.dev33.satoken.log.SaLog
Sa-Token 日志输出接口,只有一个实现类就在同log
包下SaLogForConsole
。在看实现类的源码时看到这个倒是涨一点知识😂。挺有意思的。
/*
// 三种写法速度对比
// if( config.getIsColorLog() != null && config.getIsColorLog() ) 10亿次,2058ms
// if( config.getIsColorLog() == Boolean.TRUE ) 10亿次,1050ms 最快
// if( Objects.equals(config.getIsColorLog(), Boolean.TRUE) ) 10亿次,1543ms
*/
小结
关于SaManager
的组件就挑上面几个介绍了一下。
剩下的SaTempInterface
(临时 token
认证模块)、SaJsonTemplate
(JSON
转换器)、SaSignTemplate
(API
参数签名)、SaSameTemplate
(Same-Token
同源系统认证模块)就自己学习吧。
satoken组件注册
简单介绍了satoken
的组件,就要思考这些组件在项目中是如何使用的?如何管理的?
环境声明
这里针对如下环境来讲一下。
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redisson-jackson</artifactId>
</dependency>
依赖关系
他们分别对应有如下依赖。
sa-token-servlet
sa-token-servlet
仅仅是实现了前面context
章节的SaStorage
、SaRequest
、SaResponse
接口,并定义了一些错误码。
sa-token-spring-boot-starter
sa-token-spring-boot-starter
也很简单,关于SpringBoot
的自动装配原理就不多讲了,这里看resource/META-INF/spring.factories
文件,其实SpringBoot2.7
之后就换了,下面会提到。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.dev33.satoken.spring.SaTokenContextRegister
这里表示自动注入的是SaTokenContextRegister
。
1、注入了SaTokenContextForSpring
前面也有提到,其就是在SpringMVC环境下的上下文处理器,针对处理sa-token-servlet
实现的context
;
2、注入了SaPathCheckFilterForServlet
路径检查过滤器,自己可查看其代码,这里略过。
/**
* 注册 Sa-Token 框架所需要的 Bean
*
* @author click33
* @since 1.34.0
*/
public class SaTokenContextRegister {
/**
* 获取上下文处理器组件 (Spring版)
*
* @return /
*/
@Bean
public SaTokenContext getSaTokenContextForSpring() {
return new SaTokenContextForSpring();
}
/**
* 请求 path 校验过滤器
*
* @return /
*/
@Bean
public SaPathCheckFilterForServlet saPathCheckFilterForServlet() {
return new SaPathCheckFilterForServlet();
}
}
sa-token-redisson-jackson
好了,sa-token-spring-boot-starter
解决了确定context
的问题。
sa-token-redisson-jackson
要解决SaTokenDao
持久层实现的问题。前面提到SaTokenDao
有很多实现,默认实现在内存中存储数据,这里使用了redisson
。
可以看到resource/META-INF/
不仅有spring.factories
还有spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
,这就是SpringBoot2.7
前后自动装配的差别,有机会可以再探讨一下。
resource/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
内容如下
cn.dev33.satoken.dao.SaTokenDaoRedissonJackson
这里注入的是SaTokenDaoRedissonJackson
,是SaTokenDao
的一个实现类,具体方法实现自己学习吧😂。其中SaSessionForJacksonCustomized
是SaSession
的Jackson
序列化实现类。
sa-token-spring-boot-autoconfig
重头戏来了!!!
前面那么多东西都要在这里串起来了。
直接开始!
resource/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
内容如下
cn.dev33.satoken.spring.SaBeanRegister
cn.dev33.satoken.spring.SaBeanInject
cn.dev33.satoken.spring.sso.SaSsoBeanRegister
cn.dev33.satoken.spring.sso.SaSsoBeanInject
cn.dev33.satoken.spring.oauth2.SaOAuth2BeanRegister
cn.dev33.satoken.spring.oauth2.SaOAuth2BeanInject
这次先不看SSO
和OAuth2
的组件。只看SaBeanRegister
和SaBeanInject
最基础的部分。
SaBeanRegister
/**
* 注册Sa-Token所需要的Bean
* <p> Bean 的注册与注入应该分开在两个文件中,否则在某些场景下会造成循环依赖
* @author click33
*
*/
public class SaBeanRegister {
/**
* 获取配置Bean
*
* @return 配置对象
*/
@Bean
@ConfigurationProperties(prefix = "sa-token")
public SaTokenConfig getSaTokenConfig() {
return new SaTokenConfig();
}
/**
* 获取 json 转换器 Bean (Jackson版)
*
* @return json 转换器 Bean (Jackson版)
*/
@Bean
public SaJsonTemplate getSaJsonTemplateForJackson() {
return new SaJsonTemplateForJackson();
}
/**
* 应用上下文路径加载器
* @return /
*/
@Bean
public ApplicationContextPathLoading getApplicationContextPathLoading() {
return new ApplicationContextPathLoading();
}
}
1、通过spring
配置文件前缀为sa-token
读取SaTokenConfig
属性注入
2和3略过。哈哈哈哈哈
SaBeanInject
观察SaBeanInject
的结构,注意到其只有一个构造器,剩下的都是空返回的set
方法而这些方法入参都与前面的SaManager
里的组件几乎完全对应。
首先看这个构造器,通过调用SaManager
的静态方法来设置的。
/**
* 组件注入
* <p> 为确保 Log 组件正常打印,必须将 SaLog 和 SaTokenConfig 率先初始化 </p>
*
* @param log log 对象
* @param saTokenConfig 配置对象
*/
public SaBeanInject(
@Autowired(required = false) SaLog log,
@Autowired(required = false) SaTokenConfig saTokenConfig
){
if(log != null) {
SaManager.setLog(log);
}
if(saTokenConfig != null) {
SaManager.setConfig(saTokenConfig);
}
}
不知道你还记得前面有贴官方给SaManager
的定义:管理 Sa-Token 所有全局组件,可通过此类快速获取、写入各种全局组件对象。
回过头来看SaManager
的属性是怎么定义的,public volatile static SaTokenConfig config;
保证了多线程环境下的可见性和有序性。也就是SaTokenConfig
被更新后,其他线程能立刻看到变量更新。另外在getConfig
方法也使用了同步块确保线程安全地获取config
对象。
/**
* 全局配置对象
*/
public volatile static SaTokenConfig config;
public static void setConfig(SaTokenConfig config) {
setConfigMethod(config);
// 打印 banner
if(config !=null && config.getIsPrint()) {
SaFoxUtil.printSaToken();
}
// 如果此 config 对象没有配置 isColorLog 的值,则框架为它自动判断一下
if(config != null && config.getIsLog() != null && config.getIsLog() && config.getIsColorLog() == null) {
config.setIsColorLog(SaFoxUtil.isCanColorLog());
}
// $$ 全局事件
SaTokenEventCenter.doSetConfig(config);
// 调用一次 StpUtil 中的方法,保证其可以尽早的初始化 StpLogic
StpUtil.getLoginType();
}
private static void setConfigMethod(SaTokenConfig config) {
SaManager.config = config;
}
/**
* 获取 Sa-Token 的全局配置信息
* @return 全局配置信息
*/
public static SaTokenConfig getConfig() {
if (config == null) {
synchronized (SaManager.class) {
if (config == null) {
setConfigMethod(SaTokenConfigFactory.createConfig());
}
}
}
return config;
}
SaBeanInject
的其他方法也没有什么特别的,自己看就行了。
可以说SaBeanInject
是帮助SaManager
设置属性的重要类,是之后直接使用StpUtil
、StpLogic
等静态类的静态方法的基础。
写在最后
拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。
个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang - Overview