Sa-Token 自定义 SaTokenContext 指南:将权限认证框架无缝对接任意 Web 框架 Sa-Token 自定义 SaTokenContext 指南将权限认证框架无缝对接任意 Web 框架【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token导读Sa-Token 官方仅对 SpringBoot、SpringMVC、WebFlux、Solon 等主流 Web 框架提供了 Starter 集成包当项目使用列表之外的框架如 JFinal、JBoot、LoveQQ 或自研框架时就需要手动实现SaTokenContext接口来完成整合。本文将基于当前仓库源码从接口职责、Model 抽象、Servlet 捷径、注入机制到启动验证手把手带你将 Sa-Token 对接到任意 Web 框架实现「一套鉴权代码处处开箱即用」。一、为什么需要实现 SaTokenContext 接口在权限认证中必不可少的步骤就是从当前请求中读取 Token、获取请求参数、读写响应头。然而不同 Web 框架对请求的抽象千差万别基于 Servlet 规范的框架SpringMVC 等提供HttpServletRequest响应式框架 WebFlux 只提供ServerHttpRequest还有一些框架甚至连Request的概念都没有。如果 Sa-Token 的每一行鉴权代码都直接依赖具体框架的对象那对接一个新框架就要重写一遍全部逻辑这显然不可行。解决这个问题的关键就在于SaTokenContext接口。它位于 sa-token-core/src/main/java/cn/dev33/satoken/context/SaTokenContext.java作用是屏蔽掉不同 Web 框架之间的差异向上层提供统一调用 API。Sa-Token 的所有核心功能登录认证、权限校验、路由拦截、Session 会话等只与这套抽象打交道从而做到只用一套代码对接到所有 Web 框架。SaTokenContext只是一个接口本身没有工作能力因此必须有一个实现类注入到框架中。这也就产生了一个疑问为什么在 SpringBoot 中引用 Sa-Token 时可以直接使用无需任何额外配置原理很简单sa-token-spring-boot-starter集成包中已经内置了SaTokenContext的实现 SaTokenContextForSpring.java并借助 Spring 的自动装配特性在项目启动时通过 SaTokenContextRegister.java 自动注入到 Sa-Token 中实现开箱即用。而一旦脱离 Spring 环境我们就不能再使用sa-token-spring-boot-starter集成包只能引入sa-token-core核心包手动实现SaTokenContext接口及其依赖的 Model 接口在应用启动时将实现类注入 Sa-Token。好在这个工作并不复杂下面一步步来。二、认识 SaTokenContext 接口职责与演进文档中展示了SaTokenContext接口的历史签名包含getRequest()、getResponse()、getStorage()、matchPath()四个方法。需要说明的是当前仓库中的SaTokenContext接口已经演进路由匹配方法matchPath已被拆分到独立组件接口职责更加聚焦于上下文的生命周期管理。当前源码中的完整接口定义如下SaTokenContext.javapublic interface SaTokenContext { /** * 初始化上下文 */ void setContext(SaRequest req, SaResponse res, SaStorage stg); /** * 清除化上下文 */ void clearContext(); /** * 判断当前上下文是否可用 */ boolean isValid(); /** * 获取 Box 对象 */ SaTokenContextModelBox getModelBox(); /** * 获取当前上下文的 Request 包装对象 */ default SaRequest getRequest() { return getModelBox().getRequest(); } /** * 获取当前上下文的 Response 包装对象 */ default SaResponse getResponse() { return getModelBox().getResponse(); } /** * 获取当前上下文的 Storage 包装对象 */ default SaStorage getStorage() { return getModelBox().getStorage(); } }几个关键点setContext / clearContext负责在一次请求开始与结束时写入/清理上下文Request、Response、Storage三者打包为SaTokenContextModelBoxisValid()判断当前上下文在本次请求中是否可用getRequest / getResponse / getStorage是default 方法默认从getModelBox()中取值实现类通常无需重写。仓库内置了两个可直接参考的默认实现实现类说明源码位置SaTokenContextForThreadLocalThreadLocal 版上下文通过SaTokenContextForThreadLocalStaff在过滤器/拦截器中读写上下文SaTokenContextForThreadLocal.javaSaTokenContextDefaultImpl兜底实现所有方法直接抛出未能获取有效的上下文处理器异常用于排查依赖问题SaTokenContextDefaultImpl.java特别是SaTokenContextDefaultImpl如果代码断点走到了这个默认实现通常说明引入的依赖有问题或错误的调用了 Sa-Token 的 API可以从依赖引入与上下文注入两个方向排查。三、实现三个 Model 接口SaRequest / SaResponse / SaStorage正如每个 Web 框架都有 Request 概念的抽象Sa-Token 也封装了Request、Response、Storage三者的抽象。这三个 Model 接口是SaTokenContext工作能力的具体载体SaRequest请求对象携带着一次请求的所有参数数据抽象了参数获取、请求头、Cookie、请求路径、URL、Method、Host、Ajax 判断、请求转发等能力见 SaRequest.javaSaResponse响应对象携带着对客户端一次响应的所有数据抽象了 Cookie 写入/删除、响应状态码、响应头、重定向等能力见 SaResponse.javaSaStorage存储器对象提供[一次请求范围内]的上下文数据读写数据在请求结束后失效见 SaStorage.java。因此在实现SaTokenContext之前必须先实现这三个 Model 接口。3.1 捷径基于 Servlet 规范直接引入 sa-token-servlet先别急着动手。如果你的 Web 框架是基于 Servlet 规范开发的Sa-Token 已经为你封装好了三个 Model 接口的完整实现只需引入sa-token-servlet包即可Maven 方式坐标与sa-token-core同属cn.dev33组!-- Sa-Token 权限认证ServletAPI 集成包 -- dependency groupIdcn.dev33/groupId artifactIdsa-token-servlet/artifactId version${sa.top.version}/version /dependencyGradle 方式// Sa-Token 权限认证ServletAPI 集成包 implementation cn.dev33:sa-token-servlet:${sa.top.version}sa-token-servlet模块位于 sa-token-starter/sa-token-servlet内部提供了三个基于javax.servlet.http.HttpServletRequest / HttpServletResponse的实现Model 接口Servlet 实现类SaRequestSaRequestForServlet.javaSaResponseSaResponseForServlet.javaSaStorageSaStorageForServlet.java以SaRequestForServlet为例实现非常直观——把SaRequest接口的每个抽象方法转发给底层HttpServletRequest即可public class SaRequestForServlet implements SaRequest { /** 底层Request对象 */ protected HttpServletRequest request; public SaRequestForServlet(HttpServletRequest request) { this.request request; } Override public Object getSource() { return request; } Override public String getParam(String name) { return request.getParameter(name); } Override public String getHeader(String name) { return request.getHeader(name); } Override public String getRequestPath() { return ApplicationInfo.cutPathPrefix(request.getRequestURI()); } // ... 其余方法类似可参考完整源码 }值得注意的是getUrl()的实现它会优先读取SaManager.getConfig().getCurrDomain()配置若配置了当前域名则使用配置域名 请求路径否则回退到request.getRequestURL()。这体现了 Model 层与配置体系的联动。3.2 非 Servlet 框架手动实现三个 Model如果 Web 框架不是基于 Servlet 规范例如 WebFlux 只有ServerHttpRequest那就需要参照sa-token-servlet的实现思路将三个接口的抽象方法逐一映射到目标框架的对应对象上。建议的映射参考getSource()返回目标框架的底层 Request/Response 原生对象getParam(name)映射到目标框架的参数读取 APIgetHeader(name)映射到目标框架的请求头读取 APIgetCookieValue(name)映射到目标框架的 Cookie 读取 APIgetRequestPath()/getUrl()/getMethod()/getHost()映射到目标框架的路径、URL、方法、主机信息setStatus(sc)/setHeader(name, value)/redirect(url)映射到目标框架的响应写入 APIget(key)/set(key, value)/delete(key)映射到目标框架的请求作用域存储如 attribute map。可以观察仓库中 WebFlux 集成包的做法作为非 Servlet的范本Reactor 版上下文实现 SaTokenContextForSpringReactor.java 就是在ServerHttpRequest / ServerHttpResponse之上完成同一套抽象的。四、实现 SaTokenContext 接口三个 Model 接口就绪后就可以实现SaTokenContext接口了。同样可以参考 Spring 集成包的做法。SpringMVC 版本的历史实现SaTokenContextForSpring.java展示了最经典的实现模式在getRequest()等方法中从当前请求上下文中取出底层对象再包上 Model 实现类返回public class SaTokenContextForSpring implements SaTokenContextForReadOnly { /** 获取当前请求的 Request 包装对象 */ Override public SaRequest getRequest() { return new SaRequestForServlet(SpringMVCUtil.getRequest()); } /** 获取当前请求的 Response 包装对象 */ Override public SaResponse getResponse() { return new SaResponseForServlet(SpringMVCUtil.getResponse()); } /** 获取当前请求的 Storage 包装对象 */ Override public SaStorage getStorage() { return new SaStorageForServlet(SpringMVCUtil.getRequest()); } /** 判断在本次请求中此上下文是否可用 */ Override public boolean isValid() { return SpringMVCUtil.isWeb(); } }参考这个模式对接任意框架Xxx的SaTokenContextForXxx大致骨架如下/** * Sa-Token 上下文处理器 [ Xxx框架版本实现 ] */ public class SaTokenContextForXxx implements SaTokenContext { /** 获取当前请求的 Request 包装对象 */ Override public SaRequest getRequest() { return new SaRequestForXxx(XxxContextUtil.getRequest()); } /** 获取当前请求的 Response 包装对象 */ Override public SaResponse getResponse() { return new SaResponseForXxx(XxxContextUtil.getResponse()); } /** 获取当前请求的 Storage 包装对象 */ Override public SaStorage getStorage() { return new SaStorageForXxx(XxxContextUtil.getRequest()); } /** 判断在本次请求中此上下文是否可用 */ Override public boolean isValid() { return XxxContextUtil.isWeb(); } }如果你的框架采用请求即线程模型每个请求独占一个线程还可以直接复用仓库内置的SaTokenContextForThreadLocal只要在全局过滤器或拦截器中率先调用SaTokenContextForThreadLocalStaff.setBox(req, res, sto)完成上下文初始化即可无需自己再写一套存储逻辑。五、将自定义实现注入 Sa-Token 框架有了SaTokenContext的实现类还必须将其注入到 Sa-Token 中Sa-Token 核心 API 才会使用它。注入的入口是全局管理器SaManagerSaManager.javaprivate volatile static SaTokenContext saTokenContext; public static void setSaTokenContext(SaTokenContext saTokenContext) { SaManager.saTokenContext saTokenContext; SaTokenEventCenter.doRegisterComponent(SaTokenContext, saTokenContext); } public static SaTokenContext getSaTokenContext() { if (saTokenContext null) { synchronized (SaManager.class) { if (saTokenContext null) { SaManager.saTokenContext new SaTokenContextForThreadLocal(); } } } return saTokenContext; }从源码可以看到两个要点setSaTokenContext(...)会通过SaTokenEventCenter.doRegisterComponent(...)发布组件注册事件便于监听器感知上下文组件的变化getSaTokenContext()带有double-check 懒加载若从未注入会默认返回SaTokenContextForThreadLocalThreadLocal 版本而不是抛异常。这意味着不注入也能跑但如果没有配套的上下文初始化运行时就会因为缺少上下文数据而失败。5.1 手动注入启动时调用 setSaTokenContext对于没有自动装配特性的框架在程序启动入口手动注入即可伪代码参考如下/** * 程序启动类 */ public class Application { public static void main(String[] args) { // 框架启动 XxxApplication.run(xxx); // 将自定义的 SaTokenContext 实现类注入到框架中 SaTokenContext saTokenContext new SaTokenContextForXxx(); SaManager.setSaTokenContext(saTokenContext); } }5.2 自动注入借助框架的 Bean 机制如果你使用的框架带有自动注入特性如 Spring 的 IOC 容器那就更简单了。参考 Spring 集成包的两步式流程注册 BeanSaTokenContextRegister.java 负责把SaTokenContextForSpring声明为 Bean交给容器管理注入 Bean容器启动完成后把该 Bean 通过SaManager.setSaTokenContext(...)注入到 Sa-Token 中。对应地在你的框架中只需把注册实现类和调用SaManager.setSaTokenContext(...)两个动作放到框架的初始化回调或 Bean 装配阶段即可实现真正的自动注入、开箱即用。六、启动项目验证注入是否成功完成以上步骤后启动项目并打印一下SaManager.getSaTokenContext()System.out.println(SaManager.getSaTokenContext());如果控制台输出的是你的自定义实现类如cn.dev33.satoken.context.SaTokenContextForXxxxxxx就证明自定义SaTokenContext已经成功注入接下来即可体验 Sa-Token 的登录认证、权限校验、路由拦截、Session 会话等全部功能。若打印出来的是SaTokenContextForThreadLocal或抛出了SaTokenContextException错误码10001未能获取有效的上下文处理器则说明注入没有生效可从以下方向排查是否在sa-token-core之外错误地引入了与目标框架不匹配的 Starter 包例如在非 Spring 项目中引入sa-token-spring-boot-starter自定义实现类是否真的被实例化并调用了SaManager.setSaTokenContext(...)是否在请求处理链路过滤器/拦截器中正确完成了上下文的setContext初始化。七、进阶借助 Mock 上下文在非 Web 环境调试在完成框架对接的过程中你可能会遇到脱离 Web 容器进行单元测试的需求。仓库为此内置了Mock 上下文工具SaTokenContextMockUtil.java// 写入 Mock 上下文并执行一段代码执行完毕后自动清除上下文 SaTokenContextMockUtil.setMockContext(() - { // 在这里调用 StpUtil 等鉴权 API不再依赖真实 Web 请求 String token StpUtil.getTokenValue(); // ... });其原理是构造SaRequestForMock、SaResponseForMock、SaStorageForMock三个内存版 Mock 对象见 mock 包调用SaManager.getSaTokenContext().setContext(request, response, storage)写入并在finally块中clearContext()清理。核心测试可参考 SaTokenContextMockUtilTest.java。这意味着只要你的SaTokenContext实现支持setContext配合 Mock 上下文工具就能在完全无 Web 容器的环境下驱动 Sa-Token 全链路逻辑为对接新框架后的功能验证提供了极大便利。八、小结自定义SaTokenContext本质上是完成三件事实现三个 Model 接口SaRequest、SaResponse、SaStorage基于 Servlet 规范直接引入sa-token-servlet否则参照 SaRequestForServlet.java 等实现手动适配实现SaTokenContext接口参照 SaTokenContextForSpring.java 的模式从框架上下文中取出底层对象并包装返回注入 Sa-Token调用SaManager.setSaTokenContext(...)或借助框架的 Bean 机制自动注入。完成这步后Sa-Token 便与目标 Web 框架完成解耦式对接——框架差异被SaTokenContext这一层抽象彻底屏蔽而上层的登录、鉴权、会话等所有能力原样可用。这也正是 Sa-Token 能够仅靠核心包 一个上下文实现就横行于任意 Web 框架的根本原因。参考资源仓库内核心接口SaTokenContext.javaSaRequest.javaSaResponse.javaSaStorage.java官方实现SaTokenContextForSpring.javaSaTokenContextForSpringReactor.javaServlet 适配sa-token-servlet 模块注入与生命周期SaManager.javaSaTokenContextForThreadLocal.javaMock 上下文SaTokenContextMockUtil.javaSaTokenContextMockUtilTest.java测试参考SaTokenContextForSpringTest.javaSaTokenContextRegisterTest.java【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考