优化文档

This commit is contained in:
click33
2022-07-04 11:48:29 +08:00
parent c3275aeba6
commit 516e52c649
14 changed files with 199 additions and 129 deletions

View File

@@ -6,14 +6,14 @@
之前的章节中我们学习了“根据拦截器实现路由拦截鉴权”其实在大多数web框架中使用过滤器可以实现同样的功能本章我们就利用Sa-Token全局过滤器来实现路由拦截器鉴权。 之前的章节中我们学习了“根据拦截器实现路由拦截鉴权”其实在大多数web框架中使用过滤器可以实现同样的功能本章我们就利用Sa-Token全局过滤器来实现路由拦截器鉴权。
首先我们先梳理清楚一个问题,既然拦截器已经可以实现路由鉴权,为什么还要用过滤器再实现一遍呢?简而言之: 首先我们先梳理清楚一个问题,既然拦截器已经可以实现路由鉴权,为什么还要用过滤器再实现一遍呢?简而言之:
1. 相比于拦截器,过滤器更加底层,执行时机更靠前,有利于防渗透扫描 1. 相比于拦截器,过滤器更加底层,执行时机更靠前,有利于防渗透扫描
2. 过滤器可以拦截静态资源,方便我们做一些权限控制 2. 过滤器可以拦截静态资源,方便我们做一些权限控制
3. 部分Web框架根本就没有提供拦截器功能但几乎所有的Web框架都会提供过滤器机制 3. 部分Web框架根本就没有提供拦截器功能但几乎所有的Web框架都会提供过滤器机制
但是过滤器也有一些缺点,比如: 但是过滤器也有一些缺点,比如:
1. 由于太过底层,导致无法率先拿到`HandlerMethod`对象,无法据此添加一些额外功能 1. 由于太过底层,导致无法率先拿到`HandlerMethod`对象,无法据此添加一些额外功能
2. 由于拦截的太全面了,导致我们需要对很多特殊路由(如`/favicon.ico`)做一些额外处理 2. 由于拦截的太全面了,导致我们需要对很多特殊路由(如`/favicon.ico`)做一些额外处理
3. 在Spring中过滤器中抛出的异常无法进入全局`@ExceptionHandler`,我们必须额外编写代码进行异常处理 3. 在Spring中过滤器中抛出的异常无法进入全局`@ExceptionHandler`,我们必须额外编写代码进行异常处理
Sa-Token同时提供过滤器和拦截器机制不是为了让谁替代谁而是为了让大家根据自己的实际业务合理选择拥有更多的发挥空间。 Sa-Token同时提供过滤器和拦截器机制不是为了让谁替代谁而是为了让大家根据自己的实际业务合理选择拥有更多的发挥空间。
@@ -75,9 +75,9 @@ public class SaTokenConfigure {
``` ```
##### 注意事项: ##### 注意事项:
- 在`[认证函数]`里,你可以写和拦截器里一致的代码,进行路由匹配鉴权,参考:[路由拦截鉴权](/use/route-check) - 在`[认证函数]`里,你可以写和拦截器里一致的代码,进行路由匹配鉴权,参考:[路由拦截鉴权](/use/route-check)
- 由于过滤器中抛出的异常不进入全局异常处理,所以你必须提供`[异常处理函数]`来处理`[认证函数]`里抛出的异常 - 由于过滤器中抛出的异常不进入全局异常处理,所以你必须提供`[异常处理函数]`来处理`[认证函数]`里抛出的异常
- 在`[异常处理函数]`里的返回值,将作为字符串输出到前端,如果需要定制化返回数据,请注意其中的格式转换 - 在`[异常处理函数]`里的返回值,将作为字符串输出到前端,如果需要定制化返回数据,请注意其中的格式转换
改写 `setError` 函数的响应格式示例: 改写 `setError` 函数的响应格式示例:
``` java ``` java
@@ -93,7 +93,7 @@ JSON 工具类可参考:[Hutool-Json](https://hutool.cn/docs/#/json/JSONUtil)
### 在 WebFlux 中注册过滤器 ### 在 WebFlux 中注册过滤器
`Spring WebFlux`中不提供拦截器机制,因此若你的项目需要路由鉴权功能,过滤器是你唯一的选择,在`Spring WebFlux`注册过滤器的流程与上述流程几乎完全一致, `Spring WebFlux`中不提供拦截器机制,因此若你的项目需要路由鉴权功能,过滤器是你唯一的选择,在`Spring WebFlux`注册过滤器的流程与上述流程几乎完全一致,
除了您需要将过滤器名称由`SaServletFilter`更换为`SaReactorFilter`以外,其它所有步骤均可参考以上示例 除了您需要将过滤器名称由`SaServletFilter`更换为`SaReactorFilter`以外,其它所有步骤均可参考以上示例
``` java ``` java
/** /**
* [Sa-Token 权限认证] 配置类 * [Sa-Token 权限认证] 配置类

View File

@@ -1,8 +1,8 @@
# 全局侦听器 # 全局侦听器
接口`SaTokenListener`是Sa-Token的全局侦听器通过实现此接口你可以在用户登陆、退出、被踢下线等关键性操作时进行一些AOP操作 接口`SaTokenListener`是Sa-Token的全局侦听器通过实现此接口你可以在用户登陆、退出、被踢下线等关键性操作时进行一些AOP操作
框架对此侦听器的默认实现是log日志输出你可以通过配置`sa-token.is-log=true`开启 框架对此侦听器的默认实现是log日志输出你可以通过配置`sa-token.is-log=true`开启
下面我们演示一下如何自定义侦听器的实现: 下面我们演示一下如何自定义侦听器的实现:
@@ -11,7 +11,7 @@
### 自定义侦听器实现 ### 自定义侦听器实现
新建`MySaTokenListener.java`,继承`SaTokenListener`接口,并添加上注解`@Component`,保证此类被`SpringBoot`扫描到 新建`MySaTokenListener.java`,继承`SaTokenListener`接口,并添加上注解`@Component`,保证此类被`SpringBoot`扫描到
``` java ``` java
/** /**

View File

@@ -1,46 +1,46 @@
# Sa-Token 集成 Redis # Sa-Token 集成 Redis
--- ---
Sa-token默认将数据保存在内存中此模式读写速度最快且避免了序列化与反序列化带来的性能消耗但是此模式也有一些缺点比如 Sa-token 默认将数据保存在内存中,此模式读写速度最快,且避免了序列化与反序列化带来的性能消耗,但是此模式也有一些缺点,比如:
1. 重启后数据会丢失 1. 重启后数据会丢失
2. 无法在分布式环境中共享数据 2. 无法在分布式环境中共享数据
为此Sa-Token提供了扩展接口你可以轻松将会话数据存储在 `Redis``Memcached`等专业的缓存中间件中, 为此Sa-Token 提供了扩展接口,你可以轻松将会话数据存储在 `Redis``Memcached`等专业的缓存中间件中,
做到重启数据不丢失,而且保证分布式环境下多节点的会话一致性 做到重启数据不丢失,而且保证分布式环境下多节点的会话一致性
以下是官方提供的Redis集成包 以下是官方提供的 Redis 集成包:
--- ---
### 方式1. Sa-Token 整合 Redis 使用jdk默认序列化方式 ### 方式1Sa-Token 整合 Redis (使用 jdk 默认序列化方式)
``` xml ``` xml
<!-- Sa-Token 整合 Redis 使用jdk默认序列化方式 --> <!-- Sa-Token 整合 Redis (使用 jdk 默认序列化方式) -->
<dependency> <dependency>
<groupId>cn.dev33</groupId> <groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis</artifactId> <artifactId>sa-token-dao-redis</artifactId>
<version>${sa.top.version}</version> <version>${sa.top.version}</version>
</dependency> </dependency>
``` ```
优点兼容性好缺点Session序列化后基本不可读对开发者来讲等同于乱码 优点兼容性好缺点Session 序列化后基本不可读,对开发者来讲等同于乱码
### 方式2. Sa-Token 整合 Redis使用jackson序列化方式 ### 方式2Sa-Token 整合 Redis使用 jackson 序列化方式)
``` xml ``` xml
<!-- Sa-Token 整合 Redis 使用jackson序列化方式 --> <!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency> <dependency>
<groupId>cn.dev33</groupId> <groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId> <artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa.top.version}</version> <version>${sa.top.version}</version>
</dependency> </dependency>
``` ```
优点Session序列化后可读性强可灵活手动修改缺点兼容性稍差 优点Session 序列化后可读性强,可灵活手动修改,缺点:兼容性稍差
### 集成Redis请注意 ### 集成 Redis 请注意:
**1. 无论使用哪种序列化方式你都必须为项目提供一个Redis实例化方案例如** **1. 无论使用哪种序列化方式,你都必须为项目提供一个 Redis 实例化方案,例如:**
``` xml ``` xml
<!-- 提供Redis连接池 --> <!-- 提供Redis连接池 -->
<dependency> <dependency>
@@ -49,8 +49,8 @@ Sa-token默认将数据保存在内存中此模式读写速度最快且避
</dependency> </dependency>
``` ```
**2. 引入了依赖我还需要为Redis配置连接信息吗** <br> **2. 引入了依赖,我还需要为 Redis 配置连接信息吗?** <br>
需要只有项目初始化了正确的Redis实例`Sa-Token`才可以使用Redis进行数据持久化参考以下`yml配置` 需要!只有项目初始化了正确的 Redis 实例,`Sa-Token`才可以使用 Redis 进行数据持久化,参考以下`yml配置`
``` java ``` java
# 端口 # 端口
spring: spring:
@@ -79,11 +79,11 @@ spring:
``` ```
**3. 集成Redis后是我额外手动保存数据还是框架自动保存** <br> **3. 集成 Redis 后,是我额外手动保存数据,还是框架自动保存?** <br>
框架自动保存。集成`Redis`只需要引入对应的`pom依赖`即可框架所有上层API保持不变 框架自动保存。集成 `Redis` 只需要引入对应的 `pom依赖` 即可,框架所有上层 API 保持不变
**4. 集成包版本问题** <br> **4. 集成包版本问题** <br>
Sa-Token-Redis 集成包的版本尽量与 Sa-Token-Starter 集成包的版本一致,否则可能出现兼容性问题 Sa-Token-Redis 集成包的版本尽量与 Sa-Token-Starter 集成包的版本一致,否则可能出现兼容性问题
<br><br> <br><br>

View File

@@ -1,41 +1,41 @@
# 多账户认证 # 多账户认证
--- ---
### 0、需求场景 ### 1、需求场景
有的时候,我们会在一个项目中设计两套账号体系,比如一个电商系统的 `user表``admin表` 有的时候,我们会在一个项目中设计两套账号体系,比如一个电商系统的 `user表``admin表`
在这种场景下,如果两套账号我们都使用 `StpUtil` 类的API进行登录鉴权那么势必会发生逻辑冲突 在这种场景下,如果两套账号我们都使用 `StpUtil` 类的API进行登录鉴权那么势必会发生逻辑冲突
在Sa-Token中这个问题的模型叫做多账户体系认证 在Sa-Token中这个问题的模型叫做多账户体系认证
要解决这个问题,我们必须有一个合理的机制将这两套账号的授权给区分开,让它们互不干扰才行 要解决这个问题,我们必须有一个合理的机制将这两套账号的授权给区分开,让它们互不干扰才行
### 1、演进思路 ### 2、演进思路
假如说我们的 user表 和 admin表 都有一个 id=10001 的账号,它们对应的登录代码:`StpUtil.login(10001)` 是一样的, 假如说我们的 user表 和 admin表 都有一个 id=10001 的账号,它们对应的登录代码:`StpUtil.login(10001)` 是一样的,
那么问题来了:在`StpUtil.getLoginId()`获取到的账号id如何区分它是User用户还是Admin用户 那么问题来了:在`StpUtil.getLoginId()`获取到的账号id如何区分它是User用户还是Admin用户
你可能会想到为他们加一个固定前缀,比如`StpUtil.login("User_" + 10001)``StpUtil.login("Admin_" + 10001)`,这样确实是可以解决问题的, 你可能会想到为他们加一个固定前缀,比如`StpUtil.login("User_" + 10001)``StpUtil.login("Admin_" + 10001)`,这样确实是可以解决问题的,
但是同样的:你需要在`StpUtil.getLoginId()`时再裁剪掉相应的前缀才能获取真正的账号id这样一增一减就让我们的代码变得无比啰嗦 但是同样的:你需要在`StpUtil.getLoginId()`时再裁剪掉相应的前缀才能获取真正的账号id这样一增一减就让我们的代码变得无比啰嗦
那么,有没有从框架层面支持的,更优雅的解决方案呢? 那么,有没有从框架层面支持的,更优雅的解决方案呢?
### 2、解决方案 ### 3、解决方案
前面几篇介绍的api调用都是经过 StpUtil 类的各种静态方法进行授权认证, 前面几篇介绍的api调用都是经过 StpUtil 类的各种静态方法进行授权认证,
而如果我们深入它的源码,[点此阅览](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java) <br/> 而如果我们深入它的源码,[点此阅览](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java) <br/>
就会发现,此类并没有任何代码逻辑,唯一做的事就是对成员变量`stpLogic`的各个API包装一下进行转发 就会发现,此类并没有任何代码逻辑,唯一做的事就是对成员变量`stpLogic`的各个API包装一下进行转发
这样做有两个优点: 这样做有两个优点:
- StpLogic 类的所有函数都可以被重写,按需扩展 - StpLogic 类的所有函数都可以被重写,按需扩展
- 在构造方法时随意传入一个不同的 `loginType`,就可以再造一套账号登录体系 - 在构造方法时随意传入一个不同的 `loginType`,就可以再造一套账号登录体系
### 3、操作示例 ### 4、操作示例
比如说,对于原生`StpUtil`类,我们只做`admin账号`权限认证,而对于`user账号`,我们则: 比如说,对于原生`StpUtil`类,我们只做`admin账号`权限认证,而对于`user账号`,我们则:
1. 新建一个新的权限认证类,比如: `StpUserUtil.java` 1. 新建一个新的权限认证类,比如: `StpUserUtil.java`
2.`StpUtil.java`类的全部代码复制粘贴到 `StpUserUtil.java` 2.`StpUtil.java`类的全部代码复制粘贴到 `StpUserUtil.java`
3. 更改一下其 `LoginType` 比如: 3. 更改一下其 `LoginType` 比如:
``` java ``` java
@@ -50,16 +50,16 @@ public class StpUserUtil {
} }
``` ```
4. 接下来就可以像调用`StpUtil.java`一样调用 `StpUserUtil.java`了,这两套账号认证的逻辑是完全隔离的 4. 接下来就可以像调用`StpUtil.java`一样调用 `StpUserUtil.java`了,这两套账号认证的逻辑是完全隔离的
> 成品样例参考:[码云 StpUserUtil.java](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/at/StpUserUtil.java) > 成品样例参考:[码云 StpUserUtil.java](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/at/StpUserUtil.java)
### 4、在多账户模式下使用注解鉴权 ### 5、在多账户模式下使用注解鉴权
框架默认的注解鉴权 如`@SaCheckLogin` 只针对原生`StpUtil`进行鉴权 框架默认的注解鉴权 如`@SaCheckLogin` 只针对原生`StpUtil`进行鉴权
例如,我们在一个方法上加上`@SaCheckLogin`注解,这个注解只会放行通过`StpUtil.login(id)`进行登录的会话, 例如,我们在一个方法上加上`@SaCheckLogin`注解,这个注解只会放行通过`StpUtil.login(id)`进行登录的会话,
而对于通过`StpUserUtil.login(id)`进行登录的都会话,则始终不会通过校验 而对于通过`StpUserUtil.login(id)`进行登录的都会话,则始终不会通过校验
那么如何告诉`@SaCheckLogin`要鉴别的是哪套账号的登录会话呢很简单你只需要指定一下注解的type属性即可 那么如何告诉`@SaCheckLogin`要鉴别的是哪套账号的登录会话呢很简单你只需要指定一下注解的type属性即可
@@ -72,17 +72,18 @@ public String info() {
} }
``` ```
注:`@SaCheckRole("xxx")`、`@SaCheckPermission("xxx")`同理亦可根据type属性指定其校验的账号体系此属性默认为`""`,代表使用原生`StpUtil`账号体系 注:`@SaCheckRole("xxx")`、`@SaCheckPermission("xxx")`同理亦可根据type属性指定其校验的账号体系此属性默认为`""`,代表使用原生`StpUtil`账号体系
### 5、使用注解合并简化代码 ### 6、使用注解合并简化代码
交流群里有同学反应,虽然可以根据 `@SaCheckLogin(type = "user")` 指定账号类型,但几十上百个注解都加上这个的话,还是有些繁琐,代码也不够优雅,有么有更的解决方案? 交流群里有同学反应,虽然可以根据 `@SaCheckLogin(type = "user")` 指定账号类型,但几十上百个注解都加上这个的话,还是有些繁琐,代码也不够优雅,有么有更简单的解决方案?
我们期待一种`[注解继承/合并]`的能力,即:自定义一个注解,标注上`@SaCheckLogin(type = "user")`然后在方法上标注这个自定义注解,效果等同于标注`@SaCheckLogin(type = "user")` 我们期待一种`[注解继承/合并]`的能力,即:自定义一个注解,标注上`@SaCheckLogin(type = "user")`
然后在方法上标注这个自定义注解,效果等同于标注`@SaCheckLogin(type = "user")`。
很遗憾JDK默认的注解处理器并没有提供这种`[注解继承/合并]`的能力,不过好在我们可以利用 Spring 的注解处理器,达到同样的目的 很遗憾JDK默认的注解处理器并没有提供这种`[注解继承/合并]`的能力,不过好在我们可以利用 Spring 的注解处理器,达到同样的目的
1. 重写Sa-Token默认的注解处理器 1. 重写Sa-Token默认的注解处理器
``` java ``` java
@Configuration @Configuration
@@ -97,7 +98,7 @@ public class SaTokenConfigure {
} }
``` ```
2. 自定义一个注解 2. 自定义一个注解
``` java ``` java
/** /**
@@ -112,7 +113,7 @@ public @interface SaUserCheckLogin {
} }
``` ```
3. 接下来就可以使用我们的自定义注解了 3. 接下来就可以使用我们的自定义注解了
``` java ``` java
// 使用 @SaUserCheckLogin 的效果等同于使用:@SaCheckLogin(type = "user") // 使用 @SaUserCheckLogin 的效果等同于使用:@SaCheckLogin(type = "user")
@@ -123,15 +124,16 @@ public String info() {
} }
``` ```
注:其它注解 `@SaCheckRole("xxx")`、`@SaCheckPermission("xxx")`同理,完整示例参考:[码云:自定义注解](https://gitee.com/dromara/sa-token/tree/dev/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/at) 注:其它注解 `@SaCheckRole("xxx")`、`@SaCheckPermission("xxx")`同理,
完整示例参考:[码云:自定义注解](https://gitee.com/dromara/sa-token/tree/dev/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/at)。
### 6、同端多登陆 ### 7、同端多登陆
假设我们不仅需要在后台同时集成两套账号我们还需要在一个客户端同时登陆两套账号业务场景举例一个APP中可以同时登陆商家账号和用户账号 假设我们不仅需要在后台同时集成两套账号我们还需要在一个客户端同时登陆两套账号业务场景举例一个APP中可以同时登陆商家账号和用户账号
如果我们不做任何特殊处理的话,在客户端会发生`token覆盖`新登录的token会覆盖掉旧登录的token从而导致旧登录失效 如果我们不做任何特殊处理的话,在客户端会发生`token覆盖`新登录的token会覆盖掉旧登录的token从而导致旧登录失效
那么如何解决这个问题?<br> 那么如何解决这个问题?<br>
很简单,我们只要更改一下 `StpUserUtil` 的 `TokenName` 即可,参考示例如下: 很简单,我们只要更改一下 `StpUserUtil` 的 `TokenName` 即可,参考示例如下:
@@ -154,10 +156,10 @@ public class StpUserUtil {
} }
``` ```
再次调用 `StpUserUtil.login(10001)` 进行登录授权时token的名称将不再是 `satoken`,而是我们重写后的 `satoken-user` 再次调用 `StpUserUtil.login(10001)` 进行登录授权时token的名称将不再是 `satoken`,而是我们重写后的 `satoken-user`
### 7、不同体系不同 SaTokenConfig 配置 ### 8、不同体系不同 SaTokenConfig 配置
如果自定义的 StpUserUtil 需要使用不同 SaTokenConfig 对象, 也很简单,参考示例如下: 如果自定义的 StpUserUtil 需要使用不同 SaTokenConfig 对象, 也很简单,参考示例如下:
``` java ``` java

View File

@@ -2,10 +2,10 @@
--- ---
以上介绍的api都是操作当前账号,对当前账号进行各种鉴权操作,你可能会问,我能不能对别的账号进行一些操作?<br> 以上介绍的 API 都是操作当前账号,对当前账号进行各种鉴权操作,你可能会问,我能不能对别的账号进行一些操作?<br>
比如查看账号10001有无某个权限码、获取 账号id=10002 的 `User-Session`,等等... 比如:查看账号 10001 有无某个权限码、获取 账号 id=10002 的 `User-Session`,等等...
Sa-Token在api设计时充分考虑了这一点暴露出多个api进行此类操作 Sa-Token 在 API 设计时充分考虑了这一点暴露出多个api进行此类操作
## 有关操作其它账号的api ## 有关操作其它账号的api
@@ -36,7 +36,7 @@ StpUtil.hasPermission(10001, "user:add");
有时候,我们需要直接将当前会话的身份切换为其它账号,比如: 有时候,我们需要直接将当前会话的身份切换为其它账号,比如:
``` java ``` java
// 将当前会话[身份临时切换]为其它账号 // 将当前会话[身份临时切换]为其它账号(本次请求内有效)
StpUtil.switchTo(10044); StpUtil.switchTo(10044);
// 此时再调用此方法会返回 10044 (我们临时切换到的账号id) // 此时再调用此方法会返回 10044 (我们临时切换到的账号id)
@@ -46,12 +46,12 @@ StpUtil.getLoginId();
StpUtil.endSwitch(); StpUtil.endSwitch();
``` ```
你还可以: 直接在一个代码段里方法内临时切换身份为指定loginId此方式无需手动调用`StpUtil.endSwitch()`关闭身份切换) 你还可以直接在一个代码段里方法内临时切换身份为指定loginId此方式无需手动调用`StpUtil.endSwitch()`关闭身份切换)
``` java ``` java
System.out.println("------- [身份临时切换]调用开始..."); System.out.println("------- [身份临时切换]调用开始...");
StpUtil.switchTo(10044, () -> { StpUtil.switchTo(10044, () -> {
System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch()); System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch()); // 输出 true
System.out.println("获取当前登录账号id: " + StpUtil.getLoginId()); System.out.println("获取当前登录账号id: " + StpUtil.getLoginId()); // 输出 10044
}); });
System.out.println("------- [身份临时切换]调用结束..."); System.out.println("------- [身份临时切换]调用结束...");
``` ```

View File

@@ -1,7 +1,7 @@
# 同端互斥登录 # 同端互斥登录
如果你经常使用腾讯QQ就会发现它的登录有如下特点它可以手机电脑同时在线但是不能在两个手机上同时登录一个账号 <br/> 如果你经常使用腾讯QQ就会发现它的登录有如下特点它可以手机电脑同时在线但是不能在两个手机上同时登录一个账号 <br/>
同端互斥登录指的就是像腾讯QQ一样在同一类型设备上只允许单地点登录在不同类型设备上允许同时在线 同端互斥登录,指的就是像腾讯QQ一样在同一类型设备上只允许单地点登录在不同类型设备上允许同时在线
<button class="show-img" img-src="https://oss.dev33.cn/sa-token/doc/g/g3--mutex-login.gif">加载动态演示图</button> <button class="show-img" img-src="https://oss.dev33.cn/sa-token/doc/g/g3--mutex-login.gif">加载动态演示图</button>

View File

@@ -1,33 +1,48 @@
# 前后台分离无Cookie模式 # 前后台分离无Cookie模式
--- ---
### 何为无Cookie模式? ### 何为无 Cookie 模式?
无Cookie特指不支持Cookie功能的终端通俗来讲就是我们常说的 —— **前后台分离模式** Cookie 模式:特指不支持 Cookie 功能的终端,通俗来讲就是我们常说的 —— **前后台分离模式**
常规 Web 端鉴权方法,一般由 `Cookie模式` 完成,而 Cookie 有两个特性: 常规 Web 端鉴权方法,一般由 `Cookie模式` 完成,而 Cookie 有两个特性
1. 可由后端控制写入 1. 可由后端控制写入
2. 每次请求自动提交 2. 每次请求自动提交
这就使得我们在前端代码中,无需任何特殊操作,就能完成鉴权的全部流程(因为整个流程都是后端控制完成的)<br/> 这就使得我们在前端代码中,无需任何特殊操作,就能完成鉴权的全部流程(因为整个流程都是后端控制完成的)<br/>
而在app、小程序等前后台分离场景中一般是没有 Cookie 这一功能的,此时大多数人都会一脸懵逼,咋进行鉴权啊? 而在app、小程序等前后台分离场景中一般是没有 Cookie 这一功能的,此时大多数人都会一脸懵逼,咋进行鉴权啊?
见招拆招,其实答案很简单: 见招拆招,其实答案很简单:
- 不能后端控制写入了,就前端自己写入(难点在**后端如何将 Token 传递到前端** - 不能后端控制写入了,就前端自己写入(难点在**后端如何将 Token 传递到前端**
- 每次请求不能自动提交了,那就手动提交(难点在**前端如何将 Token 传递到后端**,同时**后端将其读取出来** - 每次请求不能自动提交了,那就手动提交(难点在**前端如何将 Token 传递到后端**,同时**后端将其读取出来**
### 1、后端将 token 返回到前端 ### 1、后端将 token 返回到前端
1. 首先调用 `StpUtil.login(id)` 进行登录 1. 首先调用 `StpUtil.login(id)` 进行登录
2. 调用 `StpUtil.getTokenInfo()` 返回当前会话的token详细参数 2. 调用 `StpUtil.getTokenInfo()` 返回当前会话的 token 详细参数
- 此方法返回一个对象,其有两个关键属性:`tokenName``tokenValue`token 的名称和 token 的值) - 此方法返回一个对象,其有两个关键属性:`tokenName``tokenValue`token 的名称和 token 的值)
- 将此对象传递到前台,让前端人员将这两个值保存到本地 - 将此对象传递到前台,让前端人员将这两个值保存到本地
代码示例:
``` java
// 登录接口
@RequestMapping("doLogin")
public SaResult doLogin() {
// 第1步先登录上
StpUtil.login(10001);
// 第2步获取 Token 相关参数
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
// 第3步返回给前端
return SaResult.data(tokenInfo);
}
```
### 2、前端将 token 提交到后端 ### 2、前端将 token 提交到后端
1. 无论是app还是小程序其传递方式都大同小异 1. 无论是app还是小程序其传递方式都大同小异
2. 那就是,将 token 塞到请求`header`里 ,格式为:`{tokenName: tokenValue}` 2. 那就是,将 token 塞到请求`header`里 ,格式为:`{tokenName: tokenValue}`。
3. 以经典跨端框架 [uni-app](https://uniapp.dcloud.io/) 为例: 3. 以经典跨端框架 [uni-app](https://uniapp.dcloud.io/) 为例:
**方式1简单粗暴** **方式1简单粗暴**
@@ -76,14 +91,14 @@ uni.request({
}); });
``` ```
4. 只要按照如此方法将`token`值传递到后端Sa-Token 就能像传统PC端一样自动读取到 token 值,进行鉴权 4. 只要按照如此方法将`token`值传递到后端Sa-Token 就能像传统PC端一样自动读取到 token 值,进行鉴权
5. 你可能会有疑问,难道我每个`ajax`都要写这么一坨?岂不是麻烦死了 5. 你可能会有疑问,难道我每个`ajax`都要写这么一坨?岂不是麻烦死了
- 你当然不能每个 ajax 都写这么一坨,因为这种重复性代码都是要封装在一个函数里统一调用的 - 你当然不能每个 ajax 都写这么一坨,因为这种重复性代码都是要封装在一个函数里统一调用的
### 其它解决方案? ### 其它解决方案?
如果你对 Cookie 非常了解,那你就会明白,所谓 Cookie ,本质上就是一个特殊的`header`参数而已, 如果你对 Cookie 非常了解,那你就会明白,所谓 Cookie ,本质上就是一个特殊的`header`参数而已,
而既然它只是一个 header 参数,我们就能手动模拟实现它,从而完成鉴权操作 而既然它只是一个 header 参数,我们就能手动模拟实现它,从而完成鉴权操作
这其实是对`无Cookie模式`的另一种解决方案,有兴趣的同学可以百度了解一下,在此暂不赘述 这其实是对`无Cookie模式`的另一种解决方案,有兴趣的同学可以百度了解一下,在此暂不赘述

View File

@@ -1,7 +1,7 @@
# 密码加密 # 密码加密
严格来讲,密码加密不属于 [权限认证] 的范畴,但是对于大多数系统来讲,密码加密又是安全认证不可或缺的部分, 严格来讲,密码加密不属于 [权限认证] 的范畴,但是对于大多数系统来讲,密码加密又是安全认证不可或缺的部分,
所以,应大家要求,`Sa-Token``v1.14版本`添加密码加密模块,该模块非常简单,仅仅封装了一些常见的加密算法 所以,应大家要求,`Sa-Token` v1.14 版本添加密码加密模块,该模块非常简单,仅仅封装了一些常见的加密算法

View File

@@ -1,16 +1,16 @@
# [记住我] 模式 # [记住我] 模式
--- ---
如图所示,一般网站的登录界面都会有一个 **`[记住我]`** 按钮,当你勾选它后,即使你关闭浏览器再次打开网站,也依然会处于登录状态,无须重复验证密码 如图所示,一般网站的登录界面都会有一个 **`[记住我]`** 按钮,当你勾选它后,即使你关闭浏览器再次打开网站,也依然会处于登录状态,无须重复验证密码
![../static/login-view.png](https://oss.dev33.cn/sa-token/doc/login-view.png) ![../static/login-view.png](https://oss.dev33.cn/sa-token/doc/login-view.png)
那么在Sa-Token中如何做到 [ 记住我 ] 功能呢? 那么在Sa-Token中如何做到 [ 记住我 ] 功能呢?
### 在sa-token中实现记住我功能 ### 在 Sa-Token 中实现记住我功能
Sa-Token的登录授权**默认就是`[记住我]`模式**,为了实现`[非记住我]`模式, 你需要在登录时如下设置: Sa-Token的登录授权**默认就是`[记住我]`模式**,为了实现`[非记住我]`模式你需要在登录时如下设置:
``` java ``` java
// 设置登录账号id为10001第二个参数指定是否为[记住我]当此值为false后关闭浏览器后再次打开需要重新登录 // 设置登录账号id为10001第二个参数指定是否为[记住我]当此值为false后关闭浏览器后再次打开需要重新登录
@@ -22,12 +22,12 @@ StpUtil.login(10001, false);
### 实现原理 ### 实现原理
Cookie作为浏览器提供的默认会话跟踪机制其生命周期有两种形式分别是 Cookie作为浏览器提供的默认会话跟踪机制其生命周期有两种形式分别是
- 临时Cookie有效期为本次会话只要关闭浏览器窗口Cookie就会消失 - 临时Cookie有效期为本次会话只要关闭浏览器窗口Cookie就会消失
- 持久Cookie有效期为一个具体的时间在时间未到期之前即使用户关闭了浏览器Cookie也不会消失 - 持久Cookie有效期为一个具体的时间在时间未到期之前即使用户关闭了浏览器Cookie也不会消失
利用Cookie的此特性我们便可以轻松实现 [记住我] 模式: 利用Cookie的此特性我们便可以轻松实现 [记住我] 模式:
- 勾选 [记住我] 按钮时:调用`StpUtil.login(10001, true)`,在浏览器写入一个`持久Cookie`储存 Token此时用户即使重启浏览器 Token 依然有效 - 勾选 [记住我] 按钮时:调用`StpUtil.login(10001, true)`,在浏览器写入一个`持久Cookie`储存 Token此时用户即使重启浏览器 Token 依然有效
- 不勾选 [记住我] 按钮时:调用`StpUtil.login(10001, false)`,在浏览器写入一个`临时Cookie`储存 Token此时用户在重启浏览器后 Token 便会消失,导致会话失效 - 不勾选 [记住我] 按钮时:调用`StpUtil.login(10001, false)`,在浏览器写入一个`临时Cookie`储存 Token此时用户在重启浏览器后 Token 便会消失,导致会话失效
<button class="show-img" img-src="https://oss.dev33.cn/sa-token/doc/g/g3--remember-me.gif">加载动态演示图</button> <button class="show-img" img-src="https://oss.dev33.cn/sa-token/doc/g/g3--remember-me.gif">加载动态演示图</button>
@@ -38,7 +38,7 @@ Cookie作为浏览器提供的默认会话跟踪机制其生命周期有两
此时机智的你😏很快发现一个问题Cookie虽好却无法在前后端分离环境下使用那是不是代表上述方案在APP、小程序等环境中无效 此时机智的你😏很快发现一个问题Cookie虽好却无法在前后端分离环境下使用那是不是代表上述方案在APP、小程序等环境中无效
准确的讲答案是肯定的任何基于Cookie的认证方案在前后台分离环境下都会失效原因在于这些客户端默认没有实现Cookie功能不过好在这些客户端一般都提供了替代方案 准确的讲答案是肯定的任何基于Cookie的认证方案在前后台分离环境下都会失效原因在于这些客户端默认没有实现Cookie功能不过好在这些客户端一般都提供了替代方案
唯一遗憾的是此场景中token的生命周期需要我们在前端手动控制 唯一遗憾的是此场景中token的生命周期需要我们在前端手动控制
以经典跨端框架 [uni-app](https://uniapp.dcloud.io/) 为例,我们可以使用如下方式达到同样的效果: 以经典跨端框架 [uni-app](https://uniapp.dcloud.io/) 为例,我们可以使用如下方式达到同样的效果:
``` js ``` js
@@ -62,8 +62,8 @@ Remember me, it's too easy!
### 登录时指定token有效期 ### 登录时指定 Token 有效期
登录时不仅可以指定是否为`[记住我]`模式,还可以指定一个特定的时间作为token有效时长如下示例 登录时不仅可以指定是否为`[记住我]`模式,还可以指定一个特定的时间作为 Token 有效时长,如下示例:
``` java ``` java
// 示例1 // 示例1
// 指定token有效期(单位: 秒)如下所示token七天有效 // 指定token有效期(单位: 秒)如下所示token七天有效
@@ -74,7 +74,7 @@ StpUtil.login(10001, new SaLoginModel().setTimeout(60 * 60 * 24 * 7));
StpUtil.login(10001, new SaLoginModel() StpUtil.login(10001, new SaLoginModel()
.setDevice("PC") // 此次登录的客户端设备类型, 用于[同端互斥登录]时指定此次登录的设备类型 .setDevice("PC") // 此次登录的客户端设备类型, 用于[同端互斥登录]时指定此次登录的设备类型
.setIsLastingCookie(true) // 是否为持久Cookie临时Cookie在浏览器关闭时会自动删除持久Cookie在重新打开后依然存在 .setIsLastingCookie(true) // 是否为持久Cookie临时Cookie在浏览器关闭时会自动删除持久Cookie在重新打开后依然存在
.setTimeout(60 * 60 * 24 * 7) // 指定此次登录token的有效期, 单位:秒 如未指定自动取全局配置的timeout值 .setTimeout(60 * 60 * 24 * 7) // 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的 timeout 值)
.setToken("xxxx-xxxx-xxxx-xxxx") // 预定此次登录的生成的Token .setToken("xxxx-xxxx-xxxx-xxxx") // 预定此次登录的生成的Token
); );
``` ```

View File

@@ -1,18 +1,18 @@
# 二级认证 # 二级认证
在某些敏感操作下,我们需要对已登录的会话进行二次验证 在某些敏感操作下,我们需要对已登录的会话进行二次验证
比如代码托管平台的仓库删除操作,尽管我们已经登录了账号,当我们点击 **[删除]** 按钮时,还是需要再次输入一遍密码,这么做主要为了两点: 比如代码托管平台的仓库删除操作,尽管我们已经登录了账号,当我们点击 **[删除]** 按钮时,还是需要再次输入一遍密码,这么做主要为了两点:
1. 保证操作者是当前账号本人 1. 保证操作者是当前账号本人
2. 增加操作步骤,防止误删除重要数据 2. 增加操作步骤,防止误删除重要数据
这就是我们本篇要讲的 —— 二级认证,即:在已登录会话的基础上,进行再次验证,提高会话的安全性。 这就是我们本篇要讲的 —— 二级认证,即:在已登录会话的基础上,进行再次验证,提高会话的安全性。
--- ---
## 具体API ### 具体API
`Sa-Token`中进行二级认证非常简单只需要使用以下API `Sa-Token`中进行二级认证非常简单只需要使用以下API
@@ -34,7 +34,7 @@ StpUtil.closeSafe();
``` ```
## 使用注解进行二级认证 ### 使用注解进行二级认证
在一个方法上使用 `@SaCheckSafe` 注解,可以在代码进入之前此方法之前进行一次二级认证 在一个方法上使用 `@SaCheckSafe` 注解,可以在代码进入之前此方法之前进行一次二级认证
``` java ``` java
// 二级认证:必须二级认证之后才能进入该方法 // 二级认证:必须二级认证之后才能进入该方法
@@ -48,3 +48,47 @@ public String add() {
详细使用方法可参考:[注解鉴权](/use/at-check),此处不再赘述 详细使用方法可参考:[注解鉴权](/use/at-check),此处不再赘述
### 一个小示例
一个完整的二级认证业务流程,应该大致如下:
``` java
// 删除仓库
@RequestMapping("deleteProject")
public SaResult deleteProject(String projectId) {
// 第1步先检查当前会话是否已完成二级认证
if(StpUtil.isSafe()) {
return SaResult.error("请完成二级认证后再次访问接口");
}
// 第2步如果已完成二级认证则开始执行业务逻辑
// ...
// 第3步返回结果
return SaResult.ok();
}
// 提供密码进行二级认证
@RequestMapping("openSafe")
public SaResult openSafe(String password) {
// 比对密码(此处只是举例,真实项目时可拿其它参数进行校验)
if("123456".equals(password)) {
// 比对成功为当前会话打开二级认证有效期为120秒
StpUtil.openSafe(120);
return SaResult.ok("二级认证成功");
}
// 如果密码校验失败,则二级认证也会失败
return SaResult.error("二级认证失败");
}
```
调用步骤:
1. 前端调用 `deleteProject` 接口,尝试删除仓库。
2. 后端校验会话尚未完成二级认证,返回: `请完成二级认证后再次访问接口`。
3. 前端将信息提示给用户,用户输入密码,调用 `openSafe` 接口。
4. 后端比对用户输入的密码完成二级认证有效期为120秒。
5. 前端在 120 秒内再次调用 `deleteProject` 接口,尝试删除仓库。
6. 后端校验会话已完成二级认证,仓库删除成功。

View File

@@ -1,7 +1,8 @@
# 会话治理 # 会话治理
尽管框架将大部分操作提供了简易的封装,但在一些特殊场景下,我们仍需要绕过框架,直达数据底层进行一些操作 <br> 尽管框架将大部分操作提供了简易的封装,但在一些特殊场景下,我们仍需要绕过框架,直达数据底层进行一些操作
Sa-Token提供以下API助你直接操作会话列表
Sa-Token提供以下API助你直接操作会话列表
--- ---
@@ -21,9 +22,9 @@ StpUtil.searchTokenSessionId(String keyword, int start, int size);
#### 参数详解: #### 参数详解:
- `keyword`: 查询关键字只有包括这个字符串的token值才会被查询出来 - `keyword`: 查询关键字,只有包括这个字符串的 token 值才会被查询出来
- `start`: 数据开始处索引, 值为-1时代表一次性取出所有数据 - `start`: 数据开始处索引, 值为-1时代表一次性取出所有数据
- `size`: 要获取的数据条数 - `size`: 要获取的数据条数
使用示例: 使用示例:
``` java ``` java
@@ -39,10 +40,10 @@ for (String token : tokenList) {
#### 注意事项: #### 注意事项:
由于会话查询底层采用了遍历方式获取数据,当数据量过大时此操作将会比较耗时,有多耗时呢?这里提供一份参考数据: 由于会话查询底层采用了遍历方式获取数据,当数据量过大时此操作将会比较耗时,有多耗时呢?这里提供一份参考数据:
- 单机模式下百万会话取出10条token平均耗时 `0.255s` - 单机模式下百万会话取出10条 Token 平均耗时 `0.255s`
- Redis模式下百万会话取出10条token平均耗时 `3.322s` - Redis模式下百万会话取出10条 Token 平均耗时 `3.322s`
请根据业务实际水平合理调用API 请根据业务实际水平合理调用API
> 如果需要实时获取当前登录人数或者需要在用户退出后自动触发某事件等, 建议采用websocket技术 > 如果需要实时获取当前登录人数或者需要在用户退出后自动触发某事件等, 建议采用websocket技术

View File

@@ -10,7 +10,7 @@
} }
``` ```
此时后端如果不做任何特殊处理,框架将会把`Bearer `视为token的一部分无法正常读取token信息导致鉴权失败 此时后端如果不做任何特殊处理,框架将会把`Bearer `视为token的一部分无法正常读取token信息导致鉴权失败
为此我们需要在yml中添加如下配置 为此我们需要在yml中添加如下配置
``` java ``` java
@@ -19,12 +19,12 @@ sa-token:
token-prefix: Bearer token-prefix: Bearer
``` ```
此时 Sa-Token 便可在读取 Token 时裁剪掉 `Bearer`,成功获取`xxxx-xxxx-xxxx-xxxx` 此时 Sa-Token 便可在读取 Token 时裁剪掉 `Bearer`,成功获取`xxxx-xxxx-xxxx-xxxx`
### 注意点 ### 注意点
1. Token前缀 与 Token值 之间必须有一个空格 1. Token前缀 与 Token值 之间必须有一个空格
2. 一旦配置了 Token前缀则前端提交token时必须带有前缀否则会导致框架无法读取token 2. 一旦配置了 Token前缀则前端提交 `Token` 时,必须带有前缀,否则会导致框架无法读取 Token
3. 由于`Cookie`中无法存储空格字符,也就意味配置token前缀后Cookie鉴权方式将会失效此时只能将token提交到`header`里进行传输 3. 由于`Cookie`中无法存储空格字符,也就意味配置 Token 前缀后Cookie 鉴权方式将会失效,此时只能将 Token 提交到`header`里进行传输

View File

@@ -1,14 +1,14 @@
# 自定义 Token 风格 # 自定义 Token 风格
本篇介绍token生成的各种风格以及自定义token生成策略 本篇介绍token生成的各种风格以及自定义token生成策略
--- ---
## 内置风格 ## 内置风格
Sa-Token默认的token生成策略是uuid风格其模样类似于`623368f0-ae5e-4475-a53f-93e4225f16ae`<br> Sa-Token默认的token生成策略是uuid风格其模样类似于`623368f0-ae5e-4475-a53f-93e4225f16ae`<br>
如果你对这种风格不太感冒还可以将token生成设置为其他风格 如果你对这种风格不太感冒还可以将token生成设置为其他风格
怎么设置呢只需要在yml配置文件里设置 `sa-token.token-style=风格类型` 即可,其有多种取值: 怎么设置呢只需要在yml配置文件里设置 `sa-token.token-style=风格类型` 即可,其有多种取值:
@@ -33,11 +33,11 @@ Sa-Token默认的token生成策略是uuid风格其模样类似于`623368f0
``` ```
## 自定义token生成策略 ## 自定义 Token 生成策略
如果你觉着以上风格都不是你喜欢的类型,那么你还可以**自定义token生成策略**来定制化token生成风格 <br> 如果你觉着以上风格都不是你喜欢的类型,那么你还可以**自定义token生成策略**来定制化token生成风格 <br>
怎么做呢?只需要重写 `SaStrategy` 策略类的 `createToken` 算法即可 怎么做呢?只需要重写 `SaStrategy` 策略类的 `createToken` 算法即可
#### 参考步骤如下: #### 参考步骤如下:

View File

@@ -121,7 +121,7 @@ Server 端:
| ticketTimeout | long | 300 | ticket 有效期 (单位: 秒) | | ticketTimeout | long | 300 | ticket 有效期 (单位: 秒) |
| allowUrl | String | * | 所有允许的授权回调地址多个用逗号隔开不在此列表中的URL将禁止下放ticket参考[SSO整合配置域名校验](/sso/sso-check-domain) | | allowUrl | String | * | 所有允许的授权回调地址多个用逗号隔开不在此列表中的URL将禁止下放ticket参考[SSO整合配置域名校验](/sso/sso-check-domain) |
| isSlo | Boolean | false | 是否打开单点注销功能 | | isSlo | Boolean | false | 是否打开单点注销功能 |
| isHttp | Boolean | false | 是否打开模式三(此值为 true 时将使用 http 请求校验ticket值、单点注销、获取userinfo | | isHttp | Boolean | false | 是否打开模式三(此值为 true 时将使用 http 请求校验ticket值、单点注销、获取userinfo,参考:[详解](/use/config?id=isHttp) |
| secretkey | String | null | 调用秘钥 用于SSO模式三单点注销的接口通信身份校验 | | secretkey | String | null | 调用秘钥 用于SSO模式三单点注销的接口通信身份校验 |
@@ -131,7 +131,7 @@ Client 端:
| :-------- | :-------- | :-------- | :-------- | | :-------- | :-------- | :-------- | :-------- |
| authUrl | String | null | 配置 Server 端单点登录授权地址 | | authUrl | String | null | 配置 Server 端单点登录授权地址 |
| isSlo | Boolean | false | 是否打开单点注销功能 | | isSlo | Boolean | false | 是否打开单点注销功能 |
| isHttp | Boolean | false | 是否打开模式三(此值为 true 时将使用 http 请求校验ticket值、单点注销、获取userinfo | | isHttp | Boolean | false | 是否打开模式三(此值为 true 时将使用 http 请求校验ticket值、单点注销、获取userinfo,参考:[详解](/use/config?id=isHttp) |
| checkTicketUrl| String | null | 配置 Server 端的 ticket 校验地址 | | checkTicketUrl| String | null | 配置 Server 端的 ticket 校验地址 |
| userinfoUrl | String | null | 配置 Server 端查询 userinfo 地址 | | userinfoUrl | String | null | 配置 Server 端查询 userinfo 地址 |
| sloUrl | String | null | 配置 Server 端单点注销地址 | | sloUrl | String | null | 配置 Server 端单点注销地址 |
@@ -244,3 +244,11 @@ sa-token:
- 此值为 false 时:四种模式(`isCode、isImplicit、isPassword、isClient`)是否生效,依靠局部配置+全局配置(两个都为 true 时才打开) - 此值为 false 时:四种模式(`isCode、isImplicit、isPassword、isClient`)是否生效,依靠局部配置+全局配置(两个都为 true 时才打开)
#### isHttp
配置含义:是否打开单点登录模式三。
- 此配置项为 false 时代表使用SSO模式二使用 Redis 校验 ticket 值、删除 Redis 数据做到单点注销、使用 Redis 同步 Userinfo 数据。
- 此配置项为 true 时代表使用SSO模式三使用 Http 请求校验 ticket 值、使用 Http 请求做到单点注销、使用 Http 请求同步 Userinfo 数据。