个人博客:无奈何杨(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最重要的模块挑一部分细致讲一下。

Sa-Token组件介绍_satoken

satoken核心

简单介绍一下satoken的项目结构,就可以步入本篇文章的正题了——satoken组件。

sa-token-core的组织结构如下,包的命名已经能说明一些什么了,接下来到了哪块就细致来说哪块。

Sa-Token组件介绍_spring_02

SaManager

全类名路径:cn.dev33.satoken.SaManager

以上结构中最突出的就是SaManager了,官方给他的定义如下,可见其重要性。

/**
 * 管理 Sa-Token 所有全局组件,可通过此类快速获取、写入各种全局组件对象
 *
 * @author click33
 * @since 1.18.0
 */
public class SaManager {
    ...
}

SaManager的结构如下,其中这些属性就是satoken最重要的组件了,下面挑出几个细讲一下。

Sa-Token组件介绍_springboot_03

SaTokenConfig

全类名路径:cn.dev33.satoken.config.SaTokenConfig

config包下除了SaTokenConfig还有SaSignConfigSaCookieConfigSaTokenConfigFactory。前三个属于配置类,最后一个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部分截图如下。

Sa-Token组件介绍_springboot_04

SaTokenDao

全类名路径:cn.dev33.satoken.dao.SaTokenDao

Sa-Token,官方给他的描述是:SaTokenDao 是数据持久层接口,负责所有会话数据的底层写入和读取。

正如次,此接口设计了如下一些操作数据读取与写入的方法,注意其中包含抽象方法、默认实现方法和默认空实现方法。

Sa-Token组件介绍_springboot_05

SaTokenDaodao包下还有一个默认实现类SaTokenDaoDefaultImpl,当然除此之外还有很多其他实现在插件工程里。

默认实现SaTokenDaoDefaultImpl:Sa-Token 持久层接口,默认实现类(基于内存 Map,系统重启后数据丢失)。所以在不引入其他如Redis需要注意重启丢失的问题。

Sa-Token组件介绍_satoken_06

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包下还有很重要的StpUtilStpLogicSaTokenInfo这次就先不讲了。

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

Sa-Token组件介绍_项目结构_07

对应上官方所说对接不同web框架,SaTokenContext的实现有下,当然对于我们使用最多的可能就是SaTokenContextForString了。

Sa-Token组件介绍_satoken_08

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 认证模块)、SaJsonTemplateJSON 转换器)、SaSignTemplateAPI 参数签名)、SaSameTemplateSame-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组件介绍_springboot_09

Sa-Token组件介绍_spring_10

sa-token-servlet

Sa-Token组件介绍_spring_11

sa-token-servlet仅仅是实现了前面context章节的SaStorageSaRequestSaResponse接口,并定义了一些错误码。

sa-token-spring-boot-starter

Sa-Token组件介绍_satoken_12

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组件介绍_satoken_13

好了,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的一个实现类,具体方法实现自己学习吧😂。其中SaSessionForJacksonCustomizedSaSessionJackson序列化实现类。

sa-token-spring-boot-autoconfig

重头戏来了!!!

前面那么多东西都要在这里串起来了。

Sa-Token组件介绍_项目结构_14

直接开始!

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

这次先不看SSOOAuth2的组件。只看SaBeanRegisterSaBeanInject最基础的部分。

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

Sa-Token组件介绍_spring_15

观察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设置属性的重要类,是之后直接使用StpUtilStpLogic等静态类的静态方法的基础。

写在最后

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


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

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview

Sa-Token组件介绍_spring_16