Sa-Token SaStrategy 全局策略:核心逻辑代理封装与自定义扩展指南
【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token
SaStrategy(全局策略)是 Sa-Token 框架中统一定义关键性逻辑算法的入口,以全局单例的形式将 Token 生成、Session 创建、唯一性校验、自动续期、配置读取等核心行为抽象为可替换的函数式策略。本文以 sa-token-doc-new/docs/api/sa-strategy.md 为骨架,结合 SaStrategy.java 源码与 SaStrategyTest.java 测试用例,完整讲解每一个内置策略的默认实现、重写方法与应用场景,读完即可按需定制 Sa-Token 的核心行为(如自定义 Token 算法、从数据库动态加载配置)。
一、SaStrategy 是什么:核心逻辑的"代理封装"
Sa-Token 框架内部分散着大量关键性算法:如何创建 Token、如何创建 Session、如何判断权限集合、如何生成不重复的 token、是否续期、如何匹配路由……在引入 SaStrategy 之前,这些逻辑散落在框架各处,开发者想要定制只能改动框架源码。
SaStrategy 的设计目标,正如 SaStrategy.java 的类注释所述:
此类统一定义框架内的一些关键性逻辑算法,方便开发者进行按需重写。
它的核心形态是一个持有大量函数式接口字段的全局单例:
- 全局单例:通过
public static final SaStrategy instance = new SaStrategy();暴露,构造器私有,全框架共享同一实例(源码见 SaStrategy.java); - 函数式字段:每个策略都是一个 Java 函数式接口(
BiFunction、Function、Supplier等),默认提供框架内置实现,开发者可直接为字段赋值覆盖; - set 连缀风格:每个策略字段都配有一个返回
this的setXxx方法,支持链式调用,同时保留对旧版SaStrategy.me的兼容(已标注@Deprecated,建议使用instance,见 SaStrategy.java)。
策略接口统一收拢在cn.dev33.satoken.fun.strategy包下(见 fun/strategy 目录),包括SaCreateTokenFunction、SaCreateSessionFunction、SaHasElementFunction、SaGenerateUniqueTokenFunction、SaAutoRenewFunction、SaCreateStpLogicFunction、SaRouteMatchFunction、SaCorsHandleFunction、SaGetSaTokenConfigFunction以及 SaRequest/SaResponse/SaStorage 创建策略等。
从使用方式看,框架内部各模块通过SaStrategy.instance.xxx调用这些策略(例如 StpLogic.java 中createToken、StpLogic.java 中hasElement),因此只要替换对应字段,框架所有调用方会一并生效——这正是"代理封装"的含义:策略层是框架逻辑的唯一入口,替换策略即替换行为。
二、核心策略详解:默认实现与可替换行为
以下 10 项策略完整覆盖 sa-strategy.md 文档中的核心策略清单,并逐一补充源码级默认实现说明。
2.1 createToken:创建 Token 的策略
- 函数签名:
BiFunction<Object, String, String>,参数为[账号id, 账号类型],返回 Token 字符串; - 重写入口:
setCreateToken(...)。
文档中默认实现简写为"xxxxx-xxxxx-xxxxx-xxxxx"(UUID 形态)。对照源码,实际默认实现远比这丰富:它会读取该账号体系配置中的tokenStyle,按风格生成不同格式的 Token(见 SaStrategy.java):
| tokenStyle 配置值 | 生成结果 | 测试断言(见 SaStrategyTest) |
|---|---|---|
uuid(默认) | UUID.randomUUID().toString(),形如xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,含- | 断言包含- |
simple-uuid | UUID 去掉所有-,固定 32 位 | 断言长度为 32 且不含- |
random-32 | 32 位随机字符串 | 断言长度为 32 |
random-64 | 64 位随机字符串 | 断言长度为 64 |
random-128 | 128 位随机字符串 | 断言长度为 128 |
tik | 2位随机 + "_" + 14位随机 + "_" + 16位随机 + "__" | 断言以__结尾 |
| 其他非法值 | 打印 warn 日志并回退为 UUID | 断言回退结果含- |
以上行为在 SaStrategyTest.java 的createToken_byTokenStyle测试中逐一验证。
自定义示例(如接入 JWT 或自研 token 格式):
SaStrategy.instance.setCreateToken((loginId, loginType) -> { // 这里可以基于 loginId、loginType 自定义生成算法,例如拼接业务前缀 return "MY-" + loginId + "-" + System.currentTimeMillis(); });2.2 createSession:创建 Session 的策略
- 函数签名:
Function<String, SaSession>,参数为[SessionId],返回SaSession实例; - 重写入口:
setCreateSession(...)。
默认实现sessionId -> new SaSession(sessionId)(见 SaStrategy.java),即创建框架标准的SaSession。测试createSession_hasElement_generateUniqueToken中验证默认创建的 Session 能正确保留传入的 id(见 SaStrategyTest.java)。
典型应用:当项目需要自定义 Session 实现(例如继承SaSession扩展字段),可在此策略中返回自定义子类:
SaStrategy.instance.setCreateSession((sessionId) -> { return new MySaSession(sessionId); // MySaSession 继承自 SaSession });2.3 sessionClassType:反序列化 SaSession 的默认类型
- 类型:
volatile Class<? extends SaSession>; - 默认值:
SaSession.class(见 SaStrategy.java)。
该字段用于指定从持久化层反序列化SaSession时使用的类型。若 2.2 中自定义了 Session 子类,通常需要同步将此字段改为对应子类的Class,保证反序列化后仍是自定义类型。注意这是一个直接字段赋值(volatile保证多线程可见性),文档未提供对应 set 方法,赋值方式为SaStrategy.instance.sessionClassType = MySaSession.class;。
2.4 hasElement:集合模糊匹配策略
- 函数签名:
BiFunction<List<String>, String, Boolean>,参数为[集合, 元素],返回是否命中; - 重写入口:
setHasElement(...)。
文档中的默认实现简写为return false,仅示意"可替换"。实际默认实现是一个先精确匹配、后通配符模糊匹配的两段式算法(见 SaStrategy.java):
- 集合为空(
null或size() == 0)直接返回false; - 先尝试
list.contains(element)精确匹配,命中即返回true; - 遍历集合,用
SaFoxUtil.vagueMatch(patt, element)逐个做通配符匹配。
典型场景:权限/角色校验。例如角色列表为["admin", "user:*"]时,元素user:1001可通过user:*通配命中。测试 SaStrategyTest.java 验证了null、空集合返回 false、精确匹配与通配匹配均正确。
该策略被StpUtil.hasElement(...)间接调用(StpLogic.hasElement委托给SaStrategy.instance.hasElement,见 StpLogic.java),进而影响hasRole、hasPermission等权限判断逻辑。
2.5 generateUniqueToken:生成唯一式 Token 的算法
- 函数签名:
SaGenerateUniqueTokenFunction,参数为[元素名称, 最大尝试次数, 创建 token 函数, 检查 token 函数],返回唯一 Token(函数式接口定义见 SaGenerateUniqueTokenFunction.java); - 重写入口:
setGenerateUniqueToken(...)。
文档中的默认实现简写为return "xxxxxx"。真实默认实现是一个"循环生成 + 唯一性校验 + 失败重试"算法(见 SaStrategy.java):
- 调用
createTokenFunction.get()生成一个 token; - 若
maxTryTimes == -1,表示不做唯一性验证,直接返回; - 否则调用
checkTokenFunction.apply(token),返回true说明可用,立即返回; - 循环超过
maxTryTimes次仍未成功,抛出SaTokenException,提示"生成算法过于简单或资源池已耗尽"。
该接口的四个参数语义(见 SaGenerateUniqueTokenFunction.java)为:elementName用于组织异常提示信息;maxTryTimes最大尝试次数;createTokenFunction负责生成;checkTokenFunction负责校验唯一性(返回 true 表示可用)。
框架内真实调用链:StpUtil.login()创建新 token 时会执行该策略(见 StpLogic.java),传入的checkTokenFunction为getLoginIdNotHandle(tokenValue) == null,即"该 token 在数据源中查不到任何账号"才认为可用;该策略同样被用于 SSO 的 ticket、临时 token(SaTempTemplate)等"需要保证唯一性"的元素的生成。
测试验证(见 SaStrategyTest.java):
- 生成器依次产出
tk-1/tk-2/tk-3,校验器要求以3结尾,最终返回tk-3; - 固定生成
"same"且校验器永远返回 false、maxTryTimes=2时,抛出SaTokenException; maxTryTimes=-1时跳过校验直接返回。
2.6 autoRenew:是否自动续期 active-timeout 的策略
- 函数签名:
Function<StpLogic, Boolean>,参数为当前StpLogic实例,返回true自动续期、false不自动续期; - 重写入口:
setAutoRenew(...)。
默认实现stpLogic -> stpLogic.getConfigOrGlobal().getAutoRenew()(见 SaStrategy.java),即直接读取配置项autoRenew(默认开启)。文档特别强调:"每次续期前都会执行,可以加入动态判断逻辑"——这意味着该策略的执行频率很高,可用于在续期前加入自定义业务判断。
典型应用:例如要求"仅在工作时间自动续期"或"VIP 账号自动续期、普通账号不续期":
SaStrategy.instance.setAutoRenew((stpLogic) -> { // 例:读配置为 true,但可附加业务动态判断 return stpLogic.getConfigOrGlobal().getAutoRenew(); });该策略在 StpLogic.java 附近被调用,作为会话活跃度续期的开关。测试autoRenew_createStpLogic_andNotImplStrategies验证了配置autoRenew=false时策略返回false(见 SaStrategyTest.java)。
2.7 createStpLogic:创建 StpLogic 的算法
- 函数签名:
SaCreateStpLogicFunction,参数为[账号体系标识 loginType],返回创建好的StpLogic对象; - 重写入口:
setCreateStpLogic(...)。
默认实现loginType -> new StpLogic(loginType)(见 SaStrategy.java)。StpLogic是每个账号体系(如login、user、admin)的逻辑封装,Sa-Token 支持多账号体系并行鉴权,此策略决定了每个loginType对应的StpLogic如何创建。
典型应用:当项目继承StpLogic并扩展方法时,通过重写此策略让所有账号体系都使用自定义子类:
SaStrategy.instance.setCreateStpLogic((loginType) -> { return new MyStpLogic(loginType); // MyStpLogic 继承 StpLogic });测试中验证了默认实现创建的StpLogic能正确携带loginType,且链式 set 后可生成"chain-app"这种带前缀的 loginType(见 SaStrategyTest.java)。
2.8 routeMatcher:路由匹配策略
- 函数签名:
SaRouteMatchFunction,参数为[pattern, path],返回是否匹配; - 重写入口:
SaStrategy.instance.routeMatcher = ...(文档未单独提供 set 方法,需直接字段赋值)。
这是少数默认未实现的策略之一:默认实现直接抛出NotImplException,错误码CODE_12401("未实现具体路由匹配策略",见 SaStrategy.java)。它由SaRouter路由拦截模块消费(SaRouter.java),具体实现由各集成插件(Servlet、WebFlux、Solon 等)注入。
测试中确认默认状态抛出NotImplException(见 SaStrategyTest.java)。普通业务开发者一般不需要修改此策略,除非要定制路由通配符语法。
2.9 corsHandle:CORS 策略处理函数
- 函数签名:
SaCorsHandleFunction,参数为[请求包装对象, 响应包装对象, 数据读写对象]; - 重写入口:
SaStrategy.instance.corsHandle = ...(直接字段赋值)。
默认实现为空操作(req, res, sto) -> {}(见 SaStrategy.java),即默认不处理 CORS,测试验证空实现可正常执行、不抛异常(见 SaStrategyTest.java)。当项目需要基于请求/响应包装对象动态写入 CORS 响应头时,可在此策略内完成。更常见的 CORS 解决方案可参考仓库文档中的全局过滤器配置(见 sa-token-doc/up/global-filter.md)。
2.10 getSaTokenConfig:获取 SaTokenConfig 的策略
- 函数签名:
SaGetSaTokenConfigFunction,其本质是Supplier<SaTokenConfig>(见 SaGetSaTokenConfigFunction.java); - 默认值:
null,表示不启用,使用框架内置逻辑; - 重写入口:
setGetSaTokenConfig(...)。
这是文档重点介绍、也最具实战价值的策略,详见下一节专章。
补充:未列出的其他策略:源码中还定义了
createSaRequest/createSaResponse/createSaStorage三个上下文对象创建策略(分别对应CODE_12402/CODE_12403/CODE_12404,默认抛NotImplException,由各集成层实现,见 SaStrategy.java)。它们不属于业务定制范围,通常无需修改。
三、getSaTokenConfig:从外部数据源动态读取配置
3.1 默认配置加载链路
当getSaTokenConfig为null时,SaManager.getConfig()走框架内置逻辑(见 SaManager.java):
- 若全局
config字段已初始化,直接返回; - 若为空,在类锁内通过
SaTokenConfigFactory.createConfig()创建配置(默认读取sa-token.properties等配置文件); - 返回并缓存到
config字段。
3.2 启用自定义策略后的行为变化
一旦执行setGetSaTokenConfig(...)赋值(非 null),SaManager.getConfig()的每次调用都会直接执行此策略并返回其结果,完全绕过内置加载链路(见 SaManager.java 的优先分支)。测试setCreateContextAndConfigChainMethods验证了策略返回的SaTokenConfig会被原样读取(见 SaStrategyTest.java)。
3.3 官方使用示例(文档原版,可直接复制运行)
SaStrategy.instance.setGetSaTokenConfig(() -> { // 从数据库读取配置,自行做好缓存 SaTokenConfig config = new SaTokenConfig(); config.setTokenName("satoken"); config.setTimeout(30 * 24 * 60 * 60); return config; });3.4 两条强制注意点
根据文档与源码双重确认,使用该策略必须遵守:
- 严禁递归调用:策略内部不要调用
SaManager.getConfig(),否则每次调用都会再次进入本策略,形成无限递归(源码注释与 SaGetSaTokenConfigFunction.java 均明确警告); - 必须自行缓存:启用后
SaManager.getConfig()每次调用都会执行策略,若每次都在策略内查询数据库,将带来明显性能开销。建议在策略内加一层缓存(内存缓存、Caffeine 等),并在配置变更时主动刷新。
典型应用场景:需要将 token 有效期、token 名称等配置动态化管理(存于数据库、配置中心),改动后无需重启应用即可生效。这是"动态配置"需求的标准落地方式,官方描述为"适用于需要从数据库等外部数据源动态读取配置的场景"。
四、重写策略:set 连缀风格与实操要点
4.1 所有重写入口一览
文档给出 7 个 set 方法,全部采用"返回this"的连缀风格(源码见 SaStrategy.java),可一行链式重写多个策略:
SaStrategy.instance .setCreateToken(createToken) // 重写创建 Token 的策略 .setCreateSession(createSession) // 重写创建 Session 的策略 .setHasElement(hasElement) // 重写集合模糊匹配策略 .setGenerateUniqueToken(generateUniqueToken)// 重写生成唯一 token 的策略 .setCreateStpLogic(createStpLogic) // 重写创建 StpLogic 的策略 .setAutoRenew(autoRenew) // 重写是否自动续期策略 .setGetSaTokenConfig(getSaTokenConfig); // 重写获取 SaTokenConfig 的策略除文档列出的 7 个方法外,源码还提供setCreateSaRequest、setCreateSaResponse、setCreateSaStorage三个上下文创建策略的重写方法(见 SaStrategy.java),主要用于集成层定制。
4.2 链式调用的测试验证
测试 SaStrategyTest.java 验证了链式 set 的行为:
- 每个 set 方法返回的都是
SaStrategy自身(assertSame(strategy, strategy.setGenerateUniqueToken(...))); - 替换后,
createToken返回自定义值"custom-token"、createSession生成"custom-sid"前缀 Session、hasElement恒为true、autoRenew侧生效(AtomicBoolean被置为 true)、createStpLogic生成"chain-app"。
4.3 实操要点
- 重写位置:建议在应用启动阶段(如 Spring Boot 的
@Configuration类、CommandLineRunner或静态初始化块)统一完成重写,避免运行时动态更换导致行为不一致; - 全局生效:
instance是全局单例,任何位置的重写都会影响整个框架所有账号体系,重写前请确认不会影响其他模块(如 SSO、OAuth2 插件同样消费这些策略); - 字段 vs 方法:
createToken等策略既有公开字段也有 set 方法;sessionClassType、routeMatcher、corsHandle没有对应 set 方法,需直接字段赋值。新代码统一推荐使用setXxx方法 +instance(me已废弃)。
五、完整落地示例:一个"自定义 Token + 动态配置"的综合配置类
综合以上策略,给出一个完整的可运行示例(以 Spring Boot 风格为例),一次性演示createToken、hasElement、getSaTokenConfig三个策略的组合使用:
import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.strategy.SaStrategy; import org.springframework.context.annotation.Configuration; @Configuration public class SaTokenStrategyConfig { public SaTokenStrategyConfig() { // 1、自定义 Token 生成算法:业务前缀 + 时间戳 SaStrategy.instance.setCreateToken((loginId, loginType) -> "MY-" + loginType + "-" + loginId + "-" + System.currentTimeMillis() ); // 2、定制权限模糊匹配:追加自定义通配规则 SaStrategy.instance.setHasElement((list, element) -> { if (list == null || list.size() == 0) { return false; } if (list.contains(element)) { return true; } // 追加一条自定义规则:element 以 list 中任一元素开头即视为匹配 for (String patt : list) { if (element.startsWith(patt)) { return true; } } return false; }); // 3、从数据库动态读取配置(内部需做好缓存,严禁回调 SaManager.getConfig()) SaStrategy.instance.setGetSaTokenConfig(() -> { SaTokenConfig config = new SaTokenConfig(); config.setTokenName("satoken"); config.setTimeout(30 * 24 * 60 * 60); // 30 天,单位:秒 // 实际项目中可改为从数据库/配置中心读取 return config; }); } }六、小结:策略层定位与适用边界
- 定位:SaStrategy 是 Sa-Token 核心逻辑的代理封装层,把分散的算法收敛为可替换的函数式策略字段,框架内部所有关键调用点(Token 创建、Session 创建、权限匹配、唯一性生成、活跃续期、配置读取等)都统一经过该单例;
- 适用场景:自定义 Token 格式(
createToken)、自定义 Session 类型(createSession+sessionClassType)、定制权限通配规则(hasElement)、控制自动续期行为(autoRenew)、多账号体系定制(createStpLogic)、外部数据源动态配置(getSaTokenConfig); - 不适用边界:
routeMatcher、createSaRequest/createSaResponse/createSaStorage属于集成层职责,默认抛NotImplException,由各集成插件实现,业务层无需(也不应)随意替换。
延伸阅读:关于SaTokenConfig各配置项(如tokenStyle、autoRenew、timeout)的完整说明,可参见 sa-token-doc/use/config.md;Session 相关 API 见 sa-token-doc/api/sa-session.md;DAO 数据源扩展见 sa-token-doc/api/sa-token-dao.md。
【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考