From 34f8a60b23866bb9de586a59fa7c3571a74373bb Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Thu, 10 Apr 2025 10:44:39 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=8D=87=E7=BA=A7=E2=80=9C=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=20SaTokenContext=20=E6=8C=87=E5=8D=97?= =?UTF-8?q?=E2=80=9D=E7=AB=A0=E8=8A=82=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/fun/sa-token-context--backup.md | 180 ++++++++++++++++++ sa-token-doc/fun/sa-token-context.md | 183 ++++++------------- 2 files changed, 237 insertions(+), 126 deletions(-) create mode 100644 sa-token-doc/fun/sa-token-context--backup.md diff --git a/sa-token-doc/fun/sa-token-context--backup.md b/sa-token-doc/fun/sa-token-context--backup.md new file mode 100644 index 00000000..32d3b58b --- /dev/null +++ b/sa-token-doc/fun/sa-token-context--backup.md @@ -0,0 +1,180 @@ +# 自定义 SaTokenContext 指南 + +目前 Sa-Token 仅对 SpringBoot、SpringMVC、WebFlux、Solon 等部分 Web 框架制作了 Starter 集成包, +如果我们使用的 Web 框架不在上述列表之中,则需要自定义 SaTokenContext 接口的实现完成整合工作。 + +--- + +### 1、SaTokenContext是什么,为什么要实现 SaTokenContext 接口? + +在鉴权中,必不可少的步骤就是从 `HttpServletRequest` 中读取 Token,然而并不是所有框架都具有 HttpServletRequest 对象,例如在 WebFlux 中,只有 `ServerHttpRequest`, +在一些其它Web框架中,可能连 `Request` 的概念都没有。 + +那么,Sa-Token 如何只用一套代码就对接到所有 Web 框架呢? + +解决这个问题的关键就在于 `SaTokenContext` 接口,此接口的作用是屏蔽掉不同 Web 框架之间的差异,提供统一的调用API: + +![sa-token-context](https://oss.dev33.cn/sa-token/doc/sa-token-context.svg 's-w') + + +SaTokenContext只是一个接口,没有工作能力,这也就意味着 SaTokenContext 接口的实现是必须的。 +那么疑问来了,我们之前在 SpringBoot 中引用 Sa-Token 时为什么可以直接使用呢? + +其实原理很简单,`sa-token-spring-boot-starter`集成包中已经内置了`SaTokenContext`的实现:[SaTokenContextForSpring](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenContextForSpring.java), +并且根据 Spring 的自动注入特性,在项目启动时注入到 Sa-Token 中,做到“开箱即用”。 + +那么如果我们使用不是 Spring 框架,是不是就必须得手动实现 `SaTokenContext` 接口?答案是肯定的,脱离Spring 环境后,我们就不能再使用`sa-token-spring-boot-starter`集成包了, +此时我们只能引入 `sa-token-core` 核心包,然后手动实现 `SaTokenContext` 接口。 + +不过不用怕,这个工作很简单,只要跟着下面的文档一步步来,你就可以将 Sa-Token 对接到任意Web框架中。 + + +### 2、实现 Model 接口 +我们先来观察一下 `SaTokenContext` 接口的签名: +``` java +/** + * Sa-Token 上下文处理器 + */ +public interface SaTokenContext { + + /** + * 获取当前请求的 [Request] 对象 + */ + public SaRequest getRequest(); + + /** + * 获取当前请求的 [Response] 对象 + */ + public SaResponse getResponse(); + + /** + * 获取当前请求的 [存储器] 对象 + */ + public SaStorage getStorage(); + + /** + * 校验指定路由匹配符是否可以匹配成功指定路径 + */ + public boolean matchPath(String pattern, String path); + +} +``` + +你可能对 `SaRequest` 比较疑惑,这个对象是干什么用的?正如每个 Web 框架都有 Request 概念的抽象,Sa-Token 也封装了 `Request`、`Response`、`Storage`三者的抽象: + +- `Request`:请求对象,携带着一次请求的所有参数数据。参考:[SaRequest.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java)。 +- `Response`:响应对象,携带着对客户端一次响应的所有数据。参考:[SaResponse.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaResponse.java)。 +- `Storage`:请求上下文对象,提供 [一次请求范围内] 的上下文数据读写。参考:[SaStorage.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaStorage.java)。 + + +因此,在实现 `SaTokenContext` 之前,你必须先实现这三个 Model 接口。 + +先别着急动手,如果你的 Web 框架是基于 Servlet 规范开发的,那么 Sa-Token 已经为你封装好了三个 Model 接口的实现,你要做的就是引入 `sa-token-servlet`包即可: + + + +``` xml + + + cn.dev33 + sa-token-servlet + ${sa.top.version} + +``` + +``` gradle +// Sa-Token 权限认证(ServletAPI 集成包) +implementation 'cn.dev33:sa-token-servlet:${sa.top.version}' +``` + + + +如果你的 Web 框架不是基于 Servlet 规范,那么你就需要手动实现这三个 Model 接口,我们可以参考 `sa-token-servlet` 是怎样实现的: +[SaRequestForServlet.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java)、 +[SaResponseForServlet.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaResponseForServlet.java)、 +[SaStorageForServlet.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaStorageForServlet.java)。 + + +### 3、实现 SaTokenContext 接口 + +接下来我们奔入主题,提供 `SaTokenContext` 接口的实现,同样我们可以参考 Spring 集成包是怎样实现的: + +``` java +/** + * Sa-Token 上下文处理器 [ SpringMVC版本实现 ] + */ +public class SaTokenContextForSpring implements SaTokenContext { + + /** + * 获取当前请求的Request对象 + */ + @Override + public SaRequest getRequest() { + return new SaRequestForServlet(SpringMVCUtil.getRequest()); + } + + /** + * 获取当前请求的Response对象 + */ + @Override + public SaResponse getResponse() { + return new SaResponseForServlet(SpringMVCUtil.getResponse()); + } + + /** + * 获取当前请求的 [存储器] 对象 + */ + @Override + public SaStorage getStorage() { + return new SaStorageForServlet(SpringMVCUtil.getRequest()); + } + + /** + * 校验指定路由匹配符是否可以匹配成功指定路径 + */ + @Override + public boolean matchPath(String pattern, String path) { + return SaPathMatcherHolder.getPathMatcher().match(pattern, path); + } + +} +``` + +详细参考: +[SaTokenContextForSpring.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenContextForSpring.java) + + +### 4、将自定义实现注入到 Sa-Token 框架中 + +有了 `SaTokenContext` 接口的实现,我们还需要将这个实现类注入到 Sa-Token 之中,伪代码参考如下: +``` java +/** + * 程序启动类 + */ +public class Application { + + public static void main(String[] args) { + // 框架启动 + XxxApplication.run(xxx); + + // 将自定义的 SaTokenContext 实现类注入到框架中 + SaTokenContext saTokenContext = new SaTokenContextForXxx(); + SaManager.setSaTokenContext(saTokenContext); + } + +} +``` + +如果你使用的框架带有自动注入特性,那就更简单了,参考 Spring 集成包的 Bean 注入流程: +[注册Bean](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenContextRegister.java)、 +[注入Bean](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/SaBeanInject.java) + + +### 5、启动项目 + +启动项目,尝试打印一下 `SaManager.getSaTokenContext()` 对象,如果输出的是你的自定义实现类,那就证明你已经自定义 `SaTokenContext` 成功了, +快来体验一下 Sa-Token 的各种功能吧。 + + + + diff --git a/sa-token-doc/fun/sa-token-context.md b/sa-token-doc/fun/sa-token-context.md index 32d3b58b..6fcd9c39 100644 --- a/sa-token-doc/fun/sa-token-context.md +++ b/sa-token-doc/fun/sa-token-context.md @@ -1,75 +1,70 @@ # 自定义 SaTokenContext 指南 目前 Sa-Token 仅对 SpringBoot、SpringMVC、WebFlux、Solon 等部分 Web 框架制作了 Starter 集成包, -如果我们使用的 Web 框架不在上述列表之中,则需要自定义 SaTokenContext 接口的实现完成整合工作。 +如果我们使用的 Web 框架不在上述列表之中,则需要自定义 SaTokenContext 相关接口完成整合工作。 + +我们需要关注的主要就是四个接口: + +- SaTokenContext:上下文管理器。 +- SaRequest:请求对象,携带着一次请求的所有参数数据。 +- SaResponse:响应对象,携带着对客户端一次响应的所有数据。 +- SaStorage:请求上下文对象,提供 [一次请求范围内] 的上下文数据读写。 --- -### 1、SaTokenContext是什么,为什么要实现 SaTokenContext 接口? -在鉴权中,必不可少的步骤就是从 `HttpServletRequest` 中读取 Token,然而并不是所有框架都具有 HttpServletRequest 对象,例如在 WebFlux 中,只有 `ServerHttpRequest`, -在一些其它Web框架中,可能连 `Request` 的概念都没有。 +### 上下文包装类 -那么,Sa-Token 如何只用一套代码就对接到所有 Web 框架呢? +在鉴权中,必不可少的步骤就是从 `HttpServletRequest` 中读取 Token,然而当我们调用 `StpUtil.isLogin()` 获取当前会话是否登录时, +我们并没有传递 `HttpServletRequest` 参数,框架是怎么读取出来 Token 的呢? -解决这个问题的关键就在于 `SaTokenContext` 接口,此接口的作用是屏蔽掉不同 Web 框架之间的差异,提供统一的调用API: +以 SpringBoot 项目为例,Sa-Token 框架会自动注册一个全局过滤器,在每次接收到请求时,将 `HttpServletRequest` 对象保存在 `ThreadLocal` 之中。 -![sa-token-context](https://oss.dev33.cn/sa-token/doc/sa-token-context.svg 's-w') +在后续的方法中,如果你调用了 `StpUtil.isLogin()` 等方法,框架便会从 `ThreadLocal` 中获取 `HttpServletRequest` 对象,从而进一步读取 token 等信息。 +让我们来看一下具体的代码细节,全局上下文初始化过滤器: -SaTokenContext只是一个接口,没有工作能力,这也就意味着 SaTokenContext 接口的实现是必须的。 -那么疑问来了,我们之前在 SpringBoot 中引用 Sa-Token 时为什么可以直接使用呢? - -其实原理很简单,`sa-token-spring-boot-starter`集成包中已经内置了`SaTokenContext`的实现:[SaTokenContextForSpring](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenContextForSpring.java), -并且根据 Spring 的自动注入特性,在项目启动时注入到 Sa-Token 中,做到“开箱即用”。 - -那么如果我们使用不是 Spring 框架,是不是就必须得手动实现 `SaTokenContext` 接口?答案是肯定的,脱离Spring 环境后,我们就不能再使用`sa-token-spring-boot-starter`集成包了, -此时我们只能引入 `sa-token-core` 核心包,然后手动实现 `SaTokenContext` 接口。 - -不过不用怕,这个工作很简单,只要跟着下面的文档一步步来,你就可以将 Sa-Token 对接到任意Web框架中。 - - -### 2、实现 Model 接口 -我们先来观察一下 `SaTokenContext` 接口的签名: ``` java /** - * Sa-Token 上下文处理器 + * SaTokenContext 上下文初始化过滤器 (基于 Servlet) */ -public interface SaTokenContext { - - /** - * 获取当前请求的 [Request] 对象 - */ - public SaRequest getRequest(); - - /** - * 获取当前请求的 [Response] 对象 - */ - public SaResponse getResponse(); - - /** - * 获取当前请求的 [存储器] 对象 - */ - public SaStorage getStorage(); - - /** - * 校验指定路由匹配符是否可以匹配成功指定路径 - */ - public boolean matchPath(String pattern, String path); - +@Order(SaTokenConsts.SA_TOKEN_CONTEXT_FILTER_ORDER) +public class SaTokenContextFilterForServlet implements Filter { + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + try { + SaTokenContextServletUtil.setContext((HttpServletRequest) request, (HttpServletResponse) response); + chain.doFilter(request, response); + } finally { + SaTokenContextServletUtil.clearContext(); + } + } } ``` -你可能对 `SaRequest` 比较疑惑,这个对象是干什么用的?正如每个 Web 框架都有 Request 概念的抽象,Sa-Token 也封装了 `Request`、`Response`、`Storage`三者的抽象: +进一步追踪 `SaTokenContextServletUtil.setContext` 方法: -- `Request`:请求对象,携带着一次请求的所有参数数据。参考:[SaRequest.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java)。 -- `Response`:响应对象,携带着对客户端一次响应的所有数据。参考:[SaResponse.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaResponse.java)。 -- `Storage`:请求上下文对象,提供 [一次请求范围内] 的上下文数据读写。参考:[SaStorage.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaStorage.java)。 +``` java +public static void setContext(HttpServletRequest request, HttpServletResponse response) { + SaRequest req = new SaRequestForServlet(request); + SaResponse res = new SaResponseForServlet(response); + SaStorage stg = new SaStorageForServlet(request); + SaManager.getSaTokenContext().setContext(req, res, stg); +} +``` +此处有一个细节,为什么保存的不是原生 `HttpServletRequest` 与 `HttpServletResponse`,而是 `SaRequest`、`SaResponse`、`SaStorage` 三个包装对象? -因此,在实现 `SaTokenContext` 之前,你必须先实现这三个 Model 接口。 +因为并不是所有的 web 框架都具有 `HttpServletRequest` 对象,例如在 WebFlux 中,只有 `ServerHttpRequest`, +在一些其它Web框架中,可能连 `Request` 的概念都没有。 -先别着急动手,如果你的 Web 框架是基于 Servlet 规范开发的,那么 Sa-Token 已经为你封装好了三个 Model 接口的实现,你要做的就是引入 `sa-token-servlet`包即可: +Sa-Token 为了一套代码对接所有的 Web 框架,就在原生请求对象的基础上又封装了一层 `SaTokenContext` 相关接口,用于屏蔽掉不同 Web 框架之间的差异,提供统一的调用API: + +![sa-token-context](https://oss.dev33.cn/sa-token/doc/plugin/sa-token-context-2.svg) + +因此,要对接不同的 Web 框架,就要针对不同的 Web 框架封装不同版本的 `SaRequest`、`SaResponse`、`SaStorage` 包装类对象。 + +如果你的 Web 框架是基于 Servlet 规范开发的,那么你可以直接引入 `sa-token-servlet`,这个包封装了针对 Servlet 规范的上下文包装类对象: @@ -89,92 +84,28 @@ implementation 'cn.dev33:sa-token-servlet:${sa.top.version}' -如果你的 Web 框架不是基于 Servlet 规范,那么你就需要手动实现这三个 Model 接口,我们可以参考 `sa-token-servlet` 是怎样实现的: +如果你的 web 框架不是基于 Servlet 规范开发的,也问题不大,手动实现一下即可,参考一下 Servlet 包是怎么做的: [SaRequestForServlet.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java)、 [SaResponseForServlet.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaResponseForServlet.java)、 [SaStorageForServlet.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaStorageForServlet.java)。 - -### 3、实现 SaTokenContext 接口 - -接下来我们奔入主题,提供 `SaTokenContext` 接口的实现,同样我们可以参考 Spring 集成包是怎样实现的: +封装好包装类对象之后,接下来要做的就是在这个 Web 框架中注册一个全局过滤器,将包装类对象保存到“全局上下文管理器”之中,以备调用: ``` java -/** - * Sa-Token 上下文处理器 [ SpringMVC版本实现 ] - */ -public class SaTokenContextForSpring implements SaTokenContext { - - /** - * 获取当前请求的Request对象 - */ - @Override - public SaRequest getRequest() { - return new SaRequestForServlet(SpringMVCUtil.getRequest()); - } - - /** - * 获取当前请求的Response对象 - */ - @Override - public SaResponse getResponse() { - return new SaResponseForServlet(SpringMVCUtil.getResponse()); - } - - /** - * 获取当前请求的 [存储器] 对象 - */ - @Override - public SaStorage getStorage() { - return new SaStorageForServlet(SpringMVCUtil.getRequest()); - } - - /** - * 校验指定路由匹配符是否可以匹配成功指定路径 - */ - @Override - public boolean matchPath(String pattern, String path) { - return SaPathMatcherHolder.getPathMatcher().match(pattern, path); - } - -} +SaRequest req = new SaRequestForXxx(request); +SaResponse res = new SaResponseForXxx(response); +SaStorage stg = new SaStorageForXxx(request); +SaManager.getSaTokenContext().setContext(req, res, stg); ``` -详细参考: -[SaTokenContextForSpring.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenContextForSpring.java) +这样我们即可在具体的 Controller 请求中,成功调用 `StpUtil.isLogin()` 的 API。 - -### 4、将自定义实现注入到 Sa-Token 框架中 - -有了 `SaTokenContext` 接口的实现,我们还需要将这个实现类注入到 Sa-Token 之中,伪代码参考如下: -``` java -/** - * 程序启动类 - */ -public class Application { - - public static void main(String[] args) { - // 框架启动 - XxxApplication.run(xxx); - - // 将自定义的 SaTokenContext 实现类注入到框架中 - SaTokenContext saTokenContext = new SaTokenContextForXxx(); - SaManager.setSaTokenContext(saTokenContext); - } - -} -``` - -如果你使用的框架带有自动注入特性,那就更简单了,参考 Spring 集成包的 Bean 注入流程: -[注册Bean](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenContextRegister.java)、 -[注入Bean](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/SaBeanInject.java) - - -### 5、启动项目 - -启动项目,尝试打印一下 `SaManager.getSaTokenContext()` 对象,如果输出的是你的自定义实现类,那就证明你已经自定义 `SaTokenContext` 成功了, -快来体验一下 Sa-Token 的各种功能吧。 +总结:整体的步骤并不复杂,就是先定义 `SaRequest`、`SaResponse`、`SaStorage` 的包装类,然后在全局过滤器保存在上下文管理器中。 +可以参考具体实现 `sa-token-spring-boot-starter`(SpringBoot2 项目 starter 包): +[SaTokenContextFilterForServlet.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/filter/SaTokenContextFilterForServlet.java) +### 旧版本方案 +在旧版本中(< v1.42.0)我们推荐的方案是自定义整个 `SaTokenCentext` 接口,目前此方案在新版本已不推荐,此处仅做留档备份:[自定义 SaTokenContext 指南](/fun/sa-token-context--backup.md)