mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-09-22 20:13:34 +08:00
优化文档
This commit is contained in:
@@ -6,14 +6,14 @@
|
||||
之前的章节中,我们学习了“根据拦截器实现路由拦截鉴权”,其实在大多数web框架中,使用过滤器可以实现同样的功能,本章我们就利用Sa-Token全局过滤器来实现路由拦截器鉴权。
|
||||
|
||||
首先我们先梳理清楚一个问题,既然拦截器已经可以实现路由鉴权,为什么还要用过滤器再实现一遍呢?简而言之:
|
||||
1. 相比于拦截器,过滤器更加底层,执行时机更靠前,有利于防渗透扫描
|
||||
2. 过滤器可以拦截静态资源,方便我们做一些权限控制
|
||||
3. 部分Web框架根本就没有提供拦截器功能,但几乎所有的Web框架都会提供过滤器机制
|
||||
1. 相比于拦截器,过滤器更加底层,执行时机更靠前,有利于防渗透扫描。
|
||||
2. 过滤器可以拦截静态资源,方便我们做一些权限控制。
|
||||
3. 部分Web框架根本就没有提供拦截器功能,但几乎所有的Web框架都会提供过滤器机制。
|
||||
|
||||
但是过滤器也有一些缺点,比如:
|
||||
1. 由于太过底层,导致无法率先拿到`HandlerMethod`对象,无法据此添加一些额外功能
|
||||
2. 由于拦截的太全面了,导致我们需要对很多特殊路由(如`/favicon.ico`)做一些额外处理
|
||||
3. 在Spring中,过滤器中抛出的异常无法进入全局`@ExceptionHandler`,我们必须额外编写代码进行异常处理
|
||||
1. 由于太过底层,导致无法率先拿到`HandlerMethod`对象,无法据此添加一些额外功能。
|
||||
2. 由于拦截的太全面了,导致我们需要对很多特殊路由(如`/favicon.ico`)做一些额外处理。
|
||||
3. 在Spring中,过滤器中抛出的异常无法进入全局`@ExceptionHandler`,我们必须额外编写代码进行异常处理。
|
||||
|
||||
Sa-Token同时提供过滤器和拦截器机制,不是为了让谁替代谁,而是为了让大家根据自己的实际业务合理选择,拥有更多的发挥空间。
|
||||
|
||||
@@ -75,9 +75,9 @@ public class SaTokenConfigure {
|
||||
```
|
||||
|
||||
##### 注意事项:
|
||||
- 在`[认证函数]`里,你可以写和拦截器里一致的代码,进行路由匹配鉴权,参考:[路由拦截鉴权](/use/route-check)
|
||||
- 由于过滤器中抛出的异常不进入全局异常处理,所以你必须提供`[异常处理函数]`来处理`[认证函数]`里抛出的异常
|
||||
- 在`[异常处理函数]`里的返回值,将作为字符串输出到前端,如果需要定制化返回数据,请注意其中的格式转换
|
||||
- 在`[认证函数]`里,你可以写和拦截器里一致的代码,进行路由匹配鉴权,参考:[路由拦截鉴权](/use/route-check)。
|
||||
- 由于过滤器中抛出的异常不进入全局异常处理,所以你必须提供`[异常处理函数]`来处理`[认证函数]`里抛出的异常。
|
||||
- 在`[异常处理函数]`里的返回值,将作为字符串输出到前端,如果需要定制化返回数据,请注意其中的格式转换。
|
||||
|
||||
改写 `setError` 函数的响应格式示例:
|
||||
``` java
|
||||
@@ -93,7 +93,7 @@ JSON 工具类可参考:[Hutool-Json](https://hutool.cn/docs/#/json/JSONUtil)
|
||||
|
||||
### 在 WebFlux 中注册过滤器
|
||||
`Spring WebFlux`中不提供拦截器机制,因此若你的项目需要路由鉴权功能,过滤器是你唯一的选择,在`Spring WebFlux`注册过滤器的流程与上述流程几乎完全一致,
|
||||
除了您需要将过滤器名称由`SaServletFilter`更换为`SaReactorFilter`以外,其它所有步骤均可参考以上示例
|
||||
除了您需要将过滤器名称由`SaServletFilter`更换为`SaReactorFilter`以外,其它所有步骤均可参考以上示例。
|
||||
``` java
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类
|
||||
|
@@ -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
|
||||
/**
|
||||
|
@@ -3,17 +3,17 @@
|
||||
|
||||
Sa-token 默认将数据保存在内存中,此模式读写速度最快,且避免了序列化与反序列化带来的性能消耗,但是此模式也有一些缺点,比如:
|
||||
|
||||
1. 重启后数据会丢失
|
||||
2. 无法在分布式环境中共享数据
|
||||
1. 重启后数据会丢失。
|
||||
2. 无法在分布式环境中共享数据。
|
||||
|
||||
为此,Sa-Token 提供了扩展接口,你可以轻松将会话数据存储在 `Redis`、`Memcached`等专业的缓存中间件中,
|
||||
做到重启数据不丢失,而且保证分布式环境下多节点的会话一致性
|
||||
做到重启数据不丢失,而且保证分布式环境下多节点的会话一致性。
|
||||
|
||||
以下是官方提供的 Redis 集成包:
|
||||
|
||||
---
|
||||
|
||||
### 方式1. Sa-Token 整合 Redis (使用jdk默认序列化方式)
|
||||
### 方式1、Sa-Token 整合 Redis (使用 jdk 默认序列化方式)
|
||||
``` xml
|
||||
<!-- Sa-Token 整合 Redis (使用 jdk 默认序列化方式) -->
|
||||
<dependency>
|
||||
@@ -22,10 +22,10 @@ Sa-token默认将数据保存在内存中,此模式读写速度最快,且避
|
||||
<version>${sa.top.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
优点:兼容性好,缺点:Session序列化后基本不可读,对开发者来讲等同于乱码
|
||||
优点:兼容性好,缺点:Session 序列化后基本不可读,对开发者来讲等同于乱码。
|
||||
|
||||
|
||||
### 方式2. Sa-Token 整合 Redis(使用jackson序列化方式)
|
||||
### 方式2、Sa-Token 整合 Redis(使用 jackson 序列化方式)
|
||||
``` xml
|
||||
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
|
||||
<dependency>
|
||||
@@ -34,7 +34,7 @@ Sa-token默认将数据保存在内存中,此模式读写速度最快,且避
|
||||
<version>${sa.top.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
优点:Session序列化后可读性强,可灵活手动修改,缺点:兼容性稍差
|
||||
优点:Session 序列化后可读性强,可灵活手动修改,缺点:兼容性稍差。
|
||||
|
||||
|
||||
### 集成 Redis 请注意:
|
||||
@@ -80,10 +80,10 @@ spring:
|
||||
|
||||
|
||||
**3. 集成 Redis 后,是我额外手动保存数据,还是框架自动保存?** <br>
|
||||
框架自动保存。集成`Redis`只需要引入对应的`pom依赖`即可,框架所有上层API保持不变
|
||||
框架自动保存。集成 `Redis` 只需要引入对应的 `pom依赖` 即可,框架所有上层 API 保持不变。
|
||||
|
||||
**4. 集成包版本问题** <br>
|
||||
Sa-Token-Redis 集成包的版本尽量与 Sa-Token-Starter 集成包的版本一致,否则可能出现兼容性问题
|
||||
Sa-Token-Redis 集成包的版本尽量与 Sa-Token-Starter 集成包的版本一致,否则可能出现兼容性问题。
|
||||
|
||||
|
||||
<br><br>
|
||||
|
@@ -1,41 +1,41 @@
|
||||
# 多账户认证
|
||||
---
|
||||
|
||||
### 0、需求场景
|
||||
### 1、需求场景
|
||||
有的时候,我们会在一个项目中设计两套账号体系,比如一个电商系统的 `user表` 和 `admin表`,
|
||||
在这种场景下,如果两套账号我们都使用 `StpUtil` 类的API进行登录鉴权,那么势必会发生逻辑冲突
|
||||
在这种场景下,如果两套账号我们都使用 `StpUtil` 类的API进行登录鉴权,那么势必会发生逻辑冲突。
|
||||
|
||||
在Sa-Token中,这个问题的模型叫做:多账户体系认证
|
||||
在Sa-Token中,这个问题的模型叫做:多账户体系认证。
|
||||
|
||||
要解决这个问题,我们必须有一个合理的机制将这两套账号的授权给区分开,让它们互不干扰才行
|
||||
要解决这个问题,我们必须有一个合理的机制将这两套账号的授权给区分开,让它们互不干扰才行。
|
||||
|
||||
|
||||
### 1、演进思路
|
||||
### 2、演进思路
|
||||
假如说我们的 user表 和 admin表 都有一个 id=10001 的账号,它们对应的登录代码:`StpUtil.login(10001)` 是一样的,
|
||||
那么问题来了:在`StpUtil.getLoginId()`获取到的账号id如何区分它是User用户,还是Admin用户?
|
||||
|
||||
你可能会想到为他们加一个固定前缀,比如`StpUtil.login("User_" + 10001)`、`StpUtil.login("Admin_" + 10001)`,这样确实是可以解决问题的,
|
||||
但是同样的:你需要在`StpUtil.getLoginId()`时再裁剪掉相应的前缀才能获取真正的账号id,这样一增一减就让我们的代码变得无比啰嗦
|
||||
但是同样的:你需要在`StpUtil.getLoginId()`时再裁剪掉相应的前缀才能获取真正的账号id,这样一增一减就让我们的代码变得无比啰嗦。
|
||||
|
||||
那么,有没有从框架层面支持的,更优雅的解决方案呢?
|
||||
|
||||
|
||||
### 2、解决方案
|
||||
### 3、解决方案
|
||||
|
||||
前面几篇介绍的api调用,都是经过 StpUtil 类的各种静态方法进行授权认证,
|
||||
而如果我们深入它的源码,[点此阅览](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 类的所有函数都可以被重写,按需扩展
|
||||
- 在构造方法时随意传入一个不同的 `loginType`,就可以再造一套账号登录体系
|
||||
- StpLogic 类的所有函数都可以被重写,按需扩展。
|
||||
- 在构造方法时随意传入一个不同的 `loginType`,就可以再造一套账号登录体系。
|
||||
|
||||
|
||||
### 3、操作示例
|
||||
### 4、操作示例
|
||||
|
||||
比如说,对于原生`StpUtil`类,我们只做`admin账号`权限认证,而对于`user账号`,我们则:
|
||||
1. 新建一个新的权限认证类,比如: `StpUserUtil.java`
|
||||
2. 将`StpUtil.java`类的全部代码复制粘贴到 `StpUserUtil.java`里
|
||||
1. 新建一个新的权限认证类,比如: `StpUserUtil.java`。
|
||||
2. 将`StpUtil.java`类的全部代码复制粘贴到 `StpUserUtil.java`里。
|
||||
3. 更改一下其 `LoginType`, 比如:
|
||||
|
||||
``` 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)
|
||||
|
||||
|
||||
### 4、在多账户模式下使用注解鉴权
|
||||
框架默认的注解鉴权 如`@SaCheckLogin` 只针对原生`StpUtil`进行鉴权
|
||||
### 5、在多账户模式下使用注解鉴权
|
||||
框架默认的注解鉴权 如`@SaCheckLogin` 只针对原生`StpUtil`进行鉴权。
|
||||
|
||||
例如,我们在一个方法上加上`@SaCheckLogin`注解,这个注解只会放行通过`StpUtil.login(id)`进行登录的会话,
|
||||
而对于通过`StpUserUtil.login(id)`进行登录的都会话,则始终不会通过校验
|
||||
而对于通过`StpUserUtil.login(id)`进行登录的都会话,则始终不会通过校验。
|
||||
|
||||
那么如何告诉`@SaCheckLogin`要鉴别的是哪套账号的登录会话呢?很简单,你只需要指定一下注解的type属性即可:
|
||||
|
||||
@@ -72,17 +72,18 @@ public String info() {
|
||||
}
|
||||
```
|
||||
|
||||
注:`@SaCheckRole("xxx")`、`@SaCheckPermission("xxx")`同理,亦可根据type属性指定其校验的账号体系,此属性默认为`""`,代表使用原生`StpUtil`账号体系
|
||||
注:`@SaCheckRole("xxx")`、`@SaCheckPermission("xxx")`同理,亦可根据type属性指定其校验的账号体系,此属性默认为`""`,代表使用原生`StpUtil`账号体系。
|
||||
|
||||
|
||||
### 5、使用注解合并简化代码
|
||||
交流群里有同学反应,虽然可以根据 `@SaCheckLogin(type = "user")` 指定账号类型,但几十上百个注解都加上这个的话,还是有些繁琐,代码也不够优雅,有么有更改的解决方案?
|
||||
### 6、使用注解合并简化代码
|
||||
交流群里有同学反应,虽然可以根据 `@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
|
||||
@Configuration
|
||||
@@ -97,7 +98,7 @@ public class SaTokenConfigure {
|
||||
}
|
||||
```
|
||||
|
||||
2. 自定义一个注解
|
||||
2. 自定义一个注解:
|
||||
|
||||
``` java
|
||||
/**
|
||||
@@ -112,7 +113,7 @@ public @interface SaUserCheckLogin {
|
||||
}
|
||||
```
|
||||
|
||||
3. 接下来就可以使用我们的自定义注解了
|
||||
3. 接下来就可以使用我们的自定义注解了:
|
||||
|
||||
``` java
|
||||
// 使用 @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、同端多登陆
|
||||
假设我们不仅需要在后台同时集成两套账号,我们还需要在一个客户端同时登陆两套账号(业务场景举例:一个APP中可以同时登陆商家账号和用户账号)
|
||||
### 7、同端多登陆
|
||||
假设我们不仅需要在后台同时集成两套账号,我们还需要在一个客户端同时登陆两套账号(业务场景举例:一个APP中可以同时登陆商家账号和用户账号)。
|
||||
|
||||
如果我们不做任何特殊处理的话,在客户端会发生`token覆盖`,新登录的token会覆盖掉旧登录的token从而导致旧登录失效
|
||||
如果我们不做任何特殊处理的话,在客户端会发生`token覆盖`,新登录的token会覆盖掉旧登录的token从而导致旧登录失效。
|
||||
|
||||
那么如何解决这个问题?<br>
|
||||
很简单,我们只要更改一下 `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 对象, 也很简单,参考示例如下:
|
||||
|
||||
``` java
|
||||
|
@@ -2,10 +2,10 @@
|
||||
---
|
||||
|
||||
|
||||
以上介绍的api都是操作当前账号,对当前账号进行各种鉴权操作,你可能会问,我能不能对别的账号进行一些操作?<br>
|
||||
以上介绍的 API 都是操作当前账号,对当前账号进行各种鉴权操作,你可能会问,我能不能对别的账号进行一些操作?<br>
|
||||
比如:查看账号 10001 有无某个权限码、获取 账号 id=10002 的 `User-Session`,等等...
|
||||
|
||||
Sa-Token在api设计时充分考虑了这一点,暴露出多个api进行此类操作
|
||||
Sa-Token 在 API 设计时充分考虑了这一点,暴露出多个api进行此类操作:
|
||||
|
||||
|
||||
## 有关操作其它账号的api
|
||||
@@ -36,7 +36,7 @@ StpUtil.hasPermission(10001, "user:add");
|
||||
|
||||
有时候,我们需要直接将当前会话的身份切换为其它账号,比如:
|
||||
``` java
|
||||
// 将当前会话[身份临时切换]为其它账号
|
||||
// 将当前会话[身份临时切换]为其它账号(本次请求内有效)
|
||||
StpUtil.switchTo(10044);
|
||||
|
||||
// 此时再调用此方法会返回 10044 (我们临时切换到的账号id)
|
||||
@@ -46,12 +46,12 @@ StpUtil.getLoginId();
|
||||
StpUtil.endSwitch();
|
||||
```
|
||||
|
||||
你还可以: 直接在一个代码段里方法内,临时切换身份为指定loginId(此方式无需手动调用`StpUtil.endSwitch()`关闭身份切换)
|
||||
你还可以:直接在一个代码段里方法内,临时切换身份为指定loginId(此方式无需手动调用`StpUtil.endSwitch()`关闭身份切换)
|
||||
``` java
|
||||
System.out.println("------- [身份临时切换]调用开始...");
|
||||
StpUtil.switchTo(10044, () -> {
|
||||
System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch());
|
||||
System.out.println("获取当前登录账号id: " + StpUtil.getLoginId());
|
||||
System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch()); // 输出 true
|
||||
System.out.println("获取当前登录账号id: " + StpUtil.getLoginId()); // 输出 10044
|
||||
});
|
||||
System.out.println("------- [身份临时切换]调用结束...");
|
||||
```
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# 同端互斥登录
|
||||
|
||||
如果你经常使用腾讯QQ,就会发现它的登录有如下特点:它可以手机电脑同时在线,但是不能在两个手机上同时登录一个账号 <br/>
|
||||
同端互斥登录,指的就是像腾讯QQ一样,在同一类型设备上只允许单地点登录,在不同类型设备上允许同时在线
|
||||
如果你经常使用腾讯QQ,就会发现它的登录有如下特点:它可以手机电脑同时在线,但是不能在两个手机上同时登录一个账号。 <br/>
|
||||
同端互斥登录,指的就是:像腾讯QQ一样,在同一类型设备上只允许单地点登录,在不同类型设备上允许同时在线。
|
||||
|
||||
|
||||
<button class="show-img" img-src="https://oss.dev33.cn/sa-token/doc/g/g3--mutex-login.gif">加载动态演示图</button>
|
||||
|
@@ -3,31 +3,46 @@
|
||||
|
||||
### 何为无 Cookie 模式?
|
||||
|
||||
无Cookie:特指不支持Cookie功能的终端,通俗来讲就是我们常说的 —— **前后台分离模式**
|
||||
无 Cookie 模式:特指不支持 Cookie 功能的终端,通俗来讲就是我们常说的 —— **前后台分离模式**。
|
||||
|
||||
常规 Web 端鉴权方法,一般由 `Cookie模式` 完成,而 Cookie 有两个特性:
|
||||
1. 可由后端控制写入
|
||||
2. 每次请求自动提交
|
||||
常规 Web 端鉴权方法,一般由 `Cookie模式` 完成,而 Cookie 有两个特性:
|
||||
1. 可由后端控制写入。
|
||||
2. 每次请求自动提交。
|
||||
|
||||
这就使得我们在前端代码中,无需任何特殊操作,就能完成鉴权的全部流程(因为整个流程都是后端控制完成的)<br/>
|
||||
而在app、小程序等前后台分离场景中,一般是没有 Cookie 这一功能的,此时大多数人都会一脸懵逼,咋进行鉴权啊?
|
||||
|
||||
见招拆招,其实答案很简单:
|
||||
- 不能后端控制写入了,就前端自己写入(难点在**后端如何将 Token 传递到前端**)
|
||||
- 每次请求不能自动提交了,那就手动提交(难点在**前端如何将 Token 传递到后端**,同时**后端将其读取出来**)
|
||||
- 不能后端控制写入了,就前端自己写入。(难点在**后端如何将 Token 传递到前端**)
|
||||
- 每次请求不能自动提交了,那就手动提交。(难点在**前端如何将 Token 传递到后端**,同时**后端将其读取出来**)
|
||||
|
||||
|
||||
|
||||
### 1、后端将 token 返回到前端
|
||||
|
||||
1. 首先调用 `StpUtil.login(id)` 进行登录
|
||||
2. 调用 `StpUtil.getTokenInfo()` 返回当前会话的token详细参数
|
||||
- 此方法返回一个对象,其有两个关键属性:`tokenName`和`tokenValue`(token 的名称和 token 的值)
|
||||
- 将此对象传递到前台,让前端人员将这两个值保存到本地
|
||||
1. 首先调用 `StpUtil.login(id)` 进行登录。
|
||||
2. 调用 `StpUtil.getTokenInfo()` 返回当前会话的 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 提交到后端
|
||||
1. 无论是app还是小程序,其传递方式都大同小异
|
||||
2. 那就是,将 token 塞到请求`header`里 ,格式为:`{tokenName: tokenValue}`
|
||||
1. 无论是app还是小程序,其传递方式都大同小异。
|
||||
2. 那就是,将 token 塞到请求`header`里 ,格式为:`{tokenName: tokenValue}`。
|
||||
3. 以经典跨端框架 [uni-app](https://uniapp.dcloud.io/) 为例:
|
||||
|
||||
**方式1,简单粗暴**
|
||||
@@ -76,14 +91,14 @@ uni.request({
|
||||
});
|
||||
```
|
||||
|
||||
4. 只要按照如此方法将`token`值传递到后端,Sa-Token 就能像传统PC端一样自动读取到 token 值,进行鉴权
|
||||
5. 你可能会有疑问,难道我每个`ajax`都要写这么一坨?岂不是麻烦死了
|
||||
- 你当然不能每个 ajax 都写这么一坨,因为这种重复性代码都是要封装在一个函数里统一调用的
|
||||
4. 只要按照如此方法将`token`值传递到后端,Sa-Token 就能像传统PC端一样自动读取到 token 值,进行鉴权。
|
||||
5. 你可能会有疑问,难道我每个`ajax`都要写这么一坨?岂不是麻烦死了?
|
||||
- 你当然不能每个 ajax 都写这么一坨,因为这种重复性代码都是要封装在一个函数里统一调用的。
|
||||
|
||||
|
||||
### 其它解决方案?
|
||||
如果你对 Cookie 非常了解,那你就会明白,所谓 Cookie ,本质上就是一个特殊的`header`参数而已,
|
||||
而既然它只是一个 header 参数,我们就能手动模拟实现它,从而完成鉴权操作
|
||||
而既然它只是一个 header 参数,我们就能手动模拟实现它,从而完成鉴权操作。
|
||||
|
||||
这其实是对`无Cookie模式`的另一种解决方案,有兴趣的同学可以百度了解一下,在此暂不赘述
|
||||
这其实是对`无Cookie模式`的另一种解决方案,有兴趣的同学可以百度了解一下,在此暂不赘述。
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# 密码加密
|
||||
|
||||
严格来讲,密码加密不属于 [权限认证] 的范畴,但是对于大多数系统来讲,密码加密又是安全认证不可或缺的部分,
|
||||
所以,应大家要求,`Sa-Token`在`v1.14版本`添加密码加密模块,该模块非常简单,仅仅封装了一些常见的加密算法
|
||||
所以,应大家要求,`Sa-Token`在 v1.14 版本添加密码加密模块,该模块非常简单,仅仅封装了一些常见的加密算法。
|
||||
|
||||
|
||||
|
||||
|
@@ -1,16 +1,16 @@
|
||||
# [记住我] 模式
|
||||
---
|
||||
|
||||
如图所示,一般网站的登录界面都会有一个 **`[记住我]`** 按钮,当你勾选它后,即使你关闭浏览器再次打开网站,也依然会处于登录状态,无须重复验证密码
|
||||
如图所示,一般网站的登录界面都会有一个 **`[记住我]`** 按钮,当你勾选它后,即使你关闭浏览器再次打开网站,也依然会处于登录状态,无须重复验证密码:
|
||||
|
||||

|
||||
|
||||
那么在Sa-Token中,如何做到 [ 记住我 ] 功能呢?
|
||||
|
||||
|
||||
### 在sa-token中实现记住我功能
|
||||
### 在 Sa-Token 中实现记住我功能
|
||||
|
||||
Sa-Token的登录授权,**默认就是`[记住我]`模式**,为了实现`[非记住我]`模式, 你需要在登录时如下设置:
|
||||
Sa-Token的登录授权,**默认就是`[记住我]`模式**,为了实现`[非记住我]`模式,你需要在登录时如下设置:
|
||||
|
||||
``` java
|
||||
// 设置登录账号id为10001,第二个参数指定是否为[记住我],当此值为false后,关闭浏览器后再次打开需要重新登录
|
||||
@@ -22,12 +22,12 @@ StpUtil.login(10001, false);
|
||||
|
||||
### 实现原理
|
||||
Cookie作为浏览器提供的默认会话跟踪机制,其生命周期有两种形式,分别是:
|
||||
- 临时Cookie:有效期为本次会话,只要关闭浏览器窗口,Cookie就会消失
|
||||
- 持久Cookie:有效期为一个具体的时间,在时间未到期之前,即使用户关闭了浏览器Cookie也不会消失
|
||||
- 临时Cookie:有效期为本次会话,只要关闭浏览器窗口,Cookie就会消失。
|
||||
- 持久Cookie:有效期为一个具体的时间,在时间未到期之前,即使用户关闭了浏览器Cookie也不会消失。
|
||||
|
||||
利用Cookie的此特性,我们便可以轻松实现 [记住我] 模式:
|
||||
- 勾选 [记住我] 按钮时:调用`StpUtil.login(10001, true)`,在浏览器写入一个`持久Cookie`储存 Token,此时用户即使重启浏览器 Token 依然有效
|
||||
- 不勾选 [记住我] 按钮时:调用`StpUtil.login(10001, false)`,在浏览器写入一个`临时Cookie`储存 Token,此时用户在重启浏览器后 Token 便会消失,导致会话失效
|
||||
- 勾选 [记住我] 按钮时:调用`StpUtil.login(10001, true)`,在浏览器写入一个`持久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>
|
||||
@@ -38,7 +38,7 @@ Cookie作为浏览器提供的默认会话跟踪机制,其生命周期有两
|
||||
此时机智的你😏很快发现一个问题,Cookie虽好,却无法在前后端分离环境下使用,那是不是代表上述方案在APP、小程序等环境中无效?
|
||||
|
||||
准确的讲,答案是肯定的,任何基于Cookie的认证方案在前后台分离环境下都会失效(原因在于这些客户端默认没有实现Cookie功能),不过好在,这些客户端一般都提供了替代方案,
|
||||
唯一遗憾的是,此场景中token的生命周期需要我们在前端手动控制
|
||||
唯一遗憾的是,此场景中token的生命周期需要我们在前端手动控制:
|
||||
|
||||
以经典跨端框架 [uni-app](https://uniapp.dcloud.io/) 为例,我们可以使用如下方式达到同样的效果:
|
||||
``` js
|
||||
@@ -62,8 +62,8 @@ Remember me, it's too easy!
|
||||
|
||||
|
||||
|
||||
### 登录时指定token有效期
|
||||
登录时不仅可以指定是否为`[记住我]`模式,还可以指定一个特定的时间作为token有效时长,如下示例:
|
||||
### 登录时指定 Token 有效期
|
||||
登录时不仅可以指定是否为`[记住我]`模式,还可以指定一个特定的时间作为 Token 有效时长,如下示例:
|
||||
``` java
|
||||
// 示例1:
|
||||
// 指定token有效期(单位: 秒),如下所示token七天有效
|
||||
|
@@ -1,18 +1,18 @@
|
||||
# 二级认证
|
||||
|
||||
在某些敏感操作下,我们需要对已登录的会话进行二次验证
|
||||
在某些敏感操作下,我们需要对已登录的会话进行二次验证。
|
||||
|
||||
比如代码托管平台的仓库删除操作,尽管我们已经登录了账号,当我们点击 **[删除]** 按钮时,还是需要再次输入一遍密码,这么做主要为了两点:
|
||||
|
||||
1. 保证操作者是当前账号本人
|
||||
2. 增加操作步骤,防止误删除重要数据
|
||||
1. 保证操作者是当前账号本人。
|
||||
2. 增加操作步骤,防止误删除重要数据。
|
||||
|
||||
这就是我们本篇要讲的 —— 二级认证,即:在已登录会话的基础上,进行再次验证,提高会话的安全性。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 具体API
|
||||
### 具体API
|
||||
|
||||
在`Sa-Token`中进行二级认证非常简单,只需要使用以下API:
|
||||
|
||||
@@ -34,7 +34,7 @@ StpUtil.closeSafe();
|
||||
```
|
||||
|
||||
|
||||
## 使用注解进行二级认证
|
||||
### 使用注解进行二级认证
|
||||
在一个方法上使用 `@SaCheckSafe` 注解,可以在代码进入之前此方法之前进行一次二级认证
|
||||
``` java
|
||||
// 二级认证:必须二级认证之后才能进入该方法
|
||||
@@ -48,3 +48,47 @@ public String add() {
|
||||
详细使用方法可参考:[注解鉴权](/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. 后端校验会话已完成二级认证,仓库删除成功。
|
||||
|
||||
|
@@ -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值才会被查询出来
|
||||
- `start`: 数据开始处索引, 值为-1时代表一次性取出所有数据
|
||||
- `size`: 要获取的数据条数
|
||||
- `keyword`: 查询关键字,只有包括这个字符串的 token 值才会被查询出来。
|
||||
- `start`: 数据开始处索引, 值为-1时代表一次性取出所有数据。
|
||||
- `size`: 要获取的数据条数。
|
||||
|
||||
使用示例:
|
||||
``` java
|
||||
@@ -39,10 +40,10 @@ for (String token : tokenList) {
|
||||
|
||||
#### 注意事项:
|
||||
由于会话查询底层采用了遍历方式获取数据,当数据量过大时此操作将会比较耗时,有多耗时呢?这里提供一份参考数据:
|
||||
- 单机模式下:百万会话取出10条token平均耗时 `0.255s`
|
||||
- Redis模式下:百万会话取出10条token平均耗时 `3.322s`
|
||||
- 单机模式下:百万会话取出10条 Token 平均耗时 `0.255s`。
|
||||
- Redis模式下:百万会话取出10条 Token 平均耗时 `3.322s`。
|
||||
|
||||
请根据业务实际水平合理调用API
|
||||
请根据业务实际水平合理调用API。
|
||||
|
||||
|
||||
> 如果需要实时获取当前登录人数或者需要在用户退出后自动触发某事件等, 建议采用websocket技术
|
||||
> 如果需要实时获取当前登录人数或者需要在用户退出后自动触发某事件等, 建议采用websocket技术。
|
@@ -10,7 +10,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
此时后端如果不做任何特殊处理,框架将会把`Bearer `视为token的一部分,无法正常读取token信息,导致鉴权失败
|
||||
此时后端如果不做任何特殊处理,框架将会把`Bearer `视为token的一部分,无法正常读取token信息,导致鉴权失败。
|
||||
|
||||
为此,我们需要在yml中添加如下配置:
|
||||
``` java
|
||||
@@ -19,12 +19,12 @@ sa-token:
|
||||
token-prefix: Bearer
|
||||
```
|
||||
|
||||
此时 Sa-Token 便可在读取 Token 时裁剪掉 `Bearer`,成功获取`xxxx-xxxx-xxxx-xxxx`
|
||||
此时 Sa-Token 便可在读取 Token 时裁剪掉 `Bearer`,成功获取`xxxx-xxxx-xxxx-xxxx`。
|
||||
|
||||
|
||||
### 注意点
|
||||
|
||||
1. Token前缀 与 Token值 之间必须有一个空格
|
||||
2. 一旦配置了 Token前缀,则前端提交token时,必须带有前缀,否则会导致框架无法读取token
|
||||
3. 由于`Cookie`中无法存储空格字符,也就意味配置token前缀后,Cookie鉴权方式将会失效,此时只能将token提交到`header`里进行传输
|
||||
1. Token前缀 与 Token值 之间必须有一个空格。
|
||||
2. 一旦配置了 Token前缀,则前端提交 `Token` 时,必须带有前缀,否则会导致框架无法读取 Token。
|
||||
3. 由于`Cookie`中无法存储空格字符,也就意味配置 Token 前缀后,Cookie 鉴权方式将会失效,此时只能将 Token 提交到`header`里进行传输。
|
||||
|
||||
|
@@ -1,14 +1,14 @@
|
||||
# 自定义 Token 风格
|
||||
|
||||
本篇介绍token生成的各种风格,以及自定义token生成策略
|
||||
本篇介绍token生成的各种风格,以及自定义token生成策略。
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 内置风格
|
||||
|
||||
Sa-Token默认的token生成策略是uuid风格,其模样类似于:`623368f0-ae5e-4475-a53f-93e4225f16ae`<br>
|
||||
如果你对这种风格不太感冒,还可以将token生成设置为其他风格
|
||||
Sa-Token默认的token生成策略是uuid风格,其模样类似于:`623368f0-ae5e-4475-a53f-93e4225f16ae`。<br>
|
||||
如果你对这种风格不太感冒,还可以将token生成设置为其他风格。
|
||||
|
||||
怎么设置呢?只需要在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` 算法即可:
|
||||
|
||||
|
||||
#### 参考步骤如下:
|
||||
|
@@ -121,7 +121,7 @@ Server 端:
|
||||
| ticketTimeout | long | 300 | ticket 有效期 (单位: 秒) |
|
||||
| allowUrl | String | * | 所有允许的授权回调地址,多个用逗号隔开(不在此列表中的URL将禁止下放ticket),参考:[SSO整合:配置域名校验](/sso/sso-check-domain) |
|
||||
| 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模式三单点注销的接口通信身份校验) |
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ Client 端:
|
||||
| :-------- | :-------- | :-------- | :-------- |
|
||||
| authUrl | String | null | 配置 Server 端单点登录授权地址 |
|
||||
| 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 校验地址 |
|
||||
| userinfoUrl | String | null | 配置 Server 端查询 userinfo 地址 |
|
||||
| sloUrl | String | null | 配置 Server 端单点注销地址 |
|
||||
@@ -244,3 +244,11 @@ sa-token:
|
||||
- 此值为 false 时:四种模式(`isCode、isImplicit、isPassword、isClient`)是否生效,依靠局部配置+全局配置(两个都为 true 时才打开)
|
||||
|
||||
|
||||
#### isHttp
|
||||
|
||||
配置含义:是否打开单点登录模式三。
|
||||
|
||||
- 此配置项为 false 时,代表使用SSO模式二:使用 Redis 校验 ticket 值、删除 Redis 数据做到单点注销、使用 Redis 同步 Userinfo 数据。
|
||||
- 此配置项为 true 时,代表使用SSO模式三:使用 Http 请求校验 ticket 值、使用 Http 请求做到单点注销、使用 Http 请求同步 Userinfo 数据。
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user