重构文档结构

This commit is contained in:
click33
2022-10-10 00:59:08 +08:00
parent a27ed0ce88
commit 94e572c087
120 changed files with 286 additions and 49 deletions

View File

@@ -0,0 +1,15 @@
# Sa-Token 功能结构图
---
### Sa-Token 功能结构图:
![sa-token-rz](https://oss.dev33.cn/sa-token/art/sa-token-js4.png 's-w')
### Sa-Token 认证流程图:
![sa-token-rz](https://oss.dev33.cn/sa-token/art/sa-token-rz2.png 's-w')
<!-- ![sa-token-rz](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/sa-token-rz.png 's-w') -->
PS鼠标右键选择 **`[在新窗口打开图片]`** 即可高清模式查看图片。(本流程图使用 [ProcessOn](https://www.processon.com) 绘制完成)

View File

@@ -0,0 +1,3 @@
# 解决跨域问题
参考:[https://blog.csdn.net/shengzhang_/article/details/119928794](https://blog.csdn.net/shengzhang_/article/details/119928794)

View File

@@ -0,0 +1,73 @@
# 解决反向代理 uri 丢失的问题
---
使用 `request.getRequestURL()` 可获取当前程序所在外网的访问地址,在 Sa-Token 中,其 `SaHolder.getRequest().getUrl()` 也正是借助此API完成
有很多模块都用到了这个能力比如SSO单点登录。
我们可以使用如下代码测试此API
``` java
// 显示当前程序所在外网的都访问地址
@RequestMapping("test")
public String test() {
return "您访问的是:" + SaHolder.getRequest().getUrl();
}
```
从浏览器访问此接口,我们可以看到:
![test-curr-domain.png](https://oss.dev33.cn/sa-token/doc/test-curr-domain.png 's-w-sh')
此 API 在本地开发时一般可以正常工作,然而如果我们在部署时使用 Nginx 做了一层反向代理后,其最终结果可能会和我们预想的有一点偏差:
![test-curr-domain-fxdl.png](https://oss.dev33.cn/sa-token/doc/test-curr-domain-fxdl.png 's-w-sh')
不仅是 Nginx所有包含路由转发的地方都有可能导致上述丢失 uri 的现象解决方案也很简单既然程序无法自动识别我们改成手动获取即可Sa-Token 提供两个方案:
### 方案一Nginx转发时追加 header 参数
##### 1、首先在 Nginx 代理转发的地方增加参数
![nginx-add-header.png](https://oss.dev33.cn/sa-token/doc/nginx-add-header.png 's-w-sh')
重点是这一句:`proxy_set_header Public-Network-URL http://$http_host$request_uri;`
##### 2、在程序中新增类 `CustomSaTokenContextForSpring.java`重写获取uri的逻辑
``` java
@Primary
@Component
public class CustomSaTokenContextForSpring extends SaTokenContextForSpring {
@Override
public SaRequest getRequest() {
return new SaRequestForServlet(SpringMVCUtil.getRequest()) {
@Override
public String getUrl() {
if(request.getHeader("Public-Network-URL") != null) {
return request.getHeader("Public-Network-URL");
}
return request.getRequestURL().toString();
}
};
}
}
```
其它逻辑保持不变,框架即可正确获取 uri 地址
!> 注意:步骤一与步骤二需要同步存在,否则可能有前端假传 header 参数造成安全问题
### 方案二直接在yml中配置当前项目的网络访问地址
在 `application.yml` 中增加配置:
``` yml
sa-token:
# 配置当前项目的网络访问地址
curr-domain: http://local.dev33.cn:8902/api
```
即可避免路由转发过程中丢失 uri 的问题

View File

@@ -0,0 +1,79 @@
# 参考:把路由拦截鉴权动态化
框架提供的示例是硬代码写死的,不过稍微做一下更改,你就可以让他动态化
---
参考如下:
``` java
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(handle -> {
SaRouter
.match("/**")
.notMatch(excludePaths())
.check(r -> StpUtil.checkLogin());
})).addPathPatterns("/**");
}
// 动态获取哪些 path 可以忽略鉴权
public List<String> excludePaths() {
// 此处仅为示例实际项目你可以写任意代码来查询这些path
return Arrays.asList("/path1", "/path2", "/path3");
}
```
如果不仅仅是登录校验,还需要鉴权,那也很简单:
``` java
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(handle -> {
// 遍历校验规则,依次鉴权
Map<String, String> rules = getAuthRules();
for (String path : rules.keySet()) {
SaRouter.match(path, () -> StpUtil.checkPermission(rules.get(path)));
}
})).addPathPatterns("/**");
}
// 动态获取鉴权规则
public Map<String, String> getAuthRules() {
// key 代表要拦截的 pathvalue 代表需要校验的权限
Map<String, String> authMap = new LinkedHashMap<>();
authMap.put("/user/**", "user");
authMap.put("/admin/**", "admin");
authMap.put("/article/**", "article");
// 更多规则 ...
return authMap;
}
```
---
错误的写法:
``` java
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(handle -> {
StpUtil.checkLogin();
}))
.addPathPatterns("/**")
.excludePathPatterns(excludePaths());
}
// 动态获取哪些 path 可以忽略鉴权
public List<String> excludePaths() {
// 此处仅为示例实际项目你可以写任意代码来查询这些path
return Arrays.asList("/path1", "/path2", "/path3");
}
```
错误点:因为 lambda 表达式之外的代码只会在启动时执行一次,所以 `excludePaths()` 方法是无法做到动态化读取的,
若要在项目运行时动态读写,必须把调用 `excludePaths()` 的时机放在 lambda 里。

View File

@@ -0,0 +1,107 @@
# 异常细分状态码
---
### 获取异常细分状态码
Sa-Token 中的基础异常类是 `SaTokenException`,在此基础上,又针对一些特定场景划分出诸如 `NotLoginException``NotPermissionException` 等。
但是框架中异常抛出点远远多于异常种类的划分,比如在 SSO 插件中,[ redirect 重定向地址无效 ] 和 [ ticket 参数值无效 ] 都会导致 SSO 授权的失败,
但是它们抛出的异常都是 `SaSsoException`,如果你需要对这两种异常情形做出不同的处理,仅仅判断异常的 ClassType 显然不够。
为了解决上述需求Sa-Token 对每个异常抛出点都会指定一个特定的 code 值,就像这样:
``` java
if(SaFoxUtil.isUrl(url) == false) {
throw new SaSsoException("无效redirect" + url).setCode(SaSsoExceptionCode.CODE_20001);
}
```
就像是打上一个特定的标记,不同异常情形标记的 code 码值也会不同,这就为你精细化异常处理提供了前提。
要在捕获异常时获取这个 code 码也非常简单:
``` java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(SaTokenException.class)
public SaResult handlerSaTokenException(SaTokenException e) {
// 根据不同异常细分状态码返回不同的提示
if(e.getCode() == 20001) {
return SaResult.error("redirect 重定向 url 是一个无效地址");
}
if(e.getCode() == 20002) {
return SaResult.error("redirect 重定向 url 不在 allowUrl 允许的范围内");
}
if(e.getCode() == 20004) {
return SaResult.error("提供的 ticket 是无效的");
}
// 更多 code 码判断 ...
// 默认的提示
return SaResult.error("服务器繁忙,请稍后重试...");
}
}
```
SaToken 中的所有异常都是继承于 `SaTokenException` 的,也就是说,所有异常你都可以通过 `e.getCode()` 的方式获取对应的异常细分状态码。
### 异常细分状态码-参照表
!> 目前仅对 sso 插件和 jwt 插件做了异常状态码细分,后续版本升级会支持更多模块
**sa-token-code 核心包:**
| code码值 | 含义 |
| :-------- | :-------- |
| -1 | 代表这个异常在抛出时未指定异常细分状态码 |
**sa-token-sso 单点登录相关:**
| code码值 | 含义 |
| :-------- | :-------- |
| 20001 | `redirect` 重定向 url 是一个无效地址 |
| 20002 | `redirect` 重定向 url 不在 allowUrl 允许的范围内 |
| 20003 | 接口调用方提供的 `secretkey` 秘钥无效 |
| 20004 | 提供的 `ticket` 是无效的 |
| 20005 | 在模式三下sso-client 调用 sso-server 端 校验ticket接口 时,得到的响应是校验失败 |
| 20006 | 在模式三下sso-client 调用 sso-server 端 单点注销接口 时,得到的响应是注销失败 |
| 20007 | http 请求调用 提供的 `timestamp` 与当前时间的差距超出允许的范围 |
| 20008 | http 请求调用 提供的 `sign` 无效 |
| 20009 | 本地系统没有配置 `secretkey` 字段 |
**sa-token-jwt 插件相关:**
| code码值 | 含义 |
| :-------- | :-------- |
| 40101 | 对 jwt 字符串解析失败 |
| 40102 | 此 jwt 的签名无效 |
| 40103 | 此 jwt 的 `loginType` 字段不符合预期 |
| 40104 | 此 jwt 已超时 |

103
sa-token-doc/fun/git-pr.md Normal file
View File

@@ -0,0 +1,103 @@
# 如何更新在线文档
1. 打开要修改的文档页面
2. 滑动右侧页面滑块, 查看页面内容最下方, 评论区上方
3. 找到这一行文字
![在线编辑提示](https://oss.dev33.cn/sa-token/doc/git-pr/online_1.png)
4. 点击Gitee或GitHub按钮中的任意一个, 国内用户推荐使用 [Gitee](https://gitee.com) (请先注册登录后再往下浏览)
5. 此时会进入当前页面源码预览页面,找到下方按钮组
![按钮组](https://oss.dev33.cn/sa-token/doc/git-pr/online_2.png)
6. 点击编辑按钮
7. 此时进入待修改页面的源码页面, 按照markdown格式编辑为需要的结果(Ctrl+P可查看最终效果,再次按下可恢复源码界面)
8. 滑动到最下方点击提交审核即可
# 如何提交代码
## 环境安装过程
1. 在本地[下载Git软件](https://pc.qq.com/detail/13/detail_22693.html)并安装
2. 配置用户名和邮件地址(Gitee或GitHub上关联的邮箱)
```
git config --global user.name "这里替换为你在项目中希望展示的昵称"
git config --global user.email 这里替换为你的关联邮箱
// 查看是否配置正确
git config --list
```
3. 为了让Gitee服务器认可你的身份,需要配置一次SSH Key, 在本地生成密匙对, 公钥上传到Gitee服务器后台
4. 具体方法见[Gitee如何配置SSH](https://gitee.com/help/articles/4181#article-header0), [Github如何配置SSH](https://docs.github.com/cn/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account)
5. 最小开发环境安装包括[Java JDK 8+](https://pc.qq.com/detail/0/detail_18360.html),[Maven 最新版](http://maven.apache.org/download.cgi) 和[idea IDE 社区版](https://www.jetbrains.com/zh-cn/idea/download/#section=windows)
6. 在idea 中[配置Java环境](https://www.baidu.com/s?wd=idea%20%E9%85%8D%E7%BD%AEjava%E7%8E%AF%E5%A2%83)和[配置maven环境](https://www.baidu.com/s?wd=idea%20%E9%85%8D%E7%BD%AEmaven%E7%8E%AF%E5%A2%83), 基础部分不再赘述
## 项目下载过程
1. 点击[Gitee](https://gitee.com/dromara/sa-token)或[Github](https://github.com/dromara/sa-token)进入Sa-Token项目主页, 以下以Gitee为例,Github类似(请先注册登录后再往下浏览)
2. 找到页面右上角的按钮组, 点击Forked按钮
![按钮组](https://oss.dev33.cn/sa-token/doc/git-pr/code_1.png)
3. 选择个人仓库并点击确认
4. 此时在你的个人仓库中会多了一个Sa-Token项目
5. 在新的Sa-Token项目中, 点击 ![克隆/下载](https://oss.dev33.cn/sa-token/doc/git-pr/code_2.png) 按钮, 点击弹出框里面的复制按钮
6. 在本地某空文件夹下右键选择: git bash here
![git bash](https://oss.dev33.cn/sa-token/doc/git-pr/code_4.png)
![git bash 打开后的图](https://oss.dev33.cn/sa-token/doc/git-pr/code_3.png)
14. 在里面输入如下命令, 按换行后自动下载整个项目
```
git clone 这里替换为复制后的链接
```
## 项目载入过程
1. 下载结束后, 开启 idea, 选择 File->Open... 选中项目下载后的Sa-Token文件夹(Trust Project 相信此项目, 否则不可编辑)
2. 这时项目就是可编辑状态, 修改完代码并测试完成后即可提交
## 项目暂存并提交远程
### 方式一
1. 在idea中打开项目进入Commit选项
![本地暂存](https://oss.dev33.cn/sa-token/doc/git-pr/code_5.png)
2. 勾选需要本地暂存的文件
3. 在同一页面的下方输入提示信息
![提示信息](https://oss.dev33.cn/sa-token/doc/git-pr/code_6.png)
4. 点击Commit按钮暂存到本地, 点击Commit and Push按钮暂存之后提交到远程
### 方式二
1. 除了点击Commit and Push按钮外,还有一个地方可以提交git
![git按钮](https://oss.dev33.cn/sa-token/doc/git-pr/code_7.png)
2. 位置在idea右上方的工具栏里面
3. 指向左下箭头为拉取项目,可以随时更新
4. 打对号为本地暂存
5. 指向右上箭头提交远程
## 私人项目推送到主项目
1. 提交后进入Gitee个人仓库中克隆的Sa-Token项目
2. 找到下图的Pull Request按钮
![工具栏](https://oss.dev33.cn/sa-token/doc/git-pr/code_8.png)
3. 点击提交, 进入如下页面
![提交信息填写页面](https://oss.dev33.cn/sa-token/doc/git-pr/code_9.png 's-width')
4. 在这里,你可以选择要提交的分支,一般都是dev开发分支.可以填写合并信息,其他测试审查之类的可以不填写, 最后点击创建即可完成一次提交.
## 远程项目更新
1. 有时候主项目更新了,之前克隆的项目代码陈旧,如何处理?
2. 在个人仓库的Sa-Token项目主页面中, 找到下图的圆圈
![更新按钮](https://oss.dev33.cn/sa-token/doc/git-pr/code_10.png)
3. 点击右侧圆圈按钮后Gitee会自动同步主项目, 这样就不用像我之前一样,删除项目又重新fork了.
## 为什么在国内推荐Gitee
1. 近期Github下载网速较慢
2. Gitee上中文界面方便操作

View File

@@ -0,0 +1,81 @@
# issue 提问模板
在线提问链接:[Gitee issue](https://gitee.com/dromara/sa-token/issues)、[GitHub issue](https://github.com/dromara/sa-token/issues)
> 请在新建 issue 时,尽量复制模板格式进行提交
>
> 1. 提交之前率先参考 <a href="#/more/common-questions" target="_blank">Sa-Token 常见问题解答</a> 以及善用 Gitee issues 搜索功能,查阅问题是否已有答案,已存在的 issue 就不要再重复提交了。
> 2. 问题已得到处理的 issue 请大家及时手动关闭如果超过24小时没有追问我们将默认提交者已找到解决方案关闭issue。
> 3. 有时候 issue 提交之后没有得到及时回复大家可以加入QQ群@管理员寻求帮助。
> 4. 请大家新建 issue 时删除不必要的模板信息、精简语句、**做好代码排版**,对于不方便描述的业务场景,可参阅 <a href="#/more/noun-intro" target="_blank">Sa-Token 名词解释</a> 方便组织语句,这样有助于减低大家的沟通成本。
> 5. **代码截图要带上行号报错信息要把异常堆栈截全页面截图要把地址栏带上Ajax请求要把请求地址、请求头、请求参数都截全**
---
### 预期不符:
``` js
### 使用版本:
### 涉及的功能模块:
### 测试步骤:
+ 我经过以下步骤测试:
+ 得出以下结果:
+ 和文档上描述的不一致:
+ 我的理解是:
请问,是我的理解不对,还是文档出了问题?
```
### bug反馈
``` js
### 使用版本:
### 报错信息:
### 希望结果:
### 复现步骤:
< 备注:如果复现步骤比较复杂,请将 demo 上传到 gitee 并留下地址 >
```
### 功能提问:
``` js
### 对以下问题有疑问:
< 备注:请尽量详细描述问题所在 >
```
### 建议增加新功能:
``` js
### 建议增加的新功能:
### 应用场景阐述:
< 备注:请尽量详细描述功能应用场景 >
```

View File

@@ -0,0 +1,68 @@
# 参考:将权限数据放在缓存里
前面我们讲解了如何通过`StpInterface`接口注入权限数据,框架默认是不提供缓存能力的,如果你想减小数据库的访问压力,则需要将权限数据放到缓存中
---
参考如下:
``` java
/**
* 自定义权限验证接口扩展
*/
@Component
public class StpInterfaceImpl implements StpInterface {
// 返回一个账号所拥有的权限码集合
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 1. 声明权限码集合
List<String> permissionList = new ArrayList<>();
// 2. 遍历角色列表,查询拥有的权限码
for (String roleId : getRoleList(loginId, loginType)) {
SaSession roleSession = SaSessionCustomUtil.getSessionById("role-" + roleId);
List<String> list = roleSession.get("Permission_List", () -> {
return ...; // 从数据库查询这个角色所拥有的权限列表
});
permissionList.addAll(list);
}
// 3. 返回权限码集合
return permissionList;
}
// 返回一个账号所拥有的角色标识集合
@Override
public List<String> getRoleList(Object loginId, String loginType) {
SaSession session = StpUtil.getSessionByLoginId(loginId);
return session.get("Role_List", () -> {
return ...; // 从数据库查询这个账号id拥有的角色列表
});
}
}
```
##### 疑问:为什么不直接缓存 `[账号id->权限列表]`的关系,而是 `[账号id -> 角色id -> 权限列表]`
<!-- ``` java
// 在一个账号登录时写入其权限数据
RedisUtil.setValue("账号id", <权限列表>);
// 然后在`StpInterface`接口中,如下方式获取
List<String> list = RedisUtil.getValue("账号id");
``` -->
答:`[账号id->权限列表]`的缓存方式虽然更加直接粗暴,却有一个严重的问题:
- 通常我们系统的权限架构是RBAC模型权限与用户没有直接的关系而是用户拥有指定的角色角色再拥有指定的权限
- 而这种'拥有关系'是动态的,是可以随时修改的,一旦我们修改了它们的对应关系,便要同步修改或清除对应的缓存数据
现在假设如下业务场景:我们系统中有十万个账号属于同一个角色,当我们变动这个角色的权限时,难道我们要同时清除这十万个账号的缓存信息吗?
这显然是一个不合理的操作同一时间缓存大量清除容易引起Redis的缓存雪崩
而当我们采用 `[账号id -> 角色id -> 权限列表]` 的缓存模型时,则只需要清除或修改 `[角色id -> 权限列表]` 一条缓存即可
一言以蔽之:权限的缓存模型需要跟着权限模型走,角色缓存亦然

View File

@@ -0,0 +1,60 @@
# NotLoginException 场景值
本篇介绍如何根据`NotLoginException`异常的场景值,来定制化处理未登录的逻辑 <br/>
应用场景举例:未登录、被顶下线、被踢下线等场景需要不同方式来处理
## 何为场景值
在前面的章节中,我们了解到,在会话未登录的情况下尝试获取`loginId`会使框架抛出`NotLoginException`异常,而同为未登录异常却有五种抛出场景的区分
| 场景值 | 对应常量 | 含义说明 |
|--- |--- |--- |
| -1 | NotLoginException.NOT_TOKEN | 未能从请求中读取到token |
| -2 | NotLoginException.INVALID_TOKEN| 已读取到token但是token无效 |
| -3 | NotLoginException.TOKEN_TIMEOUT| 已读取到token但是token已经过期 |
| -4 | NotLoginException.BE_REPLACED| 已读取到token但是token已被顶下线 |
| -5 | NotLoginException.KICK_OUT| 已读取到token但是token已被踢下线 |
那么,如何获取场景值呢?废话少说直接上代码:
``` java
// 全局异常拦截拦截项目中的NotLoginException异常
@ExceptionHandler(NotLoginException.class)
public SaResult handlerNotLoginException(NotLoginException nle)
throws Exception {
// 打印堆栈,以供调试
nle.printStackTrace();
// 判断场景值,定制化异常信息
String message = "";
if(nle.getType().equals(NotLoginException.NOT_TOKEN)) {
message = "未提供token";
}
else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) {
message = "token无效";
}
else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) {
message = "token已过期";
}
else if(nle.getType().equals(NotLoginException.BE_REPLACED)) {
message = "token已被顶下线";
}
else if(nle.getType().equals(NotLoginException.KICK_OUT)) {
message = "token已被踢下线";
}
else {
message = "当前会话未登录";
}
// 返回给前端
return SaResult.error(message);
}
```
<br/>
注意:以上代码并非处理逻辑的最佳方式,只为以最简单的代码演示出场景值的获取与应用,大家可以根据自己的项目需求来定制化处理

View File

@@ -0,0 +1,132 @@
# Sa-Token 插件开发指南
---
插件,从字面意思理解就是可拔插的组件,作用是在不改变 Sa-Token 现有架构的情况下,替换或扩展一部分底层代码逻辑。
为 Sa-Token 开发插件非常简单,以下是几种可用的方法:
- 自定义全局策略。
- 更改全局组件实现。
- 实现自定义SaTokenContext。
- 其它自由扩展。
下面依次介绍这几种方式。
### 1、自定义全局策略
Sa-Token 将框架的一些关键逻辑抽象出一个统一的概念 —— 策略,并统一定义在 `SaStrategy` 中,源码参考:[SaStrategy](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaStrategy.java) 。
SaStrategy 的每一个函数都可以单独重写,以 “自定义Token生成策略” 这一需求为例:
``` java
// 重写 Token 生成策略
SaStrategy.me.createToken = (loginId, loginType) -> {
return SaFoxUtil.getRandomString(60); // 随机60位长度字符串
};
```
就像变量的重新赋值一样,我们只需重新指定一个新的策略函数,即可自定义 Token 生成的逻辑。
### 2、更改全局组件实现
Sa-Token 大部分全局组件都定义在 SaManager 之上([SaManager](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/SaManager.java)
我们只需要更改组件的实现类即可。以 临时令牌认证 模块举例
##### 1、先自定义一个实现类
``` java
/**
* 临时认证模块 自定义实现
*/
public class MySaTemp implements SaTempInterface {
@Override
public String createToken(Object value, long timeout) {
System.out.println("------- 自定义一些逻辑 createToken ");
return SaTempInterface.super.createToken(value, timeout);
}
@Override
public Object parseToken(String token) {
System.out.println("------- 自定义一些逻辑 parseToken ");
return SaTempInterface.super.parseToken(token);
}
}
```
##### 2、将自定义实现类绑定在 SaManager 上
``` java
// 注入
SaManager.setSaTemp(new MySaTemp());
```
以上是手动注入方式,如果你是 Spring 的 IOC 环境,则直接在 MySaTemp 实现类加上 @Component 注解即可。
##### 3、开始测试
``` java
// 根据 value 创建一个 token
String token = SaTempUtil.createToken("10014", 120);
System.out.println("生成的Token为" + token);
Object value = SaTempUtil.parseToken(token);
System.out.println("将Token解析后的值为" + value);
```
观察控制台输出,检验自定义实现类是否注入成功:
![dev-plugin-print](https://oss.dev33.cn/sa-token/doc/dev-plugin-print.png)
### 3、实现自定义SaTokenContext
SaTokenContext 是对接不同框架的上下文接口,注入流程和第二步类似,篇幅限制,可参考:[自定义 SaTokenContext 指南](/fun/sa-token-context)
### 4、其它自由扩展
这种方式就无需注入什么全局组件替换内部实现了,你可以在 Sa-Token 的基础之上封装任何代码,进行功能扩展。
### 5、练练手
熟悉了插件开发流程,下面的 [ 待开发插件列表 ] 或许可以给你提供一个练手的方向。
##### SaTokenContext 实现:
| 插件 | 功能 | 状态 |
| :-------- | :-------- | :-------- |
| sa-token-solon-starter | Sa-Token 与 Solon 的整合 | <font color="green" >已完成</font> |
| sa-token-jfinal-starter | Sa-Token 与 JFinal 的整合 | 待开发 |
| sa-token-hasor-starter | Sa-Token 与 Hasor 的整合 | 待开发 |
##### 标签方言:
| 插件 | 功能 | 状态 |
| :-------- | :-------- | :-------- |
| sa-token-dialect-thymeleaf | Sa-Token 与 thymeleaf 的整合 | <font color="green" >已完成</font> |
| sa-token-dialect-freemarker | Sa-Token 与 freemarker 的整合 | 待开发 |
| sa-token-dialect-jsp | Sa-Token 与 jsp 的整合 | 待开发 |
| sa-token-dialect-velocity | Sa-Token 与 velocity 的整合 | 待开发 |
| sa-token-dialect-beetl | Sa-Token 与 beetl 的整合 | 待开发 |
##### 持久层扩展:
| 插件 | 功能 | 状态 |
| :-------- | :-------- | :-------- |
| sa-token-dao-redis | Sa-Token 与 Redis 的整合 | <font color="green" >已完成</font> |
| sa-token-dao-memcached | Sa-Token 与 memcached 的整合 | 待开发 |
##### 其它:
任何你认为有价值的功能代码,都可以扩展为插件。
### 6、发布代码
插件开发完毕之后你可以将其pr到官方仓库
上传到 gitee/github 作为独立项目维护,并发布到 Maven 中央仓库,参考这篇:[https://juejin.cn/post/6844904104834105358](https://juejin.cn/post/6844904104834105358)

View File

@@ -0,0 +1,171 @@
# 自定义 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
<!-- Sa-Token 权限认证ServletAPI 集成包) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-servlet</artifactId>
<version>${sa.top.version}</version>
</dependency>
```
如果你的 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/SaBeanRegister.java)、
[注入Bean](https://gitee.com/dromara/sa-token/blob/master/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaBeanInject.java)
### 5、启动项目
启动项目,尝试打印一下 `SaManager.getSaTokenContext()` 对象,如果输出的是你的自定义实现类,那就证明你已经自定义 `SaTokenContext` 成功了,
快来体验一下 Sa-Token 的各种功能吧。

View File

@@ -0,0 +1,6 @@
# Sa-Token框架掌握度--在线考试
---
此份考卷将测评您对Sa-Token框架的掌握程度满分100链接[https://ks.wjx.top/vj/wFKPziD.aspx](https://ks.wjx.top/vj/wFKPziD.aspx)

View File

@@ -0,0 +1,103 @@
# Sa-Token 中的 Session会话 模型详解
---
### 1、User-Session
提起Session你脑海中最先浮现的可能就是 JSP 中的 HttpSession它的工作原理可以大致总结为
客户端每次与服务器第一次握手时,会被强制分配一个 `[唯一id]` 作为身份标识,注入到 Cookie 之中,
之后每次发起请求时,客户端都要将它提交到后台,服务器根据 `[唯一id]` 找到每个请求专属的Session对象维持会话
这种机制简单粗暴却有N多明显的缺点
1. 同一账号分别在PC、APP登录会被识别为两个不相干的会话
2. 一个设备难以同时登录两个账号
3. 每次一个新的客户端访问服务器时都会产生一个新的Session对象即使这个客户端只访问了一次页面
4. 在不支持Cookie的客户端下这种机制会失效
Sa-Token Session可以理解为 HttpSession 的升级版:
1. Sa-Token只在调用`StpUtil.login(id)`登录会话时才会产生Session不会为每个陌生会话都产生Session节省性能
2. 在登录时产生的Session是分配给账号id的而不是分配给指定客户端的也就是说在PC、APP上登录的同一账号所得到的Session也是同一个所以两端可以非常轻松的同步数据
3. Sa-Token支持Cookie、Header、body三个途径提交Token而不是仅限于Cookie
4. 由于不强依赖Cookie所以只要将Token存储到不同的地方便可以做到一个客户端同时登录多个账号
这种为账号id分配的Session我们给它起一个合适的名字`User-Session`,你可以通过如下方式操作它:
``` java
// 获取当前会话的 User-Session
SaSession session = StpUtil.getSession();
// 从 User-Session 中读取、写入数据
session.get("name");
session.set("name", "张三");
```
使用`User-Session`在不同端同步数据是非常方便的,因为只要 PC 和 APP 登录的账号id一致它们对应的都是同一个Session
举个应用场景在PC端点赞的帖子列表在APP端的点赞记录里也要同步显示出来
### 2、Token-Session
随着业务推进,我们还可能会遇到一些需要数据隔离的场景:
> 指定客户端超过两小时无操作就自动下线,如果两小时内有操作,就再续期两小时,直到新的两小时无操作
那么这种请求访问记录应该存储在哪里呢?放在 User-Session 里吗?
可别忘了PC端和APP端可是共享的同一个 User-Session ,如果把数据放在这里,
那就意味着即使用户在PC端一直无操作只要手机上用户还在不间断的操作那PC端也不会过期
解决这个问题的关键在于虽然两个设备登录的是同一账号但是两个它们得到的token是不一样的
Sa-Token针对会话登录不仅为账号id分配了`User-Session`同时还为每个token分配了不同的`Token-Session`
不同的设备端哪怕登录了同一账号只要它们得到的token不一致它们对应的 `Token-Session` 就不一致,这就为我们不同端的独立数据读写提供了支持:
``` java
// 获取当前会话的 Token-Session
SaSession session = StpUtil.getTokenSession();
// 从 Token-Session 中读取、写入数据
session.get("name");
session.set("name", "张三");
```
### 3、Custom-Session
除了以上两种SessionSa-Token还提供了第三种Session那就是`Custom-Session`你可以将其理解为自定义Session
Custom-Session不依赖特定的 账号id 或者 token而是依赖于你提供的SessionId
``` java
// 获取指定key的 Custom-Session
SaSession session = SaSessionCustomUtil.getSessionById("goods-10001");
// 从 Custom-Session 中读取、写入数据
session.get("name");
session.set("name", "张三");
```
只要两个自定义Session的Id一致它们就是同一个Session
### 4、Session模型结构图
三种Session创建时机
- `User-Session`: 指的是框架为每个 账号id 分配的 Session
- `Token-Session`: 指的是框架为每个 token 分配的 Session
- `Custom-Session`: 指的是以一个 特定的值 作为SessionId来分配的 Session
**假设三个客户端登录同一账号且配置了不共享token那么此时的Session模型是**
![session-model](https://oss.dev33.cn/sa-token/doc/session-model3.png 's-w')
简而言之:
- `User-Session` 以UserId为主只要token指向的UserId一致那么对应的Session对象就一致
- `Token-Session` 以token为主只要token不同那么对应的Session对象就不同
- `Custom-Session` 以特定的key为主不同key对应不同的Session对象同样的key指向同一个Session对象

View File

@@ -0,0 +1,21 @@
# 技术选型:[ 单点登录 ] VS [ OAuth2.0 ]
---
QQ群库经常有小伙伴提问项目需要搭建统一认证中心是用 SSO 方便还是 OAuth2.0 方便呢?针对这个问题,我们列出两者的主要区别以供大家参考:
| 功能点 | SSO单点登录 | OAuth2.0 |
| :-------- | :-------- | :-------- |
| 统一认证 | 支持度高 | 支持度高 |
| 统一注销 | 支持度高 | 支持度低 |
| 多个系统会话一致性 | 强一致 | 弱一致 |
| 第三方应用授权管理 | 不支持 | 支持度高 |
| 自有系统授权管理 | 支持度高 | 支持度低 |
| Client级的权限校验 | 不支持 | 支持度高 |
| 集成简易度 | 比较简单 | 难度中等 |
注:以上仅为在 Sa-Token 中两种技术的差异度比较,不同框架的实现可能略有差异,但整体思想是一致的。

View File

@@ -0,0 +1,15 @@
# Sa-Token 源码用到的所有技术栈
包括但不限于以下:
- Maven多模块项目
- Servlet API、临时Cookie与永久Cookie、Request参数获取
- SpringBoot2.0、Redis、Jackson、Hutool、jwt
- SpringBoot自定义starter、Spring包扫描 + 依赖注入、AOP注解切面、yml配置映射、拦截器
- Java8 接口与default实现、静态方法、枚举、定时器、异常类、泛型、反射、IO流、自定义注解、Lambda表达式、函数式编程
- package-info注释、Serializable序列化接口、synchronized锁
- java加密算法MD5、SHA1、SHA256、AES、RSA
- OAuth2.0、同域单点登录、集群与分布式、路由Ant匹配

View File

@@ -0,0 +1,42 @@
# 三大作用域
---
Sa-Token 数据存储有三大作用域,分别是:
- `SaStorage` - 请求作用域:存储的数据只在一次请求内有效。
- `SaSession` - 会话作用域:存储的数据在一次会话范围内有效。
- `SaApplication` - 全局作用域:存储的数据在全局范围内有效。
### SaStorage - 请求作用域
在 SaStorage 中存储的数据只在一次请求范围内有效,请求结束后数据自动清除。使用 SaStorage 时无需处于登录状态。
``` java
SaStorage storage = SaHolder.getStorage();
storage.get("key"); // 取值
storage.set("key", "value"); // 写值
storage.delete("key"); // 删值
```
### SaSession - 会话作用域
在 SaSession 存储的数据在一次会话范围内有效,会话结束后数据自动清除。必须登录后才能使用 SaSession 对象。
``` java
SaSession session = StpUtil.getSession();
session.get("key"); // 取值
session.set("key", "value"); // 写值
session.delete("key"); // 删值
```
### SaApplication - 全局作用域
在 SaApplication 存储的数据在全局范围内有效,应用关闭后数据自动清除(如果集成了 Redis 那则是 Redis 关闭后数据自动清除)。使用 SaApplication 时无需处于登录状态。
``` java
SaApplication application = SaHolder.getApplication();
application.get("key"); // 取值
application.set("key", "value"); // 写值
application.delete("key"); // 删值
```

View File

@@ -0,0 +1,31 @@
# Sa-Token 开源大事记
---
- **2020-02-04** 在 GitHub 提交第一个版本,正式开源。
- **2020-09-14** GitHub star 数量破 100。
- **2020-10-26** Gitee star 数量破 100。
- **2021-03-01** 被 [HelloGitHub] 第 59 期收录推荐。
- **2021-03-26** GitHub star 数量破 1k。
- **2021-03-30** 受 TLog 作者邀请Sa-Token 加入 dromara 社区。
- **2021-03-30** 被 Gitee 列为推荐项目。
- **2021-03-31** Gitee star 数量破1K。
<!-- - **2021-04-09** GitHub star 数量破2K。 -->
<!-- - **2021-05-09** Gitee star 数量破2K。 -->
<!-- - **2021-05-17** GitHub star 数量破3K。 -->
- **2021-06-17** Sa-Token star 数量 (3529) 超过 Shiro (3506)。
<!-- - **2021-07-15** GitHub star 数量破4K。 -->
- **2021-07-26** GitHub star 数量破5K。
<!-- - **2021-07-29** Gitee star 数量破3K。 -->
<!-- - **2021-09-07** GitHub star 数量破6K。 -->
- **2021-09-24** Sa-Token star 数量 (6280) 超过 SpringSecurity (6247)。
<!-- - **2021-10-19** Gitee star 数量破4K。 -->
- **2021-11-08** 荣获开源中国 “码云 GVP 认证”。
<!-- - **2021-11-17** GitHub star 数量破7K。 -->
- **2021-11-28** Gitee star 数量破5K。
- **2021-12-27** 荣获 OSC-2021 最佳软件 Top 30。
- **2022-05-20** 成为 [可信开源社区共同体] 预备成员。
- **2022-08-01** 加入 [中国开源社区 landscape]。
- **2022-08-18** GitHub 第 10000 个 star 里程碑!

View File

@@ -0,0 +1,22 @@
# SaTokenInfo 参数详解
token信息Model: 用来描述一个token的常用参数
``` js
{
"code": 200,
"msg": "ok",
"data": {
"tokenName": "satoken", // token名称
"tokenValue": "e67b99f1-3d7a-4a8d-bb2f-e888a0805633", // token值
"isLogin": true, // 此token是否已经登录
"loginId": "10001", // 此token对应的LoginId未登录时为null
"loginType": "login", // 账号类型标识
"tokenTimeout": 2591977, // token剩余有效期 (单位: 秒)
"sessionTimeout": 2591977, // User-Session剩余有效时间 (单位: 秒)
"tokenSessionTimeout": -2, // Token-Session剩余有效时间 (单位: 秒) (-2表示系统中不存在这个缓存)
"tokenActivityTimeout": -1, // token剩余无操作有效时间 (单位: 秒)
"loginDevice": "default-device" // 登录设备类型
},
}
```

View File

@@ -0,0 +1,119 @@
# Token有效期详解
<!-- 本篇介绍Token有效期的详细用法 -->
Sa-Token 提供两种Token自动过期策略分别是`timeout``activity-timeout`,配置方法如下:
``` yml
sa-token:
# Token 有效期单位默认30天, -1代表永不过期
timeout: 2592000
# Token 临时有效期 (指定时间内无操作就视为 Token 过期) 单位: 秒,-1代表不设限
activity-timeout: -1
```
两者的区别,可以通过下面的例子体现:
> 1. 假设你到银行要存钱,首先就要办理一张卡 (要访问系统接口先登录)。
> 2. 银行为你颁发一张储蓄卡系统为你颁发一个Token以后每次存取钱都要带上这张卡后续每次访问系统都要提交 Token
> 3. 银行为这张卡设定两个过期时间:
> - 第一个是 `timeout`,代表这张卡的长久有效期,就是指这张卡最长能用多久,假设 `timeout=3年`那么3年后此卡将被银行删除想要继续来银行办理业务必须重新办卡Token 过期后想要访问系统必须重新登录)。
> - 第二个就是 `activity-timeout`,代表这张卡的临时有效期,就是指这张卡必须每隔多久来银行一次,假设 `activity-timeout=1月` 你如果超过1月不来办一次业务银行就将你的卡冻结列为长期不动户Token 长期不访问系统,被冻结,但不会被删除)。
> 4. 两个过期策略可以单独配置也可以同时配置只要有其中一个有效期超出了范围这张卡就会变得不可用两个有效期只要有一个过期了Token就无法成功访问系统了
下面是对两个过期策略的详细解释:
### timeout
1. `timeout`代表Token的长久有效期单位/秒,例如将其配置为 2592000 (30天)代表在30天后Token必定过期无法继续使用
2. `timeout`~~无法续签,想要继续使用必须重新登录~~。v1.29.0+ 版本新增续期方法:`StpUtil.renewTimeout(100)`。
3. `timeout`的值配置为-1后代表永久有效不会过期
### activity-timeout
1. `activity-timeout`代表临时有效期,单位/秒,例如将其配置为 1800 (30分钟)代表用户如果30分钟无操作则此Token会立即过期
2. 如果在30分钟内用户有操作则会再次续签30分钟用户如果一直操作则会一直续签直到连续30分钟无操作Token才会过期
3. `activity-timeout`的值配置为-1后代表永久有效不会过期此时也无需频繁续签
### 关于activity-timeout的续签
如果`activity-timeout`配置了大于零的值Sa-Token 会在登录时开始计时,在每次直接或间接调用`getLoginId()`时进行一次过期检查与续签操作。
此时会有两种情况:
1. 一种是会话无操作时间太长Token已经过期此时框架会抛出`NotLoginException`异常(场景值=-3)
2. 另一种则是会话在`activity-timeout`有效期内通过检查此时Token可以成功续签
### 我可以手动续签吗?
**可以!**
如果框架的自动续签算法无法满足您的业务需求你可以进行手动续签Sa-Token 提供两个API供你操作
1. `StpUtil.checkActivityTimeout()`: 检查当前Token 是否已经[临时过期],如果已经过期则抛出异常
2. `StpUtil.updateLastActivityToNow()`: 续签当前Token(将 [最后操作时间] 更新为当前时间戳)
注意在手动续签时即使Token已经 [临时过期] 也可续签成功如果此场景下需要提示续签失败可采用先检查再续签的形式保证Token有效性
例如以下代码:
``` java
// 先检查是否已过期
StpUtil.checkActivityTimeout();
// 检查通过后继续续签
StpUtil.updateLastActivityToNow();
```
同时,你还可以关闭框架的自动续签(在配置文件中配置 `autoRenew=false` ),此时续签操作完全由开发者控制,框架不再自动进行任何续签操作
如果你需要给其它 Token 续签:
``` java
// 为指定 Token 续签
StpUtil.stpLogic.updateLastActivityToNow(tokenValue);
```
### timeout与activity-timeout可以同时使用吗
**可以同时使用!**
两者的认证逻辑彼此独立,互不干扰,可以同时使用。
### StpUtil 类中哪些公开方法支持临时有效期自动续签?
> 直接或间接获取了当前用户id的方法 (间接调用过 StpLogic.getLoginId() 方法)
| 支持自动续签的方法 |
|---|
| StpUtil.checkLogin() |
| StpUtil.getLoginId() |
| StpUtil.getLoginIdAsInt() |
| StpUtil.getLoginIdAsString() |
| StpUtil.getLoginIdAsLong() |
|---|
| StpUtil.getSession() |
| StpUtil.getSession() |
| StpUtil.getTokenSession() |
|---|
| StpUtil.getRoleList() |
| StpUtil.hasRole() |
| StpUtil.hasRoleAnd() |
| StpUtil.hasRoleOr() |
| StpUtil.checkRole() |
| StpUtil.checkRoleAnd() |
| StpUtil.checkRoleOr() |
|---|
| StpUtil.getPermissionList() |
| StpUtil.hasPermission() |
| StpUtil.hasPermissionAnd() |
| StpUtil.hasPermissionOr() |
| StpUtil.checkPermission() |
| StpUtil.checkPermissionAnd() |
| StpUtil.checkPermissionOr() |
|---|
| StpUtil.openSafe() |
| StpUtil.isSafe() |
| StpUtil.checkSafe() |
| StpUtil.getSafeTime() |
| StpUtil.closeSafe() |
> 以下注解都间接调用过 getLoginId() 方法
| 支持自动续签的注解 |
|---|
| @SaCheckLogin |
| @SaCheckRole |
| @SaCheckPermission |
| @SaCheckSafe |