1
0
mirror of synced 2026-05-22 22:53:16 +00:00

Merge branch 'dev' into xuxiaowei/Maven-Central-Portal

This commit is contained in:
click33
2026-03-08 03:14:46 +08:00
committed by GitHub
266 changed files with 8476 additions and 2386 deletions
+80
View File
@@ -0,0 +1,80 @@
# Cursor Agent Skills
本目录存放用于辅助 Sa-Token 项目开发的 Cursor Agent Skills。这些 skills 封装了项目特定的知识和操作规范,可在对话中直接调用。
## Skill 列表
| Skill 名称 | 功能描述 | 使用场景 | 入口文件 |
|-----------|---------|---------|---------|
| `commit-message` | 根据 git 变更生成符合 Sa-Token 项目风格的 commit message | 生成提交信息、写 commit message | [SKILL.md](commit-message/SKILL.md) |
| `organize-update-log` | 根据 git 提交记录生成符合项目规范的更新日志内容 | 整理更新日志、分析版本变更 | [SKILL.md](organize-update-log/SKILL.md) |
| `remove-redundancy-import` | 检查并移除 Java 类中未被引用的冗余 import | 清理冗余导包、优化 import | [SKILL.md](remove-redundancy-import/SKILL.md) |
### 详细说明
#### commit-message
根据当前 git 变更(staged 或 unstaged),生成符合 [Conventional Commits](https://www.conventionalcommits.org/) 格式、以中文为主的 commit message。支持识别新增文件、修复 bug、重构等多种变更类型。
#### organize-update-log
根据 git 提交记录,生成符合 `sa-token-doc/more/update-log.md` 格式的更新日志内容。自动分类到插件、starter、重构、Solon、示例、文档等版块。
#### remove-redundancy-import
扫描项目中所有 Java 类,检测未被引用的冗余 import,生成清理计划供审阅,确认后执行移除。支持通过内置 Python 脚本快速扫描。
## 快速使用
在 Cursor 对话中,直接描述你的需求即可自动触发相应 skill:
```
用户:帮我生成 commit message
→ 自动使用 commit-message skill 分析 git 变更并生成提交信息
用户:整理一下更新日志
→ 自动使用 organize-update-log skill 生成更新日志
用户:清理一下冗余 import
→ 自动使用 remove-redundancy-import skill 扫描并清理未使用的 import
```
## 新增 Skill 维护指南
当新增 skill 时,请同步更新本 README 文件,保持 skill 列表的完整性。
### Skill 目录结构规范
每个 skill 应创建独立的子目录,结构如下:
```
.cursor/skills/
├── README.md # 本文件
└── skill-name/ # skill 目录(小写,短横线分隔)
├── SKILL.md # skill 主文件(必须包含 YAML 元数据和使用说明)
├── examples.md # 使用示例(可选)
├── reference.md # 参考文档(可选)
└── scan_redundant_imports.py # 辅助脚本(如需要)
```
### SKILL.md 文件格式
每个 `SKILL.md` 必须包含 YAML Front Matter
```yaml
---
name: skill-name
description: 简要描述 skill 的功能和使用场景
---
```
### 更新 README 清单
新增 skill 后,请在本文件中:
1.**Skill 列表** 表格中添加新行
2.**详细说明** 小节添加对应的描述段落
3. (可选)在 **快速使用** 中添加使用示例
## 注意事项
- 所有 skill 遵循 Sa-Token 项目特定的规范和风格
- 部分 skill(如 `remove-redundancy-import`)在执行前需要用户确认
- 可参考每个 skill 目录下的 `examples.md``reference.md` 获取更多使用帮助
+112
View File
@@ -0,0 +1,112 @@
---
name: commit-message
description: 根据 git 变更生成符合 Sa-Token 项目风格的 commit message。遵循 Conventional Commits 格式,以中文为主。当用户要求生成提交信息、写 commit message、或根据变更生成提交说明时使用。
---
# 生成 Commit Message
根据当前 git 变更(staged 或 unstaged),生成符合 Sa-Token 项目规范的 commit message。
## 使用时机
- 用户要求生成 commit message
- 用户要求根据变更写提交说明
- 用户说「帮我写个 commit」「生成提交信息」等
## 工作流程
### 第一步:获取变更内容
```bash
git status
git diff --staged
```
若无 staged 变更,则用 `git diff` 查看工作区变更。
**注意新文件**`git status` 中标记为 `Untracked files` 的新增文件也需要纳入分析,这些文件通常代表新增功能、新模块或新资源。
### 第二步:分析变更类型
根据变更内容(包括新增文件、修改文件、删除文件)选择 type 前缀:
| type | 适用场景 |
|------|----------|
| feat | 新增功能、新模块、新插件 |
| fix | 修复 bug、修正错误 |
| refactor | 重构、优化结构、重命名、移除冗余 |
| perf | 性能优化(与 refactor 区分:侧重性能) |
| docs | 文档更新、README、错别字、同步链接 |
| style | 代码格式调整(缩进、空格等,不影响逻辑) |
| chore | 构建配置、.gitignore、注释修复、依赖更新 |
| test | 单元测试、测试用例 |
| demo | 示例项目、demo 相关 |
| memo | 备忘录、内部记录 |
| revert | 回滚某次提交 |
| AI | AI 创建的 skill、规则等 |
**新增文件的处理**
- 新创建的未跟踪文件(`git status` 中显示为 `Untracked files`)必须纳入 commit message 的描述范围
- 新增文件通常对应 `feat` 类型(新功能、新模块、新插件、新资源)
- 示例场景:新增插件模块、新增配置文件、新增资源文件、新增文档、新增 skill 等
### 第三步:撰写描述
**基础格式**`type: 简短描述``type(scope): 简短描述`
**scope 可选**:涉及特定模块时使用,如 `feat(sign)``fix(oauth2)``refactor(dependencies)`
**规范**
- **50 字规则**subject 不超过 50 字符,保证在 git log 中完整显示
- **命令式语气**:用「修复」「新增」「优化」,不用「修复了」「新增了」
- **说明「做了什么」**:清晰表达变更内容,必要时说明「为什么」
- **以中文为主**:技术术语可保留英文(如 `StrFormatter``sa-token-jackson3`
- **动词开头**:新增、修复、优化、重构、移除、同步、订正 等
**可选 Body/Footer**(重要变更时):
- Body:详细说明变更背景、动机,每行不超过 72 字符
- Footer:关联 Issue,如 `Fixes #123``merge: [pr N](url)`
### 第四步:输出
直接输出可复制的 commit message。若有多条合理方案,可给出 1~2 个备选。
## 格式示例
**简单提交**(常用):
```
feat: 添加 sa-token-jackson3 插件
fix(sign): 修复签名校验在空参数时的空指针
```
**带 scope**
```
refactor(dependencies): 重构模块依赖层级
perf(oauth2): 优化 Client 信息读取算法
```
**带 body**(复杂变更):
```
feat: 新增重复登录处理策略
当同一账号不允许多客户端同时登录时,支持选择踢人下线或拦截本次登录。
```
## 参考资源
- 示例:详见 [examples.md](examples.md)
- 规范详解:详见 [reference.md](reference.md)
## 快速对照
| 变更内容 | 示例输出 |
|----------|----------|
| 新增插件 | `feat: 添加 sa-token-jackson3 插件` |
| 新增文件(未跟踪) | `feat: 新增 sign 模块配置类 SignConfig` |
| 修复 bug | `fix: 修复 StpUtil.getLoginIdByTokenNotThinkFreeze 方法缺少 static 的问题` |
| 性能优化 | `perf: 优化 StrFormatter 常量封装` |
| 重构模块 | `refactor: 重构模块依赖层级` |
| 移除冗余 | `refactor: 移除冗余导包` |
| 文档更新 | `docs: 同步最新文章列表、赞助者名单` |
| 注释修复 | `chore: 修复注释错别字` |
| 新增 skill | `AI: 新增 skills/commit-message/SKILL.md,用于根据 git 变更生成符合项目风格的 commit message` |
+97
View File
@@ -0,0 +1,97 @@
# Commit Message 示例
基于 Sa-Token 项目近期提交整理,遵循 Conventional Commits + 50/72 规则。
## feat - 新增功能
```
feat: 添加 sa-token-jackson3 插件
feat: 新增 sa-token-spring-boot4-starter 集成包
feat: 新增 sa-token-reactor-spring-boot4-starter 集成包
feat(sign): 新增签名模板自定义能力
```
## fix - 修复问题
```
fix: 修复 StpUtil.getLoginIdByTokenNotThinkFreeze 方法缺少 static 的问题
fix: 修正一处代码注释错误:SaTokenDao 注释中 数据有效期 应为 小于等于-2 (掉了等于)
fix: Bearer 全局统一大小写
fix: SaOAuth2Strategy中removeGrantTypeHandler的引用有误
```
## refactor - 重构/优化
```
refactor: 移除冗余导包
refactor: 重命名 SaRepeatLoginsMode -> SaReplacedLoginExitMode
refactor: 优化项目构建配置
refactor: 优化 OAuth2 模块在请求中读取 Client 信息算法
refactor: 优化模块依赖关系
refactor: 重构模块依赖层级
refactor: sa-token-dependencies 重构为 sa-token-basic-dependencies
refactor: SaTokenDubboContextFilter 改为使用 SaTokenContextDubboUtil 清理上下文
```
## perf - 性能优化
```
perf: 优化 StrFormatter 常量规范与封装
perf: 优化 pattern 缓存,消除魔法值
```
## docs - 文档
```
docs: 订正文档错别字
docs: 同步最新文章列表、赞助者名单
docs: 为 sa-token-sso 模块定义 STS 协议
docs: 优化 readme
docs: 同步最新博客链接
```
## chore - 杂项
```
chore: 修复注释错别字
chore: 增加忽略 .vscode 目录
```
## demo - 示例
```
demo: 新增 sa-token-demo-webflux-springboot4 示例
demo: 新增 SpringBoot4 整合 demo 示例
```
## test - 测试
```
test: 新增 sa-token-jackson3 单元测试
```
## memo - 备忘录
```
memo: 备忘录重构为专门的文件夹
```
## style - 代码格式
```
style: 统一代码缩进与空格
style: 修复 ESLint 警告
```
## revert - 回滚
```
revert: feat(sign): 新增签名模板自定义能力
```
## AI - AI 相关
```
AI: 新增 skills/remove-redundancy-import/SKILL.md,用于检查项目中的java类无效冗余导包信息并移除
AI: 新增 SKILL: organize-update-log ,用于格式化整理版本更新日志信息
```
@@ -0,0 +1,38 @@
# Commit Message 规范参考
基于 Conventional Commits 与业界最佳实践整理。
## 核心规则
| 规则 | 说明 |
|------|------|
| 50 字规则 | subject 不超过 50 字符,便于 git log 完整显示 |
| 72 字规则 | body 每行不超过 72 字符,便于阅读与 diff |
| 命令式语气 | 用「修复」「新增」而非「修复了」「新增了」 |
| 说明动机 | 重要变更在 body 中说明「为什么」而不仅是「做了什么」 |
## 格式结构
```
<type>[(<scope>)]: <subject>
[optional body]
[optional footer(s)]
```
- **subject**:必填,简明扼要
- **body**:可选,详细说明
- **footer**:可选,如 `Fixes #123``BREAKING CHANGE: xxx`
## 类型速查
- **feat**:新功能
- **fix**:修复 bug
- **refactor**:重构(结构、逻辑)
- **perf**:性能优化
- **docs**:文档
- **style**:格式(不影响逻辑)
- **chore**:构建、配置、杂项
- **test**:测试
- **revert**:回滚
+123
View File
@@ -0,0 +1,123 @@
---
name: organize-update-log
description: 根据 git 提交记录生成符合 Sa-Token 项目规范的更新日志内容。适用于分析指定版本之后的提交、提取变更并格式化为 update-log.md 风格。当用户需要生成更新日志、整理版本变更、或分析 release 之后的提交时使用。
---
# 整理更新日志
根据 git 提交记录,生成符合 `sa-token-doc/more/update-log.md` 格式的更新日志内容。
## 使用时机
- 用户要求生成/整理更新日志
- 用户要求分析「某版本之后」的提交变更
- 用户要求将 git 提交格式化为更新日志风格
- 准备发布新版本前整理 changelog
## 工作流程
### 第一步:确定基准版本
1. 询问用户基准版本(如 `v1.44.0`),或从上下文推断
2. 查找该版本的发布提交:
-`SaTokenConsts.java``pom.xml` 中搜索版本号
- 或执行:`git log --oneline --all -- sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java` 查找含 `release vX.X.X` 的提交
3. 记录基准提交 hash(如 `7bde74bc`
### 第二步:获取提交列表
执行:
```bash
git log <基准提交>..HEAD --oneline --format="%h %s"
```
可选,获取更详细的变更文件:
```bash
git log <基准提交>..HEAD --stat --format="=== %h %s ==="
```
### 第三步:分类与映射
将每条提交按以下规则归类到对应板块:
| 提交关键词/内容 | 归属板块 |
|----------------|----------|
| feat.*jackson、plugin、插件 | 插件 |
| feat.*starter、spring-boot、reactor | starter |
| refactor.*依赖、dependencies、模块 | 重构 |
| refactor.*solon、gateway | Solon(单独列出) |
| fix.*dubbo、dubbo3 | 插件 |
| demo.*、示例 | 示例 或 starter |
| docs.*、文档 | 文档 |
| chore、.gitignore、.vscode | 其它 |
| merge.*loveqq、maven-pull | 其它(含 PR 链接) |
### 第四步:动作词映射
根据提交类型选择正确的动作词:
| 提交类型 | 动作词 | 示例 |
|----------|--------|------|
| feat、新增 | 新增 | 新增 `sa-token-jackson3` 插件 |
| fix、修复 | 修复 | 修复 Maven 父子项目依赖下载问题 |
| refactor、重构 | 重构 / 移除 | 重构模块依赖层级;移除 xxx 模块 |
| 优化、perf | 优化 | 优化 Gateway 接口处理 |
| 拆分 | 拆分 | (少见) |
| 文档更新 | 同步/新增/优化/修复 | 按具体内容选择 |
### 第五步:按格式输出
使用下方模板生成最终内容。详见 [format-reference.md](format-reference.md)。
## 输出模板
```markdown
### vX.X.X @YYYY-M-D(或:开发中 / 未发布)
- 插件:
- 新增:xxx。 **[重要]**(如适用)
- 修复:xxx。merge: [pr N](https://gitee.com/dromara/sa-token/pulls/N)(如适用)
- starter
- 新增:xxx。
- 重构:
- 重构:xxx。
- 移除:xxx。
- Solon:(如有)
- 优化:xxx。merge: [pr N](url)
- 示例:(如有)
- 新增:xxx。
- 文档:
- 同步:xxx。
- 新增:xxx。
- 优化:xxx。
- 修复:xxx。
- 其它:
- 新增/修复/优化:xxx。
```
## 格式规则
1. **层级**:一级用 `-`,二级用 ` -`Tab + 短横线)
2. **动作词**:每条以「新增」「修复」「重构」「优化」「移除」「同步」等开头
3. **重要标记**:对用户影响大的变更加 `**[重要]**`
4. **PR/Issue 链接**:提交信息含 `!358``pr 340` 等时,补充 `merge: [pr N](https://gitee.com/dromara/sa-token/pulls/N)`
5. **代码/模块名**:用反引号包裹,如 `` `sa-token-jackson3` ``
6. **合并同类**:多条相似文档类提交可合并为一条(如「同步公众号、博客、赞助者名单」)
## 常见板块
- **core**:核心逻辑、API、配置变更
- **SSO**:单点登录相关
- **OAuth2**OAuth2 相关
- **插件**:插件包(jackson、dubbo、redis 等)
- **starter**Spring Boot / Reactor 等 starter
- **示例**demo 项目
- **文档**:文档、README、错别字
- **其它**:其它杂项
## 注意事项
- 合并提交(Merge branch)可忽略,只保留实际变更的提交
- 纯文档/错别字可适度合并,避免条目过多
- 版本号未发布时,可写 `v1.45.0(开发中)``未发布`
- 输出为可直接粘贴到 `update-log.md` 的 Markdown 片段
@@ -0,0 +1,108 @@
# 更新日志格式参考
本文档提供 `sa-token-doc/more/update-log.md` 的格式细节与示例,供生成更新日志时参考。
## 版本标题格式
```markdown
### v1.44.0 @2025-6-7
```
- 版本号:`v` + 主.次.修订
- 日期:`@YYYY-M-D``@YYYY-M-DD`
- 未发布时:`v1.45.0(开发中)``v1.45.0(未发布)`
## 板块结构
```
- 板块名:
- 动作词:具体描述。 **[重要]**(可选) merge: [pr N](url)(可选)
```
- 一级:`- 板块名:`
- 二级:` - 动作词:描述。`Tab 缩进)
## 动作词
| 动作词 | 含义 | 使用场景 |
|--------|------|----------|
| 新增 | 新功能、新模块 | feat、新增插件、新 starter |
| 修复 | Bug 修复 | fix |
| 重构 | 结构调整 | refactor |
| 优化 | 改进、优化 | 优化、perf |
| 移除 | 删除模块/功能 | 删除、移除 |
| 拆分 | 模块拆分 | 拆分 |
| 同步 | 内容同步 | 文档、赞助者、博客列表 |
| 补全 | 补充内容 | 补全文档、测试 |
| 升级 | 升级、变更 | 升级 API、模块 |
## 链接格式
**PR**
```markdown
merge: [pr 340](https://gitee.com/dromara/sa-token/pulls/340)
```
**Issue**
```markdown
fix: [#IA6ZK0](https://gitee.com/dromara/sa-token/issues/IA6ZK0)
```
## 重要标记
对用户影响较大的变更加 `**[重要]**`,通常放在句末、链接前:
```markdown
- 新增:新增 `sa-token-spring-boot4-starter` 集成包。 **[重要]**
- 新增:loveqq-framework 启动器集成。merge: [pr 340](url)
```
## 完整示例
```markdown
### v1.45.0(开发中)
- 插件:
- 新增:新增 `sa-token-jackson3` 插件,用于 Jackson 3 的 JSON 解析。 **[重要]**
- 新增:新增 `sa-token-jackson3` 单元测试。
- starter
- 新增:新增 `sa-token-spring-boot4-starter` 集成包,支持 Spring Boot 4。 **[重要]**
- 新增:新增 `sa-token-reactor-spring-boot4-starter` 集成包,支持 WebFlux + Spring Boot 4。 **[重要]**
- 新增:新增 `sa-token-demo-webflux-springboot4` 示例。
- 新增:新增 Spring Boot 4 整合 demo 示例。
- 重构:
- 重构:`sa-token-dependencies` 重构为 `sa-token-basic-dependencies`**[重要]**
- 重构:重构 Spring Boot 相关集成包,优化依赖关系。
- 移除:移除 `sa-token-spring-boot-autoconfig` 模块,相关逻辑迁移至各 starter 内。 **[重要]**
- 重构:重构模块依赖层级,新增 `sa-token-special-dependencies`
- Solon
- 优化:`sa-token-solon-plugin` 优化 Gateway 接口的处理,避免使用路由接口。merge: [pr 348](https://gitee.com/dromara/sa-token/pulls/348)
- 其它:
- 新增:loveqq-framework 启动器集成。merge: [pr 340](https://gitee.com/dromara/sa-token/pulls/340)
- 修复:修复 Maven 父子项目无法下载依赖的问题。merge: [pr 358](https://gitee.com/dromara/sa-token/pulls/358)
- 文档:
- 同步:同步公众号文章列表、博客列表、赞助者名单。
- 新增:新增《Gitee 2025年度开源项目 Web应用开发 Top 2》证书展示。
- 优化:优化框架 Slogan、README、案例库展示。
- 修复:错别字修复;文档图片地址更换为本地文件。
- 其它:
- 新增:增加忽略 .vscode 目录。
- 优化:注释优化。
```
## 提交信息到条目的映射示例
| 提交信息 | 生成条目 |
|----------|----------|
| `feat: 添加 sa-token-jackson3 插件` | 新增:新增 `sa-token-jackson3` 插件,用于 Jackson 3 的 JSON 解析。 **[重要]** |
| `refactor: 移除 sa-token-spring-boot-autoconfig 模块` | 移除:移除 `sa-token-spring-boot-autoconfig` 模块,相关逻辑迁移至各 starter 内。 **[重要]** |
| `docs: 同步最新赞助者名单` | 同步:同步赞助者名单。 |
| `!358 update maven-pull.md` | 修复:修复 Maven 父子项目无法下载依赖的问题。merge: [pr 358](url) |
## 文档类合并建议
以下类型可合并为一条:
- 同步公众号、博客、赞助者名单 → 「同步:同步公众号文章列表、博客列表、赞助者名单。」
- 多条例错别字修复 → 「修复:错别字修复。」
- 多篇文档图片本地化 → 「修复:文档图片地址更换为本地文件(基础篇、深入篇、SSO篇等)。」
@@ -0,0 +1,76 @@
---
name: remove-redundancy-import
description: 检查 Java 类中未被引用的冗余 import 并移除。先输出待审阅计划,用户确认后执行。适用于用户要求清理冗余导包、优化 import、或执行 remove-redundancy-import 时使用。
---
# 移除冗余 import
检查项目中所有 Java 类的未使用 import,生成清理计划供用户审阅,确认后执行移除。
## 使用时机
- 用户要求清理冗余导包
- 用户要求优化 Java import
- 用户明确执行 `remove-redundancy-import` 或提及本 Skill 名称
## 强制流程
**必须先输出计划,用户确认后再执行移除。** 不得在未审阅的情况下直接修改文件。
## 工作流程
### 第一步:扫描与解析
**优先使用内置脚本**:在 Skill 目录下的 [scan_redundant_imports.py](scan_redundant_imports.py) 已实现完整扫描逻辑,可直接复用。
```bash
# 在项目根目录执行
python .cursor/skills/remove-redundancy-import/scan_redundant_imports.py
# 或指定扫描根路径
python .cursor/skills/remove-redundancy-import/scan_redundant_imports.py .
```
脚本输出格式:`文件路径 | 冗余import1; import2 | 数量`,末尾两行为 `TOTAL_FILES:N``TOTAL_IMPORTS:M`
**若无 Python 环境**,可手动执行:
1. 使用 `Glob` 查找项目内所有 `**/*.java` 文件
2. 对每个文件:提取 `package``import`,按 [reference.md](reference.md) 判定是否被使用
3. 汇总存在冗余 import 的文件及列表
### 第二步:输出计划
使用下方模板生成计划报告,等待用户确认:
```markdown
## 冗余 import 清理计划
| 文件 | 待移除 import | 数量 |
|------|---------------|------|
| path/to/Foo.java | `java.util.Date`, `java.sql.Timestamp` | 2 |
| ... | ... | ... |
**共 N 个文件,M 处冗余 import。确认后执行移除。**
```
### 第三步:执行移除
用户确认后,对计划中的每个文件使用 `StrReplace` 移除对应 import 行:
- 逐行移除,每行格式为 `import ...;``import static ...;`
- 若某 import 后紧跟空行,可一并移除空行以保持格式整洁
- 移除后确认文件无语法错误
## 检测规则概要
- **普通 import**:取最后一段类名(如 `java.util.List``List`),在类体中搜索 `\bList\b`
- **static import**:取方法/字段名,在类体中搜索
- **同包冗余**:import 的包与当前文件 `package` 相同则视为冗余
- **通配符**`import pkg.*` 跳过,不自动处理
详见 [reference.md](reference.md)。
## 注意事项
- 通配符 import 无法可靠判断,一律跳过
- 注解中的类型引用采用保守策略,宁可漏检不误删
- 移除后建议用户运行 `mvn compile` 验证
@@ -0,0 +1,63 @@
# 冗余 import 检测规则
## 解析步骤
### 1. 提取 package
匹配 `package\s+([\w.]+)\s*;`,得到当前文件所在包。
### 2. 提取 import
匹配以下模式(每行一条):
- `import\s+([\w.]+)\s*;` — 普通 import
- `import\s+static\s+([\w.]+)\s*;` — static 导入类
- `import\s+static\s+([\w.]+)\.(\w+)\s*;` — static 导入成员(方法/字段)
- `import\s+[\w.]+\s*\.\s*\*\s*;` — 通配符,**跳过不处理**
### 3. 确定简单名(Simple Name
| import 类型 | 示例 | 简单名 |
|-------------|------|--------|
| 普通类 | `import java.util.List;` | `List` |
| 内部类 | `import pkg.Outer.Inner;` | `Inner` |
| static 类 | `import static pkg.Utils;` | `Utils` |
| static 成员 | `import static pkg.Utils.foo;` | `foo` |
### 4. 同包冗余
`import x.y.Z` 的包 `x.y` 与当前文件 `package x.y` 相同,则该 import 冗余(同包无需导入)。
### 5. 使用检测
在**类体**`package` 和所有 `import` 之后)中搜索:
- 使用正则 `\bSimpleName\b` 匹配整词,避免误匹配子串
- 排除:注释、字符串字面量中的出现
- 若未找到匹配,则该 import 视为未使用
## 边界情况
| 情况 | 处理方式 |
|------|----------|
| `import pkg.*;` | 跳过,不自动移除 |
| 注解中的类型 `@Foo` | 若 `Foo` 为 import 的简单名,视为已使用 |
| 泛型 `List<String>` | `List` 会匹配,视为已使用 |
| 同名类(如 `java.util.Date``java.sql.Date`) | 两 import 都保留;若仅一个被使用,只移除未使用的 |
| Javadoc `@param` 中的类型 | 保守:若不确定则保留 |
## 正则参考
```
// package
package\s+([\w.]+)\s*;
// 普通 import(非通配符)
import\s+(?!static)([\w.]+)\s*;
// static import 成员
import\s+static\s+[\w.]+\.(\w+)\s*;
// static import 类
import\s+static\s+([\w.]+)\s*;
```
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
冗余 import 扫描脚本
按 reference.md 规则扫描项目内 Java 文件,输出待移除的冗余 import 列表。
用法:在项目根目录执行 python scan_redundant_imports.py
"""
import os
import re
import sys
def get_simple_name(imp: str) -> str | None:
"""从 import 行提取简单名(用于类体搜索)"""
m = re.match(r'import\s+static\s+[\w.]+\.(\w+)\s*;', imp)
if m:
return m.group(1)
m = re.match(r'import\s+(?:static\s+)?([\w.]+)\s*;', imp)
if m:
return m.group(1).split('.')[-1]
return None
def get_import_full(imp: str) -> str:
"""提取 import 的完整限定名"""
m = re.match(r'import\s+(?:static\s+)?([\w.]+)\s*;', imp)
return m.group(1).strip() if m else ''
def get_import_package(imp: str) -> str:
"""提取 import 所在包(用于同包冗余判断)"""
m = re.match(r'import\s+(?:static\s+)?([\w.]+)\s*;', imp)
if m:
parts = m.group(1).split('.')
return '.'.join(parts[:-1]) if len(parts) > 1 else ''
return ''
def find_class_body_start(content: str) -> int:
"""找到类体起始位置(最后一个 import 之后)"""
last = 0
for m in re.finditer(r'import\s+(?:static\s+)?[\w.]+\s*;', content):
last = m.end()
return last
def main() -> None:
root = sys.argv[1] if len(sys.argv) > 1 else '.'
skip_dirs = {'target', 'build', '.git', 'node_modules'}
results = []
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in skip_dirs]
for f in filenames:
if not f.endswith('.java'):
continue
path = os.path.join(dirpath, f).replace('\\', '/')
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as fp:
content = fp.read()
except OSError:
continue
pkg_match = re.search(r'package\s+([\w.]+)\s*;', content)
file_pkg = pkg_match.group(1) if pkg_match else ''
imports = re.findall(r'import\s+(?:static\s+)?[\w.]+\s*;', content)
imports = [i for i in imports if '*;' not in i and '.*' not in i]
body_start = find_class_body_start(content)
body = content[body_start:]
redundant = []
for imp in imports:
simple = get_simple_name(imp)
if not simple:
continue
imp_full = get_import_full(imp)
imp_pkg = get_import_package(imp)
if imp_pkg and imp_pkg == file_pkg:
redundant.append(imp_full)
continue
if not re.search(r'\b' + re.escape(simple) + r'\b', body):
redundant.append(imp_full)
if redundant:
results.append((path, redundant))
for path, red in results:
print(f"{path} | {'; '.join(red)} | {len(red)}")
print("TOTAL_FILES:" + str(len(results)))
print("TOTAL_IMPORTS:" + str(sum(len(r[1]) for r in results)))
if __name__ == '__main__':
main()
+2
View File
@@ -12,7 +12,9 @@ unpackage/
/.factorypath
.idea/
.vscode/
sa-token-three-plugin/
sa-token-doc/big-file/
.flattened-pom.xml
+1 -1
View File
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2011-2019 hubin.
Copyright 2011-Present hubin.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -0,0 +1,41 @@
## 未完成目标
### 1、尝试将所有 `<properties>` 依赖版本号定义在同一个 pom.xml 里。 **[❌失败]**
**尝试1:将所有 `<properties>` 定义在 `sa-token-dependencies` 里:**
结果: 无法在 `sa-token-spring-boot2/3/4-dependencies` 中引用这些 `<properties>`,因为 `<dependencyManagement> <dependencies> <scope>import</scope>` 只会导入目标的 `<dependencyManagement>` 版本号定义,不会导入目标的 `<properties>` 属性。
`<properties>` 只会在 父子结构中向下传递,不会在 `<dependencyManagement> <dependencies> <scope>import</scope>` 中传递。
**尝试2:将所有 `<properties>` 定义在 `sa-token-parent` 里:**
结果:在 `sa-token-dependencies` 里无法引用这些 `<properties>`,因为 `sa-token-parent` 不是 `sa-token-dependencies` 的父模块。
`sa-token-parent` 定义为 `sa-token-dependencies` 的父模块行吗?
不行,因为在 `sa-token-parent` 通过 `<dependencyManagement> <dependencies> <scope>import</scope>` 导入了 `sa-token-dependencies`,如果再把 `sa-token-parent` 定义为 `sa-token-dependencies` 的父模块,会造成循环依赖。
执行 `mvn package` 打包时,maven 会直接报错:
```
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[ERROR] The dependencies of type=pom and with scope=import form a cycle: cn.dev33:sa-token-parent:1.44.0 -> cn.dev33:sa-token-basic-dependencies:1.44.0 -> cn.dev33:sa-token-basic-dependencies:1.44.0 @ cn.dev33:sa-token-basic-dependencies:1.44.0
@
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]
[ERROR] The project cn.dev33:sa-token-parent:1.44.0 (E:\work\project-yun\sa-token\pom.xml) has 1 error
[ERROR] The dependencies of type=pom and with scope=import form a cycle: cn.dev33:sa-token-parent:1.44.0 -> cn.dev33:sa-token-basic-dependencies:1.44.0 -> cn.dev33:sa-token-basic-dependencies:1.44.0 @ cn.dev33:sa-token-basic-dependencies:1.44.0
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException
```
+133
View File
@@ -0,0 +1,133 @@
2026-3-1 调试记录
启动 SaOAuth2ServerApplication,报错空指针:SaOAuth2ServerController 文件的 SaOAuth2Strategy.instance.notLoginView 空指针,SaOAuth2Strategy.instance 为 null
SaOAuth2Strategy.instance 的定义为:
public static final SaOAuth2Strategy instance = new SaOAuth2Strategy();
看代码是无论如何也不可能空指针的,诡异。
在 main 方法第一句加上测试
@SpringBootApplication
public class SaOAuth2ServerApplication {
public static void main(String[] args) {
System.out.println(SaOAuth2Strategy.instance);
SpringApplication.run(SaOAuth2ServerApplication.class, args);
System.out.println("\nSa-Token-OAuth2 Server端启动成功,配置如下:");
System.out.println(SaOAuth2Manager.getServerConfig());
}
}
打印居然为 null。
询问 AI,解释的乱七八糟,没有参考价值。
然后在根目录执行 mvn clean,居然无法成功。sa-token-test 模块无法 clean 。
报错 test 依赖不存在
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
最后必须在 dependencyManagement 加上这个才行
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.18</version>
</dependency>
可是就算不加,我也已经在 sa-token-spring-boot2-dependencies 中定义这个依赖了呀,为什么 在 sa-token-test 中无法 import spring-boot-starter-test
加了后,mvn clean 执行成功了
但是 mvn package 又开始无法打包。
可以昨天我明明能打包成功的啊?今天好像就变动了一下 sa-token-test 中的依赖配置。这有什么影响吗?
而且打包报错信息居然是:sa-token-jboot-plugin 插件中 javax.servlet.http.HttpServletRequest 无法转换为 HttpServletRequest
什么东西啊。
抓头挠腮解决不了。
这个插件已经十几个版本没有变动过代码了,代码不变,打包环境不变,命令不变,今天就突然报这种莫名其妙的错误,无奈,只能先去除这个插件,不让它参与打包。
继续打包,又开始报错:
sa-token-jfinal-plugin 中 cn.dev33.satoken.context.SaTokenContext 无法转换为 SaTokenContext。
这一瞬间我怀疑自己正处于梦中。
纠结了半分钟,继续去除此插件,继续打包。
打包成功了。
启动 SaOAuth2ServerApplication,启动成功,SaOAuth2ServerController 文件的 SaOAuth2Strategy.instance.notLoginView 空指针问题,消失了。
请问中间的这几个报错和这个空指针有任何关联吗?我请问呢?
注:以上所有叙述均为最后打包成功后进行回忆,可能细节上略有偏差。
两小时后:
本来可以运行成功的代码,只要一改子模块的代码就无法再运行成功,报错:java: 无法访问SaRequest。
试了好多解决方案,不行。
---
吃了两份炉盖香酥鸡饼,原来人在压力大的时候真的需要补充能量。
---
继续报错:
Maven 资源编译器: 模块 'sa-token-oauth2' 所需的 Maven 项目配置不可用。仅当从 IDE 启动外部构建时,才支持 Maven 项目编译。
sa-token-jwt、sso、sign 等模块均出现此问题
最后:
把项目删掉,重新下载一份,导入
项目可以运行成功了,但是每次修改子模块,在 demo 示例里无法实时起作用。需要 mvn clean install 才能看到效果。
最后:
取消勾选 maven 配置项:Delegate IDE build/run actions to Maven
一切问题解决,包括最上面的诡异调试现象也消失了。
idea,你给老子爬
+86 -47
View File
@@ -2,28 +2,28 @@
<img alt="logo" src="https://sa-token.cc/logo.png" width="150" height="150">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.44.0</h1>
<h4 align="center">开源、免费、轻量级 Java 权限认证框架,让鉴权变得简单、优雅!</h4>
<h4 align="center">开源、免费、一站式 java 权限认证框架,让鉴权变得简单、优雅! </h4>
<p align="center">
<a href="https://gitee.com/dromara/sa-token/stargazers"><img src="https://gitee.com/dromara/sa-token/badge/star.svg?theme=gvp"></a>
<a href="https://gitee.com/dromara/sa-token/members"><img src="https://gitee.com/dromara/sa-token/badge/fork.svg?theme=gvp"></a>
<a href="https://gitcode.com/dromara/sa-token/stargazers"><img src="https://gitcode.com/dromara/Sa-Token/star/badge.svg"></a>
<a href="https://atomgit.com/dromara/sa-token/stargazers"><img src="https://atomgit.com/dromara/Sa-Token/star/badge.svg"></a>
<a href="https://github.com/dromara/sa-token/stargazers"><img src="https://img.shields.io/github/stars/dromara/sa-token?style=flat-square&logo=GitHub"></a>
<a href="https://github.com/dromara/sa-token/network/members"><img src="https://img.shields.io/github/forks/dromara/sa-token?style=flat-square&logo=GitHub"></a>
<a href="https://github.com/dromara/sa-token/watchers"><img src="https://img.shields.io/github/watchers/dromara/sa-token?style=flat-square&logo=GitHub"></a>
<!-- <a href="https://github.com/dromara/sa-token/watchers"><img src="https://img.shields.io/github/watchers/dromara/sa-token?style=flat-square&logo=GitHub"></a> -->
<!-- <a href="https://github.com/dromara/sa-token/issues"><img src="https://img.shields.io/github/issues/dromara/sa-token.svg?style=flat-square&logo=GitHub"></a> -->
<a href="https://github.com/dromara/sa-token/blob/master/LICENSE"><img src="https://img.shields.io/github/license/dromara/sa-token.svg?style=flat-square"></a>
</p>
<!-- <p align="center">学习测试请拉取 master 分支,dev 是在开发分支 (在根目录执行 `git checkout master`)</p> -->
<p align="center"><a href="https://sa-token.cc" target="_blank">在线文档:https://sa-token.cc</a></p>
<p align="center"><a href="https://sa-token.cc?way=readme" target="_blank">在线文档:https://sa-token.cc</a></p>
---
### Sa-Token 介绍
### 🛠️ Sa-Token 介绍
Sa-Token 是一个轻量级 Java 权限认证框架,目前拥有五大核心模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。
![sa-token-jss](https://oss.dev33.cn/sa-token/doc/home/sa-token-jss--tran.png)
![sa-token-jss](https://sa-token.cc/big-file/index/intro/sa-token-jss--tran.png)
要在 SpringBoot 项目中使用 Sa-Token,你只需要在 pom.xml 中引入依赖:
@@ -36,7 +36,7 @@ Sa-Token 是一个轻量级 Java 权限认证框架,目前拥有五大核心
</dependency>
```
除了 SpringBoot2、Sa-Token 还为 SpringBoot3、Solon、JFinal 等常见 Web 框架提供集成包,做到真正的开箱即用。
除了支持 SpringBoot2、Sa-Token 还为 SpringBoot3、Solon、JFinal 等常见 Web 框架提供集成包,做到真正的开箱即用。
<details>
@@ -144,59 +144,95 @@ registry.addInterceptor(new SaInterceptor(handler -> {
### SSO 单点登录
### 🍃 SSO 单点登录
Sa-Token SSO 分为三种模式,解决同域、跨域、共享Redis、跨Redis、前后端一体、前后端分离……等不同架构下的 SSO 接入问题
Sa-Token SSO 分为三种模式,解决`同域、跨域、共享Redis、跨Redis、前后端一体、前后端分离、纯 js、vue2、vue3、java 项目、非 java 项目` 等架构下的 SSO 认证需求
![sa-token-jss](https://oss.dev33.cn/sa-token/doc/home/sa-token-sso--white.png)
![sa-token-jss](https://sa-token.cc/big-file/doc/sso/sa-token-sso--white.png)
| 系统架构 | 采用模式 | 简介 | 文档链接 |
| :-------- | :-------- | :-------- | :-------- |
| 前端同域 + 后端同 Redis | 模式一 | 共享Cookie同步会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso1-client) |
| 前端同域 + 后端同 Redis | 模式 | URL重定向传播会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso2-client) |
| 前端不同域 + 后端同Redis | 模式 | Http请求获取会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso3-client) |
| 系统架构 | 采用模式 | 简介 | 文档链接 |
| :-------- | :-------- |:----------------| :-------- |
| 前端同域 + 后端同 Redis | 模式 | 共享Cookie同步会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso1-client) |
| 前端不同域 + 后端同 Redis | 模式 | URL重定向传播会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso2-client) |
| 前端不同域 + 后端 不同Redis | 模式三 | HTTP请求获取会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso3-client) |
1. 前端同域:就是指多个系统可以部署在同一个主域名之下,比如:`c1.domain.com`、`c2.domain.com`、`c3.domain.com`
2. 后端同Redis:就是指多个系统可以连接同一个Redis。(此处并非要所有项目数据都放在一个Redis中,Sa-Token提供 **`[权限缓存与业务缓存分离]`** 的解决方案)
3. 如果无法做到前端同域,也无法做到后端同Redis,可以走模式三Http请求校验 ticket 获取会话。
4. 提供 NoSdk 模式示例,不使用 Sa-Token 的系统也可以对接。
5. 提供 sso-server 接口文档,不使用 java 语言的系统也可以对接
6. 提供前后端分离整合方案:无论是 sso-server 还是 sso-client 前后端分离都可以整合
7. 提供安全校验:域名校验、ticket校验、参数签名校验,有效防 ticket 劫持,防请求重放等攻击
8. 参数防丢:笔者曾试验多个SSO框架,均有参数丢失情况,比如登录前是:`http://a.com?id=1&name=2`,登录成功后就变成了:`http://a.com?id=1`Sa-Token-SSO 内有专门算法保证了参数不丢失,登录成功后精准原路返回。
9. 提供用户数据同步/迁移方案的建议:开发前统一迁移、运行时实时数据同步、根据关联字段匹配、根据 center_id 字段匹配等。
10. 提供直接可运行的 demo 示例,帮助你快速熟悉 SSO 大致登录流程。
2. 后端同 Redis:就是指多个系统可以连接同一个 Redis,共享会话数据。
3. 如果无法做到前端同域后端同 Redis,可以走托底的模式三Http请求校验 ticket 获取会话。
4. 提供NoSdk 模式示例 + sso-server 接口文档,非 Sa-Token 项目、非 java 项目也可以对接。
5. 提供:多重安全校验:域名校验、ticket校验、参数签名校验,有效防 ticket 劫持,防请求重放等攻击
6. 提供:大量实战痛点教学:sso-server 前后端分离设计、sso-client 前后端分离设计、用户数据同步/迁移方案设计
7. 提供:直接可运行的 demo 示例,助你快速熟悉 SSO 大致登录流程
8. 提供:深度细节优化,参数防丢:笔者曾试验多个SSO框架,均有参数丢失情况,比如登录前是:`http://a.com?id=1&name=2`,登录成功后就变成了:`http://a.com?id=1`Sa-Token-SSO 内有专门算法保证了参数不丢失,登录成功后精准原路返回。
### OAuth2 授权认证
Sa-Token-OAuth2 模块分为四种授权模式,解决不同场景下的授权需求
| 授权模式 | 简介 |
| :-------- | :-------- |
| 授权码(Authorization Code | OAuth2.0 标准授权步骤,Server 端向 Client 端下放 Code 码,Client 端再用 Code 码换取授权 Token |
| 隐藏式(Implicit) | 无法使用授权码模式时的备用选择,Server 端使用 URL 重定向方式直接将 Token 下放到 Client 端页面 |
| 密码式(Password | Client直接拿着用户的账号密码换取授权 Token |
| 客户端凭证(Client Credentials| Server 端针对 Client 级别的 Token,代表应用自身的资源授权 |
### 🍂 OAuth2 授权认证
Sa-Token OAuth2 模块分为四种授权模式,解决不同场景下的授权需求
| 授权模式 | 简介 |
| :-------- | :-------- |
| 授权码式 | OAuth2 标准授权步骤,server 端下放 codeclient 端获取 code 码兑换 access_token |
| 隐藏式 | 备用选择,server 端使用 URL 重定向方式直接将 access_token 下放到 client 端页面 |
| 密码式 | client 直接拿着用户的账号密码换取授权 access_token |
| 客户端凭证式 | server 端针对 client 级别的 client_token,代表应用自身的资源授权 |
详细参考文档:[https://sa-token.cc/doc.html#/oauth2/readme](https://sa-token.cc/doc.html#/oauth2/readme)
### 开源集成案例
### 📖❓ 疑问解答
**1、Sa-Token 功能全不全?**
七年磨一剑:五大核心模块(登录、鉴权、SSO、OAuth2、微服务) + 众多实用插件 (短 token、jwt 集成、API 参数签名、API Key 秘钥授权...) 我们提供的不只是权限认证,我们提供的是一站式解决方案。
**2、Sa-Token 好不好学?**
中文文档 + 中文代码注释 + 中文交流社区 + 大量实战案例博客 + 多个视频教程 + 大量优秀开源项目集成案例。
**3、Sa-Token 用的人多不多?**
截止统计日 (2026-1-25) 起,Sa-Token 在:
- Gitee 关注量达到 48627 Star,位列平台所有推荐项目排行榜第一名。
- GitHub 关注量达到 18523 Star,是主要竞争框架 Spring Security 的 1.97 倍,Apache Shiro 的 4.19 倍。
- 25+ 微信粉丝群 (500人)8+ QQ粉丝群 (1000人 or 2000人) ,在线文档访问量月PV 20万+。
这是众多开发者用脚投票的数据,相信这些数据比任何言语都能证明 Sa-Token 的热度。
**4、Sa-Token 有哪些权威认证?**
曾获荣誉包括但不限于:Gitee GVP 最有价值开源项目、GitCode G-Star 优质开源项目、OSCHINA 2021 人气指数 TOP 30 开源项目、OSCHINA 2022 年度最火热中国开源项目社区之一、开放原子基金会2023快速成长开源项目、 Dromara 组织顶尖项目(之一)、可信开源社区共同体预备成员、所在开源社区 “Dromara” 荣获《2024中国互联网发展创新与投资大赛(开源)》二等奖。 Gitee High Star 计划项目(5000+star)。Gitee 2025年度开源项目 Web应用开发 Top 2。
**5、Sa-Token 收费吗?**
Sa-Token 采用 Apache-2.0 开源协议,承诺框架本身与在线文档永久免费开放。当然如果您有心赞助 Sa-Token,我们也不回避:[赞助链接](https://sa-token.cc/doc.html#/more/sa-token-donate)。
我们将定期同步赞助者名单到在线文档展示。(您需要注意的一点是:该赞助仅为友情赞助,不提供任何商业交换)
### 🚀 优秀开源集成案例
- [[ Snowy ]](https://gitee.com/xiaonuobase/snowy):国内首个国密前后分离快速开发平台,采用 Vue3 + Vite + SpringBoot + Mp + HuTool + SaToken。
- [[ RuoYi-Vue-Plus ]](https://gitee.com/dromara/RuoYi-Vue-Plus):重写RuoYi-Vue所有功能 集成 Sa-Token、Mybatis-Plus、Xxl-Job、knife4j、OSS 定期同步。
- [[ Smart-Admin ]](https://gitee.com/lab1024/smart-admin)SmartAdmin 国内首个以「高质量代码」为核心,「简洁、高效、安全」中后台快速开发平台。
- [[ 橙单 ]](https://gitee.com/orangeform/orange-admin) 橙单中台化低代码生成器。可完整支持多应用、多租户、多渠道、工作流、框架技术栈自由组合等。
- [[ 灯灯 ]](https://gitee.com/dromara/lamp-cloud) 专注于多租户解决方案的中后台快速开发平台。支持独立数据库、共享数据架构 和 非租户模式 ✨
- [[ 拾壹博客 ]](https://gitee.com/quequnlong/shiyi-blog):一款 vue + springboot 前后端分离的博客系统。
- [[ Snowy ]](https://gitee.com/xiaonuobase/snowy):国内首个国密前后分离快速开发平台,采用 Vue3 + AntDesignVue3 + Vite + SpringBoot + Mp + HuTool + SaToken。
- [[ RuoYi-Vue-Plus ]](https://gitee.com/dromara/RuoYi-Vue-Plus):重写RuoYi-Vue所有功能 集成 Sa-Token+Mybatis-Plus+Jackson+Xxl-Job+knife4j+Hutool+OSS 定期同步
- [[Smart-Admin]](https://gitee.com/lab1024/smart-admin)SmartAdmin国内首个以「高质量代码」为核心,「简洁、高效、安全」中后台快速开发平台;
- [[ 灯灯 ]](https://gitee.com/dromara/lamp-cloud) 专注于多租户解决方案的微服务中后台快速开发平台。租户模式支持独立数据库(DATASOURCE模式)、共享数据架构(COLUMN模式) 和 非租户模式(NONE模式)✨
- [[ EasyAdmin ]](https://gitee.com/lakernote/easy-admin):一个基于SpringBoot2 + Sa-Token + Mybatis-Plus + Snakerflow + Layui 的后台管理系统,灵活多变可前后端分离,也可单体,内置代码生成器、权限管理、工作流引擎等
- [[ sa-admin-server ]](https://gitee.com/wlf213/sa-admin-server) 基于 sa-admin-ui 的后台管理开发脚手架。
还有更多优秀开源案例无法逐一展示,请参考:[Awesome-Sa-Token](https://gitee.com/sa-token/awesome-sa-token)
### 友情链接
### 🔗 友情链接
- [[ OkHttps ]](https://gitee.com/ejlchina-zhxu/okhttps):轻量级 http 通信框架,API无比优雅,支持 WebSocket、Stomp 协议
- [[ Forest ]](https://gitee.com/dromara/forest):声明式与编程式双修,让天下没有难以发送的 HTTP 请求
- [[ Bean Searcher ]](https://github.com/ejlchina/bean-searcher):专注高级查询的只读 ORM,使一行代码实现复杂列表检索!
@@ -209,31 +245,34 @@ Sa-Token-OAuth2 模块分为四种授权模式,解决不同场景下的授权
### 代码托管
### 📦 代码托管
- Gitee[https://gitee.com/dromara/sa-token](https://gitee.com/dromara/sa-token)
- GitHub[https://github.com/dromara/sa-token](https://github.com/dromara/sa-token)
- GitCode[https://gitcode.com/dromara/sa-token](https://gitcode.com/dromara/sa-token)
- AtomGit[https://atomgit.com/dromara/sa-token](https://atomgit.com/dromara/sa-token)
### 交流群
### 💬 交流群
<!-- QQ交流群:685792424 [点击加入](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Y05Ld4125W92YSwZ0gA8e3RhG9Q4Vsfx&authKey=IomXuIuhP9g8G7l%2ByfkrRsS7i%2Fna0lIBpkTXxx%2BQEaz0NNEyJq00kgeiC4dUyNLS&noverify=0&group_code=685792424)-->
QQ交流群:823181187 [点击加入](https://qm.qq.com/q/EBIJVZBVGE)
QQ交流群:1081649142 [点击加入](https://qm.qq.com/q/SCAaZ6Ros2)
微信交流群:
<!-- <img src="https://oss.dev33.cn/sa-token/qr/wx-qr-m-400k.png" width="230px" title="微信群" /> -->
<img src="https://oss.dev33.cn/sa-token/qr/i-wx-qr2.png" width="230px" title="微信群" />
<img src="https://sa-token.cc/big-file/contact/i-wx-qr2.jpg" width="230px" title="微信群" />
(扫码添加微信备注:sa-token,邀您加入群聊)
PS扫码添加微信 (备注:sa-token),邀您加入群聊
<br>
<img class="s-w" src="http://sa-token.cc/big-file/contact/show/wx-group-show3--liubai.png" style="max-width: 50%;" alt="微信群" />
加入群聊的好处:
- 第一时间收到框架更新通知。
- 第一时间收到框架 bug 通知。
- 第一时间收到新增开源案例通知。
- 和众多大佬一起互相 (huá shuǐ) 交流 (mō yú)。
- 和众多大佬一起互相 (huá shuǐ) 交流 (mō yú) 🖐️🐟️
+1
View File
@@ -24,6 +24,7 @@ cd sa-token-demo-solon & call mvn clean & cd ..
cd sa-token-demo-solon-redisson & call mvn clean & cd ..
cd sa-token-demo-springboot & call mvn clean & cd ..
cd sa-token-demo-springboot3-redis & call mvn clean & cd ..
cd sa-token-demo-springboot4-redis & call mvn clean & cd ..
cd sa-token-demo-springboot-low-version & call mvn clean & cd ..
cd sa-token-demo-springboot-redis & call mvn clean & cd ..
cd sa-token-demo-springboot-redisson & call mvn clean & cd ..
+11 -6
View File
@@ -19,6 +19,7 @@
<!-- 所有模块 -->
<modules>
<module>sa-token-dependencies</module>
<module>sa-token-special-dependencies</module>
<module>sa-token-bom</module>
<module>sa-token-core</module>
<module>sa-token-starter</module>
@@ -71,7 +72,11 @@
<dependencyManagement>
<dependencies>
<!--
导入 sa-token-dependencies 所有版本定义,并传导到每个子项目。
需要注意的是:该 import 只会导入 <dependencyManagement> 部分,而不会导入 <dependencies> 部分和 <properties> 部分。
-->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dependencies</artifactId>
@@ -89,7 +94,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<version>3.15.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -100,10 +105,10 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.1</version>
<version>3.12.0</version>
<configuration>
<!-- 统一生成聚合文档,解决 mvn package 时控制台发出 javadoc 警告的问题 -->
<aggregate>true</aggregate>
<!-- <aggregate>true</aggregate>-->
<!-- 忽略部分 error 和 warning -->
<failOnError>false</failOnError>
<failOnWarnings>false</failOnWarnings>
@@ -123,7 +128,7 @@
<goal>jar</goal>
</goals>
<configuration>
<additionalparam>-Xdoclint:none</additionalparam>
<doclint>none</doclint>
</configuration>
</execution>
</executions>
@@ -133,7 +138,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.2.7</version>
<version>1.7.3</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
+34 -10
View File
@@ -4,6 +4,13 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
<relativePath/>
</parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-bom</artifactId>
<version>${revision}</version>
@@ -55,6 +62,11 @@
<artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot4-starter</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-servlet</artifactId>
@@ -72,7 +84,17 @@
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-autoconfig</artifactId>
<artifactId>sa-token-spring-boot-webmvc-reactor-v2v3v4-common</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-reactor-v2v3v4-common</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-webmvc-v3v4-common</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
@@ -85,6 +107,11 @@
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot4-starter</artifactId>
<version>${revision}</version>
</dependency>
<!-- endregion-->
<!-- region sa-token-plugin -->
@@ -123,6 +150,11 @@
<artifactId>sa-token-jackson</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jackson3</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-fastjson</artifactId>
@@ -243,21 +275,13 @@
</dependencies>
</dependencyManagement>
<!-- 父仓库 -->
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
<relativePath/>
</parent>
<!-- 项目构建 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<version>3.15.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
+1 -1
View File
@@ -16,7 +16,7 @@
<description>A Java Web lightweight authority authentication framework, comprehensive function, easy to use</description>
<dependencies>
<!-- Zero dependence -->
<!-- Zero Dependence -->
</dependencies>
@@ -15,7 +15,6 @@
*/
package cn.dev33.satoken.application;
import cn.dev33.satoken.fun.SaRetFunction;
import cn.dev33.satoken.fun.SaRetGenericFunction;
/**
@@ -17,6 +17,7 @@ package cn.dev33.satoken.config;
import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode;
import cn.dev33.satoken.stp.parameter.enums.SaLogoutRange;
import cn.dev33.satoken.stp.parameter.enums.SaReplacedLoginExitMode;
import cn.dev33.satoken.stp.parameter.enums.SaReplacedRange;
import cn.dev33.satoken.util.SaFoxUtil;
@@ -64,6 +65,11 @@ public class SaTokenConfig implements Serializable {
*/
private Boolean isShare = false;
/**
* 在 isConcurrent=false 时,决定新旧设备谁将放弃会话 (OLD_DEVICE=旧设备下线,新设备登录成功, NEW_DEVICE=新设备登录失败,旧设备维持在线)
*/
private SaReplacedLoginExitMode replacedLoginExitMode = SaReplacedLoginExitMode.OLD_DEVICE;
/**
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
*/
@@ -713,6 +719,22 @@ public class SaTokenConfig implements Serializable {
return this;
}
/**
* @return 在 isConcurrent=false 时,决定新旧设备谁将放弃会话 (OLD_DEVICE=旧设备下线,新设备登录成功, NEW_DEVICE=新设备登录失败,旧设备维持在线)
*/
public SaReplacedLoginExitMode getReplacedLoginExitMode() {
return replacedLoginExitMode;
}
/**
* @param replacedLoginExitMode 在 isConcurrent=false 时,决定新旧设备谁将放弃会话 (OLD_DEVICE=旧设备下线,新设备登录成功, NEW_DEVICE=新设备登录失败,旧设备维持在线)
* @return 对象自身
*/
public SaTokenConfig setReplacedLoginExitMode(SaReplacedLoginExitMode replacedLoginExitMode) {
this.replacedLoginExitMode = replacedLoginExitMode;
return this;
}
/**
* 获取 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端 ALL_DEVICE_TYPE=所有设备类型端)
*
@@ -860,6 +882,7 @@ public class SaTokenConfig implements Serializable {
+ ", isConcurrent=" + isConcurrent
+ ", isShare=" + isShare
+ ", replacedRange=" + replacedRange
+ ", replacedLoginExitMode=" + replacedLoginExitMode
+ ", maxLoginCount=" + maxLoginCount
+ ", overflowLogoutMode=" + overflowLogoutMode
+ ", maxTryTimes=" + maxTryTimes
@@ -25,7 +25,7 @@ import cn.dev33.satoken.context.model.SaTokenContextModelBox;
*
* <p>
* 使用 [ ThreadLocal 版本 ] 上下文处理器需要在全局过滤器或者拦截器内率先调用
* SaTokenContextForThreadLocalStorage.setBox(req, res, sto) 初始化上下文
* SaTokenContextForThreadLocalStaff.setBox(req, res, sto) 初始化上下文
* </p>
*
* <p> 一般情况下你不需要直接操作此类,因为框架的 starter 集成包里已经封装了完整的上下文操作 </p>
@@ -54,7 +54,7 @@ public interface SaTokenDao {
*
* @param key 键名称
* @param value 值
* @param timeout 数据有效期(值大于0时限时存储,值=-1时永久存储,值=0或小于-2时不存储)
* @param timeout 数据有效期(值大于0时限时存储,值=-1时永久存储,值=0或小于等于-2时不存储)
*/
void set(String key, String value, long timeout);
@@ -109,7 +109,7 @@ public interface SaTokenDao {
*
* @param key 键名称
* @param object 值
* @param timeout 存活时间(值大于0时限时存储,值=-1时永久存储,值=0或小于-2时不存储)
* @param timeout 存活时间(值大于0时限时存储,值=-1时永久存储,值=0或小于等于-2时不存储)
*/
void setObject(String key, Object object, long timeout);
@@ -55,7 +55,7 @@ public interface SaTokenDaoByObjectFollowString extends SaTokenDaoBySessionFollo
*
* @param key 键名称
* @param object 值
* @param timeout 存活时间(值大于0时限时存储,值=-1时永久存储,值=0或小于-2时不存储)
* @param timeout 存活时间(值大于0时限时存储,值=-1时永久存储,值=0或小于等于-2时不存储)
*/
@Override
default void setObject(String key, Object object, long timeout) {
@@ -80,6 +80,9 @@ public interface SaErrorCode {
/** 更改 Token 指向的 账号Id 时,账号Id值为空 */
int CODE_11003 = 11003;
/** 登录失败:当前账号已在其它客户端登录 */
int CODE_11004 = 11004;
/** 未能读取到有效Token */
int CODE_11011 = 11011;
@@ -16,9 +16,7 @@
package cn.dev33.satoken.httpauth.digest;
import cn.dev33.satoken.annotation.SaCheckHttpDigest;
import cn.dev33.satoken.context.SaHolder;
import java.util.LinkedHashMap;
import java.util.Map;
/**
@@ -36,6 +36,7 @@ import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode;
import cn.dev33.satoken.stp.parameter.enums.SaLogoutRange;
import cn.dev33.satoken.stp.parameter.enums.SaReplacedLoginExitMode;
import cn.dev33.satoken.stp.parameter.enums.SaReplacedRange;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
@@ -538,13 +539,25 @@ public class StpLogic {
protected String distUsableToken(Object id, SaLoginParameter loginParameter) {
// 1、获取全局配置的 isConcurrent 参数
// 如果配置为:不允许一个账号多地同时登录,则需要先将这个账号的历史登录会话标记为:被顶下线
if( ! loginParameter.getIsConcurrent()) {
if(loginParameter.getReplacedRange() == SaReplacedRange.CURR_DEVICE_TYPE) {
replaced(id, loginParameter.getDeviceType());
}
if(loginParameter.getReplacedRange() == SaReplacedRange.ALL_DEVICE_TYPE) {
replaced(id, createSaLogoutParameter());
// 如果配置为:不允许一个账号多地同时登录,则需要根据配置选择:
// 一.将这个账号的历史登录会话标记为:被顶下线
// 二.提示错误并拒绝本次登录
if (loginParameter.getReplacedLoginExitMode() == SaReplacedLoginExitMode.OLD_DEVICE){
if(loginParameter.getReplacedRange() == SaReplacedRange.CURR_DEVICE_TYPE) {
replaced(id, loginParameter.getDeviceType());
}
if(loginParameter.getReplacedRange() == SaReplacedRange.ALL_DEVICE_TYPE) {
replaced(id, createSaLogoutParameter());
}
} else if (loginParameter.getReplacedLoginExitMode() == SaReplacedLoginExitMode.NEW_DEVICE){
List<SaTerminalInfo> terminalListByLoginId = getTerminalListByLoginId(id);
// 只有当存在有效地会话时才拒绝登录
boolean hasActiveSession = terminalListByLoginId.stream()
.anyMatch(terminal -> isValidToken(terminal.getTokenValue()));
if (hasActiveSession) {
throw new SaTokenException("登录失败:当前账号已在其它客户端登录").setCode(SaErrorCode.CODE_11004);
}
}
}
@@ -548,7 +548,7 @@ public class StpUtil {
* @param tokenValue token
* @return 账号id
*/
public Object getLoginIdByTokenNotThinkFreeze(String tokenValue) {
public static Object getLoginIdByTokenNotThinkFreeze(String tokenValue) {
return stpLogic.getLoginIdByTokenNotThinkFreeze(tokenValue);
}
@@ -21,6 +21,7 @@ import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.fun.SaParamFunction;
import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode;
import cn.dev33.satoken.stp.parameter.enums.SaReplacedLoginExitMode;
import cn.dev33.satoken.stp.parameter.enums.SaReplacedRange;
import cn.dev33.satoken.util.SaTokenConsts;
@@ -110,6 +111,11 @@ public class SaLoginParameter {
*/
private Boolean isWriteHeader;
/**
* 在 isConcurrent=false 时,决定新旧设备谁将放弃会话
*/
private SaReplacedLoginExitMode replacedLoginExitMode;
/**
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
*/
@@ -158,6 +164,7 @@ public class SaLoginParameter {
this.replacedRange = config.getReplacedRange();
this.overflowLogoutMode = config.getOverflowLogoutMode();
this.rightNowCreateTokenSession = config.getRightNowCreateTokenSession();
this.replacedLoginExitMode = config.getReplacedLoginExitMode();
this.setupCookieConfig(cookie -> {
SaCookieConfig gCookie = config.getCookie();
@@ -568,6 +575,25 @@ public class SaLoginParameter {
return this;
}
/**
* 获取:在 isConcurrent=false 时,决定新旧设备谁将放弃会话 (OLD_DEVICE=旧设备下线,新设备登录成功, NEW_DEVICE=新设备登录失败,旧设备维持在线)
* @return /
*/
public SaReplacedLoginExitMode getReplacedLoginExitMode() {
return replacedLoginExitMode;
}
/**
* 设置:在 isConcurrent=false 时,决定新旧设备谁将放弃会话 (OLD_DEVICE=旧设备下线,新设备登录成功, NEW_DEVICE=新设备登录失败,旧设备维持在线)
* @param replacedLoginExitMode /
* @return 对象自身
*/
public SaLoginParameter setReplacedLoginExitMode(SaReplacedLoginExitMode replacedLoginExitMode) {
this.replacedLoginExitMode = replacedLoginExitMode;
return this;
}
/*
* toString
*/
@@ -577,6 +603,7 @@ public class SaLoginParameter {
+ "deviceType=" + deviceType
+ ", deviceId=" + deviceId
+ ", replacedRange=" + replacedRange
+ ", replacedLoginExitMode=" + replacedLoginExitMode
+ ", overflowLogoutMode=" + overflowLogoutMode
+ ", isLastingCookie=" + isLastingCookie
+ ", timeout=" + timeout
@@ -0,0 +1,35 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.stp.parameter.enums;
/**
* 在 isConcurrent=false 时,决定新旧设备谁将放弃会话
* @author 石泽旭
* @since 1.44.0
*/
public enum SaReplacedLoginExitMode {
/**
* 旧设备下线,新设备登录成功
*/
OLD_DEVICE,
/**
* 新设备登录失败,旧设备维持在线
*/
NEW_DEVICE
}
@@ -28,12 +28,34 @@ package cn.dev33.satoken.util;
public class StrFormatter {
/**
* 占位符
* 占位符(保留原有 public 访问权限,避免破坏外部依赖)
* @deprecated 语义不明确,建议内部使用 {@link #DEFAULT_PLACEHOLDER} 替代
*/
@Deprecated
public static String EMPTY_JSON = "{}";
/**
* 反斜杠转义字符(保留原有 public 访问权限,避免破坏外部依赖)
* @deprecated 命名不规范,建议内部使用 {@link #BACKSLASH_CHAR} 替代
*/
@Deprecated
public static char C_BACKSLASH = '\\';
/**
* 新增内部规范常量(private,仅内部使用)
* 默认占位符 */
private static final String DEFAULT_PLACEHOLDER = "{}";
/**
* 反斜杠转义字符
* */
private static final char BACKSLASH_CHAR = '\\';
/**
* 字符串构建器初始扩容长度
* */
private static final int BUFFER_INIT_CAPACITY = 50;
/**
* 格式化字符串<br>
* 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
@@ -45,10 +67,10 @@ public class StrFormatter {
*
* @param strPattern 字符串模板
* @param argArray 参数列表
* @return 结果
* @return 格式化后的结果
*/
public static String format(String strPattern, Object... argArray) {
return formatWith(strPattern, EMPTY_JSON, argArray);
return formatWith(strPattern, DEFAULT_PLACEHOLDER, argArray);
}
/**
@@ -63,7 +85,7 @@ public class StrFormatter {
* @param strPattern 字符串模板
* @param placeHolder 占位符,例如{}
* @param argArray 参数列表
* @return 结果
* @return 格式化后的结果
* @since 1.33.0
*/
public static String formatWith(String strPattern, String placeHolder, Object... argArray) {
@@ -74,7 +96,7 @@ public class StrFormatter {
final int placeHolderLength = placeHolder.length();
// 初始化定义好的长度以获得更好的性能
final StringBuilder sbu = new StringBuilder(strPatternLength + 50);
final StringBuilder sbu = new StringBuilder(strPatternLength + BUFFER_INIT_CAPACITY);
int handledPosition = 0;// 记录已经处理到的位置
int delimIndex;// 占位符所在位置
@@ -90,8 +112,8 @@ public class StrFormatter {
}
// 转义符
if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) {// 转义符
if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) {// 双转义符
if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == BACKSLASH_CHAR) {// 转义符
if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == BACKSLASH_CHAR) {// 双转义符
// 转义符之前还有一个转义符,占位符依旧有效
sbu.append(strPattern, handledPosition, delimIndex - 1);
sbu.append(argArray[argIndex]);
@@ -116,4 +138,4 @@ public class StrFormatter {
return sbu.toString();
}
}
}
+2
View File
@@ -35,6 +35,7 @@
<module>sa-token-demo-solon-redisson</module>
<module>sa-token-demo-springboot</module>
<module>sa-token-demo-springboot3-redis</module>
<module>sa-token-demo-springboot4-redis</module>
<module>sa-token-demo-springboot-low-version</module>
<module>sa-token-demo-springboot-redis</module>
<module>sa-token-demo-springboot-redisson</module>
@@ -55,6 +56,7 @@
<module>sa-token-demo-thymeleaf</module>
<module>sa-token-demo-webflux</module>
<module>sa-token-demo-webflux-springboot3</module>
<module>sa-token-demo-webflux-springboot4</module>
<module>sa-token-demo-websocket</module>
<module>sa-token-demo-websocket-spring</module>
<module>sa-token-demo-loveqq-boot</module>
@@ -156,7 +156,7 @@
<div style="height: 200px;"></div>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
<script>window.jQuery || alert('当前页面CDN服务商已宕机,请将所有js包更换为本地依赖')</script>
<!-- 配置缓存读取 -->
@@ -6,7 +6,7 @@ import cn.dev33.satoken.exception.SaTokenException;
import com.pj.satoken.custom_annotation.CheckAccount;
import org.noear.solon.annotation.Component;
import java.lang.reflect.Method;
import java.lang.reflect.AnnotatedElement;
/**
* 注解 CheckAccount 的处理器
@@ -25,7 +25,7 @@ public class CheckAccountHandler implements SaAnnotationHandlerInterface<CheckAc
// 每次请求校验注解时,会执行的方法
@Override
public void checkMethod(CheckAccount at, Method method) {
public void checkMethod(CheckAccount at, AnnotatedElement element) {
// 获取前端请求提交的参数
String name = SaHolder.getRequest().getParamNotNull("name");
String pwd = SaHolder.getRequest().getParamNotNull("pwd");
@@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.1</version>
<version>3.5.11</version>
<relativePath/>
</parent>
@@ -48,7 +48,7 @@
<!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<artifactId>sa-token-redis-template</artifactId>
<version>${sa-token.version}</version>
</dependency>
@@ -0,0 +1,65 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-springboot4-redis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot 4 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.3</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
</properties>
<dependencies>
<!-- SpringBoot 4 依赖:webmvc 替代 deprecated 的 starter-webaspectj 替代 starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aspectj</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot4-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token整合 Redis -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,20 @@
package com.pj;
import cn.dev33.satoken.SaManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Sa-Token 整合 SpringBoot4 示例,整合 redis
* @author click33
*
*/
@SpringBootApplication
public class SaTokenSpringBoot4Application {
public static void main(String[] args) {
SpringApplication.run(SaTokenSpringBoot4Application.class, args);
System.out.println("\n🎉 启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}
@@ -0,0 +1,56 @@
package com.pj.current;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.exception.DisableServiceException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 全局异常处理
*/
@RestControllerAdvice
public class GlobalException {
// 全局异常拦截(拦截项目中的所有异常)
@ExceptionHandler
public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
throws Exception {
// 打印堆栈,以供调试
System.out.println("全局异常---------------");
e.printStackTrace();
// 不同异常返回不同状态码
AjaxJson aj = null;
if (e instanceof NotLoginException) { // 如果是未登录异常
NotLoginException ee = (NotLoginException) e;
aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
}
else if(e instanceof NotRoleException) { // 如果是角色异常
NotRoleException ee = (NotRoleException) e;
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
}
else if(e instanceof NotPermissionException) { // 如果是权限异常
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
}
else if(e instanceof DisableServiceException) { // 如果是被封禁异常
DisableServiceException ee = (DisableServiceException) e;
aj = AjaxJson.getNotJur("当前账号 " + ee.getService() + " 服务已被封禁 (level=" + ee.getLevel() + ")" + ee.getDisableTime() + "秒后解封");
}
else { // 普通异常, 输出:500 + 异常信息
aj = AjaxJson.getError(e.getMessage());
}
// 返回给前端
return aj;
}
}
@@ -0,0 +1,26 @@
//package com.pj.current;
//
//import java.io.IOException;
//
//import org.springframework.boot.web.servlet.error.ErrorController;
//import org.springframework.web.bind.annotation.RequestMapping;
//import org.springframework.web.bind.annotation.RestController;
//
//import cn.dev33.satoken.util.SaResult;
//import jakarta.servlet.http.HttpServletRequest;
//import jakarta.servlet.http.HttpServletResponse;
//
///**
// * 处理 404
// * @author click33
// */
//@RestController
//public class NotFoundHandle implements ErrorController {
//
// @RequestMapping("/error")
// public Object error(HttpServletRequest request, HttpServletResponse response) throws IOException {
// response.setStatus(200);
// return SaResult.get(404, "not found", null);
// }
//
//}
@@ -0,0 +1,71 @@
package com.pj.satoken;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.util.SaResult;
/**
* [Sa-Token 权限认证] 配置类
* @author click33
*
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/**
* 注册 Sa-Token 拦截器打开注解鉴权功能
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器打开注解鉴权功能
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
/**
* 注册 [Sa-Token 全局过滤器]
*/
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
// 指定 [拦截路由] 与 [放行路由]
.addInclude("/**")// .addExclude("/favicon.ico")
// 认证函数: 每次请求执行
.setAuth(obj -> {
// System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath());
})
// 异常处理函数:每次认证函数发生异常时执行此函数
.setError(e -> {
System.out.println("---------- sa全局异常 ");
e.printStackTrace();
return SaResult.error(e.getMessage());
})
// 前置函数:在每次认证函数之前执行(BeforeAuth 不受 includeList 与 excludeList 的限制,所有请求都会进入)
.setBeforeAuth(r -> {
// ---------- 设置一些安全响应头 ----------
SaHolder.getResponse()
// 服务器名称
.setServer("sa-server")
// 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
.setHeader("X-Frame-Options", "SAMEORIGIN")
// 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
.setHeader("X-XSS-Protection", "1; mode=block")
// 禁用浏览器内容嗅探
.setHeader("X-Content-Type-Options", "nosniff")
;
})
;
}
}
@@ -0,0 +1,44 @@
package com.pj.satoken;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.stp.StpInterface;
/**
* 自定义权限验证接口扩展
*/
@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
List<String> list = new ArrayList<String>();
list.add("101");
list.add("user-add");
list.add("user-delete");
list.add("user-update");
list.add("user-get");
list.add("article-get");
return list;
}
/**
* 返回一个账号所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
List<String> list = new ArrayList<String>();
list.add("admin");
list.add("super-admin");
return list;
}
}
@@ -0,0 +1,80 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.annotation.SaCheckHttpBasic;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 注解鉴权测试
* @author click33
*
*/
@RestController
@RequestMapping("/at/")
public class AtController {
// 登录认证,登录之后才可以进入方法 ---- http://localhost:8082/at/checkLogin
@SaCheckLogin
@RequestMapping("checkLogin")
public SaResult checkLogin() {
return SaResult.ok();
}
// 权限认证,具备user-add权限才可以进入方法 ---- http://localhost:8082/at/checkPermission
@SaCheckPermission("user-add")
@RequestMapping("checkPermission")
public SaResult checkPermission() {
return SaResult.ok();
}
// 权限认证,同时具备所有权限才可以进入 ---- http://localhost:8082/at/checkPermissionAnd
@SaCheckPermission({"user-add", "user-delete", "user-update"})
@RequestMapping("checkPermissionAnd")
public SaResult checkPermissionAnd() {
return SaResult.ok();
}
// 权限认证,只要具备其中一个就可以进入 ---- http://localhost:8082/at/checkPermissionOr
@SaCheckPermission(value = {"user-add", "user-delete", "user-update"}, mode = SaMode.OR)
@RequestMapping("checkPermissionOr")
public SaResult checkPermissionOr() {
return SaResult.ok();
}
// 角色认证,只有具备admin角色才可以进入 ---- http://localhost:8082/at/checkRole
@SaCheckRole("admin")
@RequestMapping("checkRole")
public SaResult checkRole() {
return SaResult.ok();
}
// 完成二级认证 ---- http://localhost:8082/at/openSafe
@RequestMapping("openSafe")
public SaResult openSafe() {
StpUtil.openSafe(200); // 打开二级认证,有效期为200秒
return SaResult.ok();
}
// 通过二级认证后才可以进入 ---- http://localhost:8082/at/checkSafe
@SaCheckSafe
@RequestMapping("checkSafe")
public SaResult checkSafe() {
return SaResult.ok();
}
// 通过Basic认证后才可以进入 ---- http://localhost:8082/at/checkBasic
@SaCheckHttpBasic(account = "sa:123456")
@RequestMapping("checkBasic")
public SaResult checkBasic() {
return SaResult.ok();
}
}
@@ -0,0 +1,48 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 登录测试
* @author click33
*
*/
@RestController
@RequestMapping("/acc/")
public class LoginController {
// 测试登录 ---- http://localhost:8082/acc/doLogin?name=zhang&pwd=123456
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
if("zhang".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功");
}
return SaResult.error("登录失败");
}
// 查询登录状态 ---- http://localhost:8082/acc/isLogin
@RequestMapping("isLogin")
public SaResult isLogin() {
return SaResult.ok("是否登录:" + StpUtil.isLogin());
}
// 查询 Token 信息 ---- http://localhost:8082/acc/tokenInfo
@RequestMapping("tokenInfo")
public SaResult tokenInfo() {
return SaResult.data(StpUtil.getTokenInfo());
}
// 测试注销 ---- http://localhost:8082/acc/logout
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}
@@ -0,0 +1,61 @@
package com.pj.test;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.pj.util.Ttime;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 压力测试
* @author click33
*
*/
@RestController
@RequestMapping("/s-test/")
public class StressTestController {
// 测试 浏览器访问: http://localhost:8082/s-test/login
// 测试前,请先将 is-read-cookie 配置为 false
@RequestMapping("login")
public SaResult login() {
// StpUtil.getTokenSession().logout();
// StpUtil.logoutByLoginId(10001);
int count = 10; // 循环多少轮
int loginCount = 10000; // 每轮循环多少次
// 循环10次 取平均时间
List<Double> list = new ArrayList<>();
for (int i = 1; i <= count; i++) {
System.out.println("\n---------------------第" + i + "轮---------------------");
Ttime t = new Ttime().start();
// 每次登录的次数
for (int j = 1; j <= loginCount; j++) {
StpUtil.login("1000" + j, "PC-" + j);
if(j % 1000 == 0) {
System.out.println("已登录:" + j);
}
}
t.end();
list.add((t.returnMs() + 0.0) / 1000);
System.out.println("" + i + "" + "用时:" + t.toString());
}
// System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size());
System.out.println("\n---------------------测试结果---------------------");
System.out.println(list.size() + "次测试: " + list);
double ss = 0;
for (int i = 0; i < list.size(); i++) {
ss += list.get(i);
}
System.out.println("平均用时: " + ss / list.size());
return SaResult.ok();
}
}
@@ -0,0 +1,43 @@
package com.pj.test;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.servlet.util.SaTokenContextJakartaServletUtil;
import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试专用Controller
* @author click33
*
*/
@RestController
@RequestMapping("/test/")
public class TestController {
// 测试 浏览器访问: http://localhost:8082/test/test
@RequestMapping("test")
public SaResult test() {
System.out.println("------------进来了");
System.out.println(SpringMVCUtil.getRequest());
System.out.println(SaTokenContextJakartaServletUtil.getRequest());
return SaResult.ok();
}
// 测试 浏览器访问: http://localhost:8082/test/test2
@RequestMapping("test2")
public SaResult test2() {
return SaResult.ok();
}
// 测试 浏览器访问: http://localhost:8082/test/getRequestPath
@RequestMapping("getRequestPath")
public SaResult getRequestPath() {
System.out.println("-------------- 测试请求 path 获取");
System.out.println("request.getRequestURI() " + SpringMVCUtil.getRequest().getRequestURI());
System.out.println("saRequest.getRequestPath() " + SaHolder.getRequest().getRequestPath());
return SaResult.ok();
}
}
@@ -0,0 +1,162 @@
package com.pj.util;
import java.io.Serializable;
import java.util.List;
/**
* ajax请求返回Json格式数据的封装
*/
public class AjaxJson implements Serializable{
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200; // 成功状态码
public static final int CODE_ERROR = 500; // 错误状态码
public static final int CODE_WARNING = 501; // 警告状态码
public static final int CODE_NOT_JUR = 403; // 无权限状态码
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
public int code; // 状态码
public String msg; // 描述信息
public Object data; // 携带对象
public Long dataCount; // 数据总数,用于分页
/**
* 返回code
* @return
*/
public int getCode() {
return this.code;
}
/**
* 给msg赋值,连缀风格
*/
public AjaxJson setMsg(String msg) {
this.msg = msg;
return this;
}
public String getMsg() {
return this.msg;
}
/**
* 给data赋值,连缀风格
*/
public AjaxJson setData(Object data) {
this.data = data;
return this;
}
/**
* 将data还原为指定类型并返回
*/
@SuppressWarnings("unchecked")
public <T> T getData(Class<T> cs) {
return (T) data;
}
// ============================ 构建 ==================================
public AjaxJson(int code, String msg, Object data, Long dataCount) {
this.code = code;
this.msg = msg;
this.data = data;
this.dataCount = dataCount;
}
// 返回成功
public static AjaxJson getSuccess() {
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
}
public static AjaxJson getSuccess(String msg) {
return new AjaxJson(CODE_SUCCESS, msg, null, null);
}
public static AjaxJson getSuccess(String msg, Object data) {
return new AjaxJson(CODE_SUCCESS, msg, data, null);
}
public static AjaxJson getSuccessData(Object data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
public static AjaxJson getSuccessArray(Object... data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
// 返回失败
public static AjaxJson getError() {
return new AjaxJson(CODE_ERROR, "error", null, null);
}
public static AjaxJson getError(String msg) {
return new AjaxJson(CODE_ERROR, msg, null, null);
}
// 返回警告
public static AjaxJson getWarning() {
return new AjaxJson(CODE_ERROR, "warning", null, null);
}
public static AjaxJson getWarning(String msg) {
return new AjaxJson(CODE_WARNING, msg, null, null);
}
// 返回未登录
public static AjaxJson getNotLogin() {
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
}
// 返回没有权限的
public static AjaxJson getNotJur(String msg) {
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
}
// 返回一个自定义状态码的
public static AjaxJson get(int code, String msg){
return new AjaxJson(code, msg, null, null);
}
// 返回分页和数据的
public static AjaxJson getPageData(Long dataCount, Object data){
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
}
// 返回,根据受影响行数的(大于0=ok,小于0=error)
public static AjaxJson getByLine(int line){
if(line > 0){
return getSuccess("ok", line);
}
return getError("error").setData(line);
}
// 返回,根据布尔值来确定最终结果的 (true=okfalse=error)
public static AjaxJson getByBoolean(boolean b){
return b ? getSuccess("ok") : getError("error");
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@SuppressWarnings("rawtypes")
@Override
public String toString() {
String data_string = null;
if(data == null){
} else if(data instanceof List){
data_string = "List(length=" + ((List)data).size() + ")";
} else {
data_string = data.toString();
}
return "{"
+ "\"code\": " + this.getCode()
+ ", \"msg\": \"" + this.getMsg() + "\""
+ ", \"data\": " + data_string
+ ", \"dataCount\": " + dataCount
+ "}";
}
}
@@ -0,0 +1,63 @@
package com.pj.util;
/**
* 用于测试用时
* @author click33
*
*/
public class Ttime {
private long start=0; //开始时间
private long end=0; //结束时间
public static Ttime t = new Ttime(); //static快捷使用
/**
* 开始计时
* @return
*/
public Ttime start() {
start=System.currentTimeMillis();
return this;
}
/**
* 结束计时
*/
public Ttime end() {
end=System.currentTimeMillis();
return this;
}
/**
* 返回所用毫秒数
*/
public long returnMs() {
return end-start;
}
/**
* 格式化输出结果
*/
public void outTime() {
System.out.println(this.toString());
}
/**
* 结束并格式化输出结果
*/
public void endOutTime() {
this.end().outTime();
}
@Override
public String toString() {
return (returnMs() + 0.0) / 1000 + "s"; // 格式化为:0.01s
}
}
@@ -0,0 +1,50 @@
# 端口
server:
port: 8082
# sa-token 配置
sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik
token-style: uuid
# 是否输出操作日志
is-log: true
spring:
data:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
@@ -0,0 +1,40 @@
// 服务端地址
var baseUrl = "http://sa-sso-server.com:9000";
// sa
var sa = {};
// 打开loading
sa.loading = function(msg) {
layer.closeAll(); // 开始前先把所有弹窗关了
return layer.msg(msg, {icon: 16, shade: 0.3, time: 1000 * 20, skin: 'ajax-layer-load'});
};
// 隐藏loading
sa.hideLoading = function() {
layer.closeAll();
};
// 封装一下Ajax
sa.ajax = function(url, data, successFn) {
$.ajax({
url: baseUrl + url,
type: "post",
data: data,
dataType: 'json',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'satoken': localStorage.getItem('satoken')
},
success: function(res){
console.log('返回数据:', res);
successFn(res);
},
error: function(xhr, type, errorThrown){
if(xhr.status == 0){
return alert('无法连接到服务器,请检查网络');
}
return alert("异常:" + JSON.stringify(xhr));
}
});
}
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SSO-Server 平台首页</title>
</head>
<body>
<h2>SSO-Server 平台首页 (前后端分离模式) (平台中心模式)</h2>
<p>
<a href='sso-auth.html?client=sso-client3&redirect=http://sa-sso-client1.com:9003/sso/login?back=http://sa-sso-client1.com:9003/'
target='_blank'> 进入Client1系统 </a>
</p>
<p>
<a href='sso-auth.html?client=sso-client3&redirect=http://sa-sso-client2.com:9003/sso/login?back=http://sa-sso-client2.com:9003/'
target='_blank'> 进入Client2系统 </a>
</p>
<p>
<a href='sso-auth.html?client=sso-client3&redirect=http://sa-sso-client3.com:9003/sso/login?back=http://sa-sso-client3.com:9003/'
target='_blank'> 进入Client3系统 </a>
</p>
<!-- scripts -->
<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
<script src="./common.js"></script>
<script>
sa.ajax("/sso/isLogin", {}, function(res) {
if(res.data) {
// 已登录...
console.log('已登录,开始操作...');
} else {
layer.msg('未登录,请先登录...')
setTimeout(function(){
location.href = './sso-auth.html';
}, 1000)
}
})
</script>
</body>
</html>
@@ -4,7 +4,7 @@
<title>Sa-SSO-Server 认证中心-登录</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="./login.css">
<link rel="stylesheet" href="./sso-auth.css">
</head>
<body>
<div class="view-box">
@@ -35,9 +35,10 @@
</div>
<!-- scripts -->
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
<script src="./login.js"></script>
<script src="./common.js"></script>
<script src="./sso-auth.js"></script>
</body>
</html>
@@ -1,43 +1,3 @@
// 服务端地址
var baseUrl = "http://sa-sso-server.com:9000";
// sa
var sa = {};
// 打开loading
sa.loading = function(msg) {
layer.closeAll(); // 开始前先把所有弹窗关了
return layer.msg(msg, {icon: 16, shade: 0.3, time: 1000 * 20, skin: 'ajax-layer-load'});
};
// 隐藏loading
sa.hideLoading = function() {
layer.closeAll();
};
// 封装一下Ajax
sa.ajax = function(url, data, successFn) {
$.ajax({
url: baseUrl + url,
type: "post",
data: data,
dataType: 'json',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'satoken': localStorage.getItem('satoken')
},
success: function(res){
console.log('返回数据:', res);
successFn(res);
},
error: function(xhr, type, errorThrown){
if(xhr.status == 0){
return alert('无法连接到服务器,请检查网络');
}
return alert("异常:" + JSON.stringify(xhr));
}
});
}
// ----------------------------------- 相关事件 -----------------------------------
@@ -47,16 +7,29 @@ var pData = {
redirect: getParam('redirect', ''),
mode: getParam('mode', '')
};
sa.ajax("/sso/getRedirectUrl", pData, function(res) {
if(res.code == 200) {
// 已登录,并且redirect地址有效,开始跳转
location.href = res.data;
} else if(res.code == 401) {
console.log('未登录');
} else {
layer.alert(res.msg);
}
})
// 提供 redirect 参数时,登录后往 redirect 跳转
if(pData.redirect) {
sa.ajax("/sso/getRedirectUrl", pData, function(res) {
if(res.code == 200) {
// 已登录,并且redirect地址有效,开始跳转
location.href = res.data;
} else if(res.code == 401) {
console.log('未登录');
} else {
layer.alert(res.msg);
}
})
} else {
// 未提供 redirect 参数时,登录后往 home 跳转
sa.ajax("/sso/isLogin", {}, function(res) {
if(res.data) {
location.href = './home.html';
} else {
console.log('未登录,请先登录...');
}
})
}
// 登录
$('.login-btn').click(function(){
@@ -16,7 +16,15 @@ import org.springframework.web.bind.annotation.RestController;
*/
@RestController
public class H5Controller {
/**
* 返回当前是否已经登录
*/
@RequestMapping("/sso/isLogin")
public SaResult isLogin() {
return SaResult.data(StpUtil.isLogin());
}
/**
* 获取 redirectUrl
*/
@@ -53,7 +53,7 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
@@ -105,6 +105,17 @@ public class TestController {
return SaResult.ok("登录人:" + StpUtil.getLoginIdByToken(satoken));
}
// API测试:SaSession 写值 --- http://localhost:8081/test/sessionSet
@RequestMapping("sessionSet")
public Mono<SaResult> sessionSet() {
return SaReactorHolder.sync(() -> {
System.out.println("session name 值为:" + StpUtil.getSession().get("name"));
StpUtil.getSession().set("name", "zhangsan");
System.out.println("session name 值为:" + StpUtil.getSession().get("name"));
return SaResult.data(StpUtil.getSession().get("name"));
});
}
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public SaResult test() {
@@ -20,27 +20,27 @@ sa-token:
is-log: true
spring:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 10000ms
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
data:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 10000ms
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
@@ -0,0 +1,61 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-webflux-springboot4</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot 4 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.3</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
</properties>
<dependencies>
<!-- springboot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Sa-Token 权限认证(Reactor响应式集成), 在线文档:https://sa-token.cc/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot4-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token 整合 Redis -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template</artifactId>
<version>${sa-token.version}</version>
</dependency> -->
<!-- 提供redis连接池 -->
<!-- <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency> -->
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,22 @@
package com.pj;
import cn.dev33.satoken.SaManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Sa-Token整合webflux 示例 (springboot4)
*
* @author click33
* @since 2026年2月27日
*
*/
@SpringBootApplication
public class SaTokenWebfluxSpringboot4Application {
public static void main(String[] args) {
SpringApplication.run(SaTokenWebfluxSpringboot4Application.class, args);
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}
@@ -13,42 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.reactor.filter;
package com.pj.satoken;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaTokenContextModelBox;
import cn.dev33.satoken.exception.BackResultException;
import cn.dev33.satoken.exception.StopMatchException;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.reactor.util.SaReactorOperateUtil;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaTokenConsts;
import org.springframework.core.annotation.Order;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
/**
* CORS 跨域策略过滤器 (基于 Reactor)
*
* @author click33
* @since 1.42.0
* 自定义过滤器
*/
@Order(SaTokenConsts.CORS_FILTER_ORDER)
public class SaTokenCorsFilterForReactor implements WebFilter {
@Component
public class MyFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
System.out.println("进入自定义过滤器");
try {
// set 上下文再调用 Sa-Token 同步 API并在 finally 里清除上下文
SaReactorSyncHolder.setContext(exchange);
SaTokenContextModelBox box = SaHolder.getContext().getModelBox();
SaStrategy.instance.corsHandle.execute(box.getRequest(), box.getResponse(), box.getStorage());
}
catch (StopMatchException ignored) {}
catch (BackResultException e) {
return SaReactorOperateUtil.writeResult(exchange, e.getMessage());
System.out.println(StpUtil.isLogin());
}
finally {
SaReactorSyncHolder.clearContext();
@@ -0,0 +1,39 @@
package com.pj.satoken;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* [Sa-Token 权限认证] 配置类
* @author click33
*
*/
@Configuration
public class SaTokenConfigure {
/**
* 注册 [sa-token全局过滤器]
*/
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
// 指定 [拦截路由]
.addInclude("/**")
// 指定 [放行路由]
.addExclude("/favicon.ico")
// 指定[认证函数]: 每次请求执行
.setAuth(r -> {
System.out.println("---------- sa全局认证");
// SaRouter.match("/test/test", () -> StpUtil.checkLogin());
})
// 指定[异常处理函数]:每次[认证函数]发生异常时执行此函数
.setError(e -> {
System.out.println("---------- sa全局异常 ");
return SaResult.error(e.getMessage());
})
;
}
}
@@ -0,0 +1,44 @@
package com.pj.satoken;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.stp.StpInterface;
/**
* 自定义权限验证接口扩展
*/
@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
List<String> list = new ArrayList<String>();
list.add("101");
list.add("user-add");
list.add("user-delete");
list.add("user-update");
list.add("user-get");
list.add("article-get");
return list;
}
/**
* 返回一个账号所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
List<String> list = new ArrayList<String>();
list.add("admin");
list.add("super-admin");
return list;
}
}
@@ -0,0 +1,32 @@
package com.pj.test;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
@Configuration
public class DefineRoutes {
/**
* 函数式编程,初始化路由表
* @return 路由表
*/
@Bean
public RouterFunction<ServerResponse> getRoutes() {
return RouterFunctions.route(RequestPredicates.GET("/fun"), req -> {
return SaReactorSyncHolder.setContext(req.exchange(), () -> {
System.out.println("是否登录:" + StpUtil.isLogin());
SaResult res = SaResult.data(StpUtil.getTokenInfo());
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(res);
});
});
}
}
@@ -0,0 +1,19 @@
package com.pj.test;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理
*/
@RestControllerAdvice
public class GlobalException {
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}
@@ -0,0 +1,126 @@
package com.pj.test;
import cn.dev33.satoken.reactor.context.SaReactorHolder;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.time.Duration;
/**
* 测试专用Controller
* @author click33
*
*/
@RestController
@RequestMapping("/test/")
public class TestController {
@Autowired
UserService userService;
// 登录测试:Controller 里调用 Sa-Token API --- http://localhost:8081/test/login
@RequestMapping("login")
public Mono<SaResult> login(@RequestParam(defaultValue="10001") String id) {
return SaReactorHolder.sync(() -> {
StpUtil.login(id);
return SaResult.ok("登录成功");
});
}
// API测试:手动设置上下文、try-finally 形式 --- http://localhost:8081/test/isLogin
@RequestMapping("isLogin")
public SaResult isLogin(ServerWebExchange exchange) {
try {
SaReactorSyncHolder.setContext(exchange);
System.out.println("是否登录:" + StpUtil.isLogin());
return SaResult.data(StpUtil.getTokenInfo());
} finally {
SaReactorSyncHolder.clearContext();
}
}
// API测试:手动设置上下文、lambda 表达式形式 --- http://localhost:8081/test/isLogin2
@RequestMapping("isLogin2")
public SaResult isLogin2(ServerWebExchange exchange) {
SaResult res = SaReactorSyncHolder.setContext(exchange, ()->{
System.out.println("是否登录:" + StpUtil.isLogin());
return SaResult.data(StpUtil.getTokenInfo());
});
return SaResult.data(res);
}
// API测试:自动设置上下文、lambda 表达式形式 --- http://localhost:8081/test/isLogin3
@RequestMapping("isLogin3")
public Mono<SaResult> isLogin3() {
return SaReactorHolder.sync(() -> {
System.out.println("是否登录:" + StpUtil.isLogin());
userService.isLogin();
return SaResult.data(StpUtil.getTokenInfo());
});
}
// API测试:自动设置上下文、调用 userService Mono 方法 --- http://localhost:8081/test/isLogin4
@RequestMapping("isLogin4")
public Mono<SaResult> isLogin4() {
return userService.findUserIdByNamePwd("ZhangSan", "123456").flatMap(userId -> {
return SaReactorHolder.sync(() -> {
StpUtil.login(userId);
return SaResult.data(StpUtil.getTokenInfo());
});
});
}
// API测试:切换线程、复杂嵌套调用 --- http://localhost:8081/test/isLogin5
@RequestMapping("isLogin5")
public Mono<SaResult> isLogin5() {
System.out.println("线程id-----" + Thread.currentThread().getId());
// 要点:在流里调用 Sa-Token API 之前,必须用 SaReactorHolder.sync( () -> {} ) 进行包裹
return Mono.delay(Duration.ofSeconds(1))
.doOnNext(r-> System.out.println("线程id-----" + Thread.currentThread().getId()))
.map(r-> SaReactorHolder.sync( () -> userService.isLogin() ))
.map(r-> userService.findUserIdByNamePwd("ZhangSan", "123456"))
.map(r-> SaReactorHolder.sync( () -> userService.isLogin() ))
.flatMap(isLogin -> {
System.out.println("是否登录 " + isLogin);
return SaReactorHolder.sync(() -> {
System.out.println("是否登录 " + StpUtil.isLogin());
return SaResult.data(StpUtil.getTokenInfo());
});
});
}
// API测试:使用上下文无关的API --- http://localhost:8081/test/isLogin6
@RequestMapping("isLogin6")
public SaResult isLogin6(@CookieValue("satoken") String satoken) {
System.out.println("token 为:" + satoken);
System.out.println("登录人:" + StpUtil.getLoginIdByToken(satoken));
return SaResult.ok("登录人:" + StpUtil.getLoginIdByToken(satoken));
}
// API测试:SaSession 写值 --- http://localhost:8081/test/sessionSet
@RequestMapping("sessionSet")
public Mono<SaResult> sessionSet() {
return SaReactorHolder.sync(() -> {
System.out.println("session name 值为:" + StpUtil.getSession().get("name"));
StpUtil.getSession().set("name", "zhangsan");
System.out.println("session name 值为:" + StpUtil.getSession().get("name"));
return SaResult.data(StpUtil.getSession().get("name"));
});
}
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public SaResult test() {
System.out.println("线程id------- " + Thread.currentThread().getId());
return SaResult.ok();
}
}
@@ -0,0 +1,25 @@
package com.pj.test;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
/**
* 模拟 Service 方法
* @author click33
* @since 2025/4/6
*/
@Service
public class UserService {
public boolean isLogin() {
System.out.println("UserService 里调用 API 测试,是否登录:" + StpUtil.isLogin());
return StpUtil.isLogin();
}
public Mono<Long> findUserIdByNamePwd(String name, String pwd) {
// ...
return Mono.just(10001L);
}
}
@@ -0,0 +1,47 @@
# 端口
server:
port: 8081
# sa-token 配置
sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik
token-style: uuid
# 是否输出操作日志
is-log: true
spring:
data:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 10000ms
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
+1 -1
View File
@@ -53,7 +53,7 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
@@ -105,6 +105,17 @@ public class TestController {
return SaResult.ok("登录人:" + StpUtil.getLoginIdByToken(satoken));
}
// API测试:SaSession 写值 --- http://localhost:8081/test/sessionSet
@RequestMapping("sessionSet")
public Mono<SaResult> sessionSet() {
return SaReactorHolder.sync(() -> {
System.out.println("session name 值为:" + StpUtil.getSession().get("name"));
StpUtil.getSession().set("name", "zhangsan");
System.out.println("session name 值为:" + StpUtil.getSession().get("name"));
return SaResult.data(StpUtil.getSession().get("name"));
});
}
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public SaResult test() {
+30 -89
View File
@@ -4,33 +4,35 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-bom</artifactId>
<version>${revision}</version>
<!-- 寻址 -->
<relativePath>../sa-token-bom/pom.xml</relativePath>
</parent>
<packaging>pom</packaging>
<artifactId>sa-token-dependencies</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<name>sa-token-dependencies</name>
<description>Sa-Token Dependencies</description>
<properties>
<revision>1.44.0</revision>
<!-- 统一定义依赖版本号 -->
<springboot.version>2.7.18</springboot.version>
<springboot3.version>3.4.3</springboot3.version>
<spring-web.low.version>5.3.39</spring-web.low.version>
<reactor-core.version>3.7.4</reactor-core.version>
<!-- 第三方依赖版本号 -->
<jackson-databind.version>2.13.4.1</jackson-databind.version>
<jackson-datatype-jsr310.version>2.11.2</jackson-datatype-jsr310.version>
<jackson3-databind.version>3.1.0</jackson3-databind.version>
<servlet-api.version>3.1.0</servlet-api.version>
<jakarta-servlet-api.version>6.0.0</jakarta-servlet-api.version>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<freemarker.version>2.3.34</freemarker.version>
<solon.version>3.2.1</solon.version>
<noear-redisx.version>1.6.9</noear-redisx.version>
<noear-snack3.version>3.2.133</noear-snack3.version>
<noear-redisx.version>1.8.2</noear-redisx.version>
<noear-snack3.version>3.2.139</noear-snack3.version>
<noear-snack4.version>4.0.20</noear-snack4.version>
<jfinal.version>4.9.17</jfinal.version>
<jboot.version>3.14.4</jboot.version>
<loveqq.version>1.1.2-java8</loveqq.version>
<loveqq.version>1.1.5-java8</loveqq.version>
<commons-pool2.version>2.5.0</commons-pool2.version>
<dubbo.version>2.7.21</dubbo.version>
<grpc-spring-boot-starter.version>2.10.1.RELEASE</grpc-spring-boot-starter.version>
@@ -68,40 +70,19 @@
<version>${jakarta-servlet-api.version}</version>
</dependency>
<!-- spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.version}</version>
</dependency>
<!-- spring-boot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${springboot.version}</version>
</dependency>
<!-- spring-web -->
<!-- <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.7</version>
</dependency> -->
<!-- reactor-core -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>${reactor-core.version}</version>
</dependency>
<!-- jackson-databind -->
<!-- jackson2 databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-databind.version}</version>
</dependency>
<!-- jackson3 databind (tools.jackson) -->
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson3-databind.version}</version>
</dependency>
<!-- solon -->
<dependency>
@@ -116,6 +97,13 @@
<artifactId>snack3</artifactId>
<version>${noear-snack3.version}</version>
</dependency>
<!-- snack4 -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>snack4</artifactId>
<version>${noear-snack4.version}</version>
</dependency>
<!-- jboot -->
<dependency>
@@ -152,25 +140,7 @@
<version>${loveqq.version}</version>
</dependency>
<!-- test -->
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${springboot.version}</version>
</dependency> -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${springboot.version}</version>
</dependency>
<!-- ****************** sa-token-plugin 相关依赖 ****************** -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${springboot.version}</version>
</dependency>
<!-- Redisson 相关操作API -->
<dependency>
@@ -241,26 +211,6 @@
<version>${freemarker.version}</version>
</dependency>
<!-- spring-boot-starter-thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>${springboot.version}</version>
</dependency>
<!-- spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${springboot.version}</version>
</dependency>
<!-- spring-boot-starter-actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${springboot.version}</version>
</dependency>
<!-- hutool-jwt -->
<dependency>
@@ -317,15 +267,6 @@
<version>${okhttps.version}</version>
</dependency>
<!-- sa-token 自身依赖 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-bom</artifactId>
<version>${revision}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
+49 -12
View File
@@ -2,33 +2,35 @@
<img alt="logo" src="https://sa-token.cc/logo.png" width="150" height="150">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.44.0</h1>
<h5 align="center">开源、免费、轻量级 Java 权限认证框架,让鉴权变得简单、优雅!</h5>
<h5 align="center">开源、免费、一站式 java 权限认证框架,让鉴权变得简单、优雅!</h5>
<p align="center" class="badge-box">
<a href="https://gitee.com/dromara/sa-token/stargazers"><img src="https://gitee.com/dromara/sa-token/badge/star.svg?theme=gvp"></a>
<a href="https://gitee.com/dromara/sa-token/members"><img src="https://gitee.com/dromara/sa-token/badge/fork.svg?theme=gvp"></a>
<a href="https://gitcode.com/dromara/sa-token/stargazers"><img src="https://gitcode.com/dromara/Sa-Token/star/badge.svg"></a>
<a href="https://atomgit.com/dromara/sa-token/stargazers"><img src="https://atomgit.com/dromara/Sa-Token/star/badge.svg"></a>
<a href="https://github.com/dromara/sa-token/stargazers"><img src="https://img.shields.io/github/stars/dromara/sa-token?style=flat-square&logo=GitHub"></a>
<a href="https://github.com/dromara/sa-token/network/members"><img src="https://img.shields.io/github/forks/dromara/sa-token?style=flat-square&logo=GitHub"></a>
<a href="https://github.com/dromara/sa-token/watchers"><img src="https://img.shields.io/github/watchers/dromara/sa-token?style=flat-square&logo=GitHub"></a>
<!-- <a href="https://github.com/dromara/sa-token/watchers"><img src="https://img.shields.io/github/watchers/dromara/sa-token?style=flat-square&logo=GitHub"></a> -->
<!-- <a href="https://github.com/dromara/sa-token/issues"><img src="https://img.shields.io/github/issues/dromara/sa-token.svg?style=flat-square&logo=GitHub"></a> -->
<a href="https://github.com/dromara/sa-token/blob/master/LICENSE"><img src="https://img.shields.io/github/license/dromara/sa-token.svg?style=flat-square"></a>
</p>
---
## 前言:️
## 📝 前言:️
为了保证新同学不迷路,请允许我唠叨一下:无论您从何处看到本篇文章,最新开发文档永远在:[https://sa-token.cc](https://sa-token.cc)
建议收藏在浏览器书签,如果您已经身处本网站下,则请忽略此条说明。
本文档将会尽力讲解每个功能的设计原因、应用场景,用心阅读文档,你学习到的将不止是 `Sa-Token` 框架本身,更是绝大多数场景下权限设计的最佳实践。
## Sa-Token 介绍
## 🛠️ Sa-Token 介绍
**Sa-Token** 是一个轻量级 Java 权限认证框架,主要解决:**登录认证**、**权限认证**、**单点登录**、**OAuth2.0**、**分布式Session会话**、**微服务网关鉴权**
等一系列权限相关问题。
![sa-token-jss](https://oss.dev33.cn/sa-token/doc/home/sa-token-jss--tran.png)
<!-- ![sa-token-jss](/big-file/index/intro/sa-token-jss--tran.png) -->
<object class="sa-token-jss-img" data="/big-file/index/intro/sa-token-jss--tran--onclick.svg"></object>
Sa-Token 旨在以简单、优雅的方式完成系统的权限认证部分,以登录认证为例,你只需要:
@@ -83,7 +85,7 @@ registry.addInterceptor(new SaInterceptor(handler -> {
当你受够 Shiro、SpringSecurity 等框架的三拜九叩之后,你就会明白,相对于这些传统老牌框架,Sa-Token 的 API 设计是多么的简单、优雅!
## Sa-Token 功能一览
## 🎉 Sa-Token 功能一览
Sa-Token 目前主要五大功能模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。
@@ -127,10 +129,45 @@ Sa-Token 目前主要五大功能模块:登录认证、权限认证、单点
功能结构图:
![sa-token-js](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/x/sa-token-js4.png 's-w')
<img class="s-w" src="/big-file/index/intro/sa-token-js4.png" />
## 开源仓库 Star 趋势
## 📖❓ 疑问解答
**1、Sa-Token 功能全不全?**
七年磨一剑:五大核心模块(登录、鉴权、SSO、OAuth2、微服务) + 众多实用插件 (短 token、jwt 集成、API 参数签名、API Key 秘钥授权...) 我们提供的不只是权限认证,我们提供的是一站式解决方案。
**2、Sa-Token 好不好学?**
中文文档 + 中文代码注释 + 中文交流社区 + 大量实战案例博客 + 多个视频教程 + 大量优秀开源项目集成案例。
**3、Sa-Token 用的人多不多?**
截止统计日 (2026-1-25) 起,Sa-Token 在:
- Gitee 关注量达到 48627 Star,位列平台所有推荐项目排行榜第一名。
- GitHub 关注量达到 18523 Star,是主要竞争框架 Spring Security 的 1.97 倍,Apache Shiro 的 4.19 倍。
- 25+ 微信粉丝群 (500人)8+ QQ粉丝群 (1000人 or 2000人) ,在线文档访问量月PV 20万+。
这是众多开发者用脚投票的数据,相信这些数据比任何言语都能证明 Sa-Token 的热度。
**4、Sa-Token 有哪些权威认证?**
曾获荣誉包括但不限于:Gitee GVP 最有价值开源项目、GitCode G-Star 优质开源项目、OSCHINA 2021 人气指数 TOP 30 开源项目、OSCHINA 2022 年度最火热中国开源项目社区之一、开放原子基金会2023快速成长开源项目、 Dromara 组织顶尖项目(之一)、可信开源社区共同体预备成员、所在开源社区 “Dromara” 荣获《2024中国互联网发展创新与投资大赛(开源)》二等奖。 Gitee High Star 计划项目(5000+star)。Gitee 2025年度开源项目 Web应用开发 Top 2。
**5、Sa-Token 收费吗?**
Sa-Token 采用 Apache-2.0 开源协议,承诺框架本身与在线文档永久免费开放。当然如果您有心赞助 Sa-Token,我们也不回避:[赞助链接](https://sa-token.cc/doc.html#/more/sa-token-donate)。
我们将定期同步赞助者名单到在线文档展示。(您需要注意的一点是:该赞助仅为友情赞助,不提供任何商业交换)
## 📈 开源仓库 Star 趋势
<p class="un-dec-a-pre"></p>
@@ -138,15 +175,15 @@ Sa-Token 目前主要五大功能模块:登录认证、权限认证、单点
如果 Sa-Token 帮助到了您,希望您可以为其点上一个 `star`
[码云](https://gitee.com/dromara/sa-token)、
[AtomGit](https://atomgit.com/dromara/sa-token)、
[GitHub](https://github.com/dromara/sa-token)
## 使用Sa-Token的开源项目
## 🚀 使用 Sa-Token 的开源项目
参考:[Sa-Token 生态](/more/link)
## 交流群
## 💬 交流群
加入 Sa-Token 框架 QQ、微信讨论群:[点击加入](/more/join-group.md)
+9 -2
View File
@@ -55,7 +55,7 @@
- [NoSdk、ReSdk 模式与非 java 项目](/sso/sso-nosdk)
- [SSO 代码 API 参考](/sso/sso-dev)
- [常见问题总结](/sso/sso-questions)
- [Sa-Sso-Pro:单点登录商业版](/sso/sso-pro)
- [Sa-Pro:单点登录商业版](/pro/st_sso)
- **OAuth2.0**
- [OAuth2.0简述](/oauth2/readme)
@@ -75,6 +75,7 @@
- [OAuth2-与登录会话实现数据互通](/oauth2/oauth2-interworking)
- [OAuth2 代码 API 参考](/oauth2/oauth2-dev)
- [常见问题总结](/oauth2/oauth2-questions)
- [Sa-Max:统一认证商业版](/pro/st_oauth2)
<!-- - [前后端分离模式整合方案](/oauth2/4) -->
<!-- - [平台中心模式开发](/oauth2/5) -->
<!-- - [jwt 风格 token](/oauth2/6) -->
@@ -103,6 +104,7 @@
- [Sa-Token 插件开发指南](/fun/plugin-dev)
- [自定义 SaTokenContext 指南](/fun/sa-token-context)
- **API手册**
- [StpUtil-鉴权工具类](/api/stp-util)
- [SaSession-会话对象](/api/sa-session)
@@ -111,12 +113,18 @@
- [全局类、方法](/more/common-action)
- **框架设计**
- [仓库目录](/arch/dir-intro)
- [数据结构](/arch/data-structure)
- **其它**
- [更新日志](/more/update-log)
- [框架生态](/more/link)
- [框架博客](/more/blog)
- [推荐公众号](/more/tj-gzh)
- [加入讨论群](/more/join-group)
- [Sa-Token 内容合作群](/more/content-cooperation)
- [赞助 Sa-Token](/more/sa-token-donate)
- [需求提交](/more/demand-commit)
- [问卷调查](/more/wenjuan)
@@ -133,7 +141,6 @@
- [数据读写三大作用域](/fun/three-scope)
- [TokenInfo参数详解](/fun/token-info)
- [异常细分状态码](/fun/exception-code)
- [数据结构](/fun/data-structure)
- [自定义注解](/fun/custom-annotations)
- [防火墙](/fun/firewall)
- [参考:把权限放在缓存里](/fun/jur-cache)
+106 -30
View File
@@ -4,7 +4,7 @@ SaStrategy-全局策略,核心逻辑的代理封装。
---
### 所有策略
### 核心策略
``` java
/**
@@ -24,6 +24,11 @@ public Function<String, SaSession> createSession = (sessionId) -> {
return new SaSession(sessionId);
};
/**
* 反序列化 SaSession 时默认指定的类型
*/
public Class<? extends SaSession> sessionClassType = SaSession.class;
/**
* 判断:集合中是否包含指定元素(模糊匹配)
* <p> 参数 [集合, 元素]
@@ -32,38 +37,14 @@ public BiFunction<List<String>, String, Boolean> hasElement = (list, element) ->
return false;
};
/**
* 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
* <p> 参数 [Method句柄]
*/
public Consumer<Method> checkMethodAnnotation = (method) -> {
// ...
};
/**
* 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
* <p> 参数 [element元素]
* 生成唯一式 token 的算法
* <p> 参数:元素名称, 最大尝试次数, 创建 token 函数, 检查 token 函数 </p>
*/
public Consumer<AnnotatedElement> checkElementAnnotation = (target) -> {
// ...
};
/**
* 从元素上获取注解(注解鉴权内部实现)
* <p> 参数 [element元素,要获取的注解类型]
*/
public BiFunction<AnnotatedElement, Class<? extends Annotation> , Annotation> getAnnotation = (element, annotationClass)->{
// 默认使用jdk的注解处理器
return element.getAnnotation(annotationClass);
};
/**
* 拼接两个url
* <p> 例如:url1=http://domain.cnurl2=/sso/auth,则返回:http://domain.cn/sso/auth
* <p> 参数 [第一个url, 第二个url]
*/
public BiFunction<String, String, String> spliceTwoUrl = (url1, url2) -> {
return xxx;
public SaGenerateUniqueTokenFunction generateUniqueToken = (elementName, maxTryTimes, createTokenFunction, checkTokenFunction) -> {
// ...
return "xxxxxx";
};
/**
@@ -74,10 +55,105 @@ public BiFunction<String, String, String> spliceTwoUrl = (url1, url2) -> {
public Function<StpLogic, Boolean> autoRenew = (stpLogic) -> {
return stpLogic.getConfigOrGlobal().getAutoRenew();
};
/**
* 创建 StpLogic 的算法
* <p> 参数:账号体系标识 </p>
* <p> 返回:创建好的 StpLogic 对象 </p>
*/
public SaCreateStpLogicFunction createStpLogic = (loginType) -> {
return new StpLogic(loginType);
};
/**
* 路由匹配策略
* <p> 参数:pattern, path </p>
* <p> 返回:是否匹配 </p>
*/
public SaRouteMatchFunction routeMatcher = (pattern, path) -> {
return true;
};
/**
* CORS 策略处理函数
* <p> 参数:请求包装对象, 响应包装对象, 数据读写对象 </p>
*/
public SaCorsHandleFunction corsHandle = (req, res, sto) -> {
};
```
### 注解操作相关策略
``` java
/**
* 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
* <p> 参数:Method句柄 </p>
* <p> 返回:无 </p>
*/
public SaCheckMethodAnnotationFunction checkMethodAnnotation = (method) -> {
// ...
};
/**
* 对一个 [Element] 对象进行注解校验 (注解鉴权内部实现)
* <p> 参数:element元素 </p>
* <p> 返回:无 </p>
*/
@SuppressWarnings("unchecked")
public SaCheckElementAnnotationFunction checkElementAnnotation = (element) -> {
// ...
};
/**
* 从元素上获取注解
* <p> 参数:element元素,要获取的注解类型 </p>
* <p> 返回:注解对象 </p>
*/
public SaGetAnnotationFunction getAnnotation = (element, annotationClass)->{
return element.getAnnotation(annotationClass);
};
/**
* 判断一个 Method 或其所属 Class 是否包含指定注解
* <p> 参数:Method、注解 </p>
* <p> 返回:是否包含 </p>
*/
public SaIsAnnotationPresentFunction isAnnotationPresent = (method, annotationClass) -> {
// ...
return false;
};
/**
* SaCheckELRootMap 扩展函数
* <p> 参数:SaCheckELRootMap 对象 </p>
*/
public SaCheckELRootMapExtendFunction checkELRootMapExtendFunction = rootMap -> {
// 默认不做任何处理
};
```
### 防火墙相关策略
``` java
/**
* 防火墙校验函数
* <p> 参数:请求对象、响应对象、预留扩展参数 </p>
*/
public SaFirewallCheckFunction check = (req, res, extArg) -> {
// ...
};
/**
* 自定义当请求 path 校验不通过时地处理方案
* <p> 参数:防火墙校验异常、请求对象、响应对象、预留扩展参数 </p>
*/
SaFirewallStrategy.instance.checkFailHandle = (e, req, res, extArg) -> {
// 自定义处理逻辑 ...
};
```
参考:[防火墙](/fun/firewall)
@@ -6,24 +6,20 @@
### 1.1、token -> loginId 映射
``` js
{tokenName}:{loginType}:token:{tokenValue}
// ttl = 此 token 的 timeout 有效期
{tokenName}:{loginType}:token:{tokenValue} --> {loginId}
```
<details>
<summary>详细</summary>
key 示例 ttl 为 timeout 有效期值
示例:
``` js
satoken:login:token:47ab0105-2be1-400c-b517-82f81a0cfcf8
```
正常 value 格式
``` js
10001 loginId,登录id,一般为账号id
satoken:login:token:47ab0105-2be1-400c-b517-82f81a0cfcf8 --> 10001
```
异常 value 格式
``` js
-1 未能从请求中读取到有效 token
-2 已读取到 token,但是 token 无效
@@ -40,28 +36,27 @@ satoken:login:token:47ab0105-2be1-400c-b517-82f81a0cfcf8
### 1.2、active-timeout
``` js
{tokenName}:{loginType}:last-active:{tokenValue}
// ttl = 对应 token 的 timeout 有效期值
{tokenName}:{loginType}:last-active:{tokenValue} --> {13位时间戳}
```
<details>
<summary>详细</summary>
key 示例 key 的 ttl 为 timeout 有效期值
示例:
``` js
satoken:login:last-active:06d1f12b-614e-4c00-8d8e-c07fef5f4aa9
satoken:login:last-active:06d1f12b-614e-4c00-8d8e-c07fef5f4aa9 --> 1722334954193
```
value 格式
value 格式分两种:
```
1722334954193 // 单值时:此 token 最后访问日期
1722334954193, 1200 // 双值时:此 token 最后访问日期,此 token 指定的动态 active-timeout 值
```
active-timeout 判断方式
注意:判断一个 token 是否 active-timeout 过期,与 ttl 无关,而是利 value 值计算
``` js
当前时间 - token 最后访问时间 > active-timeout
返回 true 此 token 已冻结
返回 false:此 token 未冻结
当前时间 - token 最后访问时间 > active-timeout true=token 已冻结,false=token 未冻结
```
</details>
@@ -71,9 +66,9 @@ active-timeout 判断方式:
### 1.3、SaSession
``` js
{tokenName}:{loginType}:session:{loginId} // Account-Session
{tokenName}:{loginType}:token-session:{loginId} // Token-Session
{tokenName}:custom:session:{sessionId} // Custom-Session
{tokenName}:{loginType}:session:{loginId} --> {SaSession 对象} // Account-Session
{tokenName}:{loginType}:token-session:{loginId} --> {SaSession 对象} // Token-Session
{tokenName}:custom:session:{sessionId} --> {SaSession 对象} // Custom-Session
```
<details>
@@ -139,14 +134,14 @@ value 格式
### 1.4、二级认证
``` js
{tokenName}:{loginType}:safe:{service}:{tokenValue}
{tokenName}:{loginType}:safe:{service}:{tokenValue} --> SAFE_AUTH_SAVE_VALUE
```
value 为常亮值:`SAFE_AUTH_SAVE_VALUE`
### 1.5、账号服务封禁
``` js
{tokenName}:{loginType}:disable:{service}:{loginId}
{tokenName}:{loginType}:disable:{service}:{loginId} --> {level}
```
value 为封禁等级,int类型
@@ -157,19 +152,19 @@ SaApplication 全局变量
{tokenName}:var:{变量名}
```
本次请求新创建 token,存储 key
本次请求新创建 token在 SaStorage 存储 key
``` js
JUST_CREATED_
JUST_CREATED_ --> {token}
```
本次请求新创建 token,存储 key (无前缀方式)
本次请求新创建 token在 SaStorage 存储 key (无前缀方式)
``` js
JUST_CREATED_NOT_PREFIX_
JUST_CREATED_NOT_PREFIX_ --> {token}
```
临时身份切换,使用的key
``` js
SWITCH_TO_SAVE_KEY_{loginType}
SWITCH_TO_SAVE_KEY_{loginType} --> {loginId}
```
@@ -177,23 +172,19 @@ SWITCH_TO_SAVE_KEY_{loginType}
### 2.1、ticket -> loginId 映射
``` js
{tokenName}:ticket:{ticket}
// ttl = 此 ticket 有效期,下同理
{tokenName}:ticket:{ticket} --> {loginId}
```
值为 loginId
### 2.2、ticket -> client 映射
``` js
{tokenName}:ticket-client:{ticket}
{tokenName}:ticket-client:{ticket} --> {client}
```
值为 client
### 2.3、loginId -> ticket 映射(loginId 反查 ticket
### 2.3、loginId -> ticket 映射(client + loginId 反查 ticket
``` js
{tokenName}:id-ticket:{id}
{tokenName}:ticket-index:{client}:{loginId} --> {ticket}
```
值为 ticket
@@ -201,21 +192,21 @@ SWITCH_TO_SAVE_KEY_{loginType}
### 3.1、Code 授权码
``` js
{tokenName}:oauth2:code:{code}
{tokenName}:oauth2:code:{code} --> {CodeModel 对象}
```
<details>
<summary>详细</summary>
值为 CodeModel
value 示例:
``` js
{
"@class": "cn.dev33.satoken.oauth2.model.CodeModel", // java class 信息
"code": "AbRVp2HrgyklE0BXYWszskGJWAGY7xhGu6Zaco4zJECzGYagCCFWj0jOlHza", // code值
"scope": "", // 所申请权限列表,多个用逗号隔开
"loginId": "10001", // 对应的loginId
"redirectUri": "", // 重定向地址
"@class": "cn.dev33.satoken.oauth2.model.CodeModel", // java class 信息
"code": "AbRVp2HrgyklE0BXYWszskGJWAGY7xhGu6Zaco4zJECzGYagCCFWj0jOlHza", // code值
"scope": "", // 所申请权限列表,多个用逗号隔开
"loginId": "10001", // 对应的loginId
"redirectUri": "", // 重定向地址
}
```
@@ -223,20 +214,20 @@ SWITCH_TO_SAVE_KEY_{loginType}
clientId + loginId 反查 code
``` js
{tokenName}:oauth2:code-index:{clientId}:{loginId}
{tokenName}:oauth2:code-index:{clientId}:{loginId} --> {code 值}
```
### 3.2、Access-Token 资源令牌
``` js
{tokenName}:oauth2:access-token:{accessToken}
{tokenName}:oauth2:access-token:{accessToken} --> {AccessTokenModel 对象}
```
<details>
<summary>详细</summary>
值为 AccessTokenModel
value 示例:
``` js
{
@@ -277,19 +268,19 @@ clientId + loginId 反查 code
clientId + loginId 反查 Access-Token
``` js
{tokenName}:oauth2:access-token-index:{clientId}:{loginId}
{tokenName}:oauth2:access-token-index:{clientId}:{loginId} --> {access_token 值}
```
### 3.3、Refresh-Token 资源令牌
``` js
{tokenName}:oauth2:refresh-token:{refreshToken}
{tokenName}:oauth2:refresh-token:{refreshToken} --> {RefreshTokenModel 对象}
```
<details>
<summary>详细</summary>
值为 RefreshTokenModel
value 示例:
``` js
{
@@ -324,19 +315,19 @@ clientId + loginId 反查 Access-Token
clientId + loginId 反查 Refresh-Token
``` js
{tokenName}:oauth2:refresh-token-index:{clientId}:{loginId}
{tokenName}:oauth2:refresh-token-index:{clientId}:{loginId} --> {refresh_token 值}
```
### 3.4、Client-Token 应用令牌
``` js
{tokenName}:oauth2:client-token:{clientToken}
{tokenName}:oauth2:client-token:{clientToken} --> {ClientTokenModel 对象}
```
<details>
<summary>详细</summary>
值为 ClientTokenModel
value 示例:
``` js
{
@@ -368,19 +359,19 @@ clientId + loginId 反查 Refresh-Token
clientId 反查 Client-Token
``` js
{tokenName}:oauth2:client-token-index:{clientId}
{tokenName}:oauth2:client-token-index:{clientId} --> {client_token 值}
```
Lower-Client-Token 次级应用令牌索引
``` js
{tokenName}:oauth2:lower-client-token-index:{clientId}
{tokenName}:oauth2:lower-client-token-index:{clientId} --> {client_token 值}
```
### 3.5、用户授权记录
``` js
{tokenName}:oauth2:grant-scope:{clientId}:{loginId}
{tokenName}:oauth2:grant-scope:{clientId}:{loginId} --> {scope列表}
```
值为 scope 列表,多个用逗号隔开
值为 scope 列表,多个用逗号隔开,例如:`userinfo,openid,userid`
@@ -388,21 +379,35 @@ Lower-Client-Token 次级应用令牌索引
## 4、插件
### 4.1、临时 token 会话
temp-token -> value
``` js
{tokenName}:temp-token:{service}:{token}
// namespace 默认值为 "temp-token"
{tokenName}:{namespace}:{temp-token} --> {value}
```
value 反查 temp-token
``` js
{tokenName}:raw-session:{namespace}:{value} --> {Raw#SaSession 对象}
```
- 在 SaSession 以 `__HD_TEMP_TOKEN_MAP` 为 key 存储 temp-token 索引列表。值类型为 Map。
- 其中:Map 的 key = temp-token 值,Map 的 value = 此 temp-token 到期时间戳。
### 4.2、 Same-Token
Same-Token
``` js
{tokenName}:var:same-token
{tokenName}:var:same-token --> {same-token 值}
```
Past-Same-Token
``` js
{tokenName}:var:past-same-token
{tokenName}:var:past-same-token --> {same-token 值}
```
@@ -410,9 +415,59 @@ Past-Same-Token
随机字符串
``` js
{tokenName}:sign:nonce:{32位随机字符}
// nonce 值 默认为 32位随机字符
{tokenName}:sign:nonce:{nonce} --> {nonce 值}
```
### 4.4、API Key
``` js
// namespace 默认值为 "apikey" (全小写), ttl = 此 API Key 剩余有效期
{tokenName}:{namespace}:{apikey} --> {ApiKeyModel 对象}
```
<details>
<summary>详细</summary>
key 示例:
``` js
satoken:apikey:AK-XCoJLP2E7Q9GXyPiiZWMM8Sqi6Fm0JoFC41R
```
value 示例:
``` js
{
"@class": "cn.dev33.satoken.apikey.model.ApiKeyModel", // java class 信息
"title": "test", // API Key 名称
"intro": null, // 用途介绍
"apiKey": "AK-XCoJLP2E7Q9GXyPiiZWMM8Sqi6Fm0JoFC41R", // API Key 值
"loginId": "10001", // 所属用户 id
"createTime": 1766509019137, // 创建时间戳
"expiresTime": 1769101019136, // 到期时间戳
"isValid": true, // 是否有效
"scopes": [ // 含有权限
"java.util.ArrayList",
[
"userinfo",
"user-update"
]
],
"extraData": null // 扩展数据:Map<String, Object> 类型
}
```
</details>
value 反查 API Key
``` js
{tokenName}:raw-session:{namespace}:{value} --> {Raw#SaSession 对象}
```
- 在 SaSession 以 `__HD_API_KEY_LIST` 为 key 存储 API Key 索引列表。值类型为 List (API Key 列表)。
+157
View File
@@ -0,0 +1,157 @@
# 仓库目录介绍
---
### 1、仓库根目录介绍:
``` js
── sa-token
├── sa-token-core // [核心] Sa-Token 核心模块
├── sa-token-dependencies // [依赖] Sa-Token 依赖版本信息
├── sa-token-bom // [核心] Sa-Token bom 包
├── sa-token-starter // [整合] Sa-Token 与其它框架整合
├── sa-token-plugin // [插件] Sa-Token 插件合集
├── sa-token-demo // [示例] Sa-Token 示例合集
├── sa-token-test // [测试] Sa-Token 单元测试合集
├── sa-token-doc // [文档] Sa-Token 开发文档
├── pom.xml // [依赖] 顶级pom文件
├── LICENSE // 开源协议
├── mvn clean.bat // 一键 mvn clean 核心包+所有示例包
├── mvn test.bat // 一键单元测试
├── preview-doc.bat // 一键预览开发文档
├── README.md // 仓库自述文件
```
### 2、所有目录详细介绍:
``` js
── sa-token
├── sa-token-core // [核心] Sa-Token 核心模块
├── sa-token-dependencies // [依赖] Sa-Token 依赖版本信息
├── sa-token-bom // [核心] Sa-Token bom 包
├── sa-token-starter // [整合] Sa-Token 与其它框架整合
├── sa-token-servlet // [整合] Sa-Token 整合 Servlet 容器实现类包
├── sa-token-spring-boot-starter // [整合] Sa-Token 整合 SpringBoot2 快速集成
├── sa-token-reactor-spring-boot-starter // [整合] Sa-Token 整合 SpringBoot2 Reactor 响应式编程 快速集成
├── sa-token-jakarta-servlet // [整合] Sa-Token 整合 Jakarta-Servlet 容器实现类包
├── sa-token-spring-boot3-starter // [整合] Sa-Token 整合 SpringBoot3 快速集成
├── sa-token-reactor-spring-boot3-starter // [整合] Sa-Token 整合 SpringBoot3 Reactor 响应式编程 快速集成
├── sa-token-spring-boot-autoconfig // [整合] Sa-Token 整合 SpringBoot 自动配置包
├── sa-token-solon-plugin // [整合] Sa-Token 整合 Solon 快速集成
├── sa-token-jfinal-plugin // [整合] Sa-Token 整合 JFinal 快速集成
├── sa-token-jboot-plugin // [整合] Sa-Token 整合 jboot 快速集成
├── sa-token-plugin // [插件] Sa-Token 插件合集
├── sa-token-jackson // [插件] Sa-Token 整合 Jackson (json序列化插件)
├── sa-token-fastjson // [插件] Sa-Token 整合 Fastjson (json序列化插件)
├── sa-token-fastjson2 // [插件] Sa-Token 整合 Fastjson (json序列化插件)
├── sa-token-snack3 // [插件] Sa-Token 整合 Snack3 (json序列化插件)
├── sa-token-hutool-timed-cache // [插件] Sa-Token 整合 Hutool 缓存组件 Timed-Cache(基于内存) (数据缓存插件)
├── sa-token-caffeine // [插件] Sa-Token 整合 Caffeine 缓存组件(基于内存) (数据缓存插件)
├── sa-token-thymeleaf // [插件] Sa-Token 整合 Thymeleaf (自定义标签方言)
├── sa-token-freemarker // [插件] Sa-Token 整合 Freemarker (自定义标签方言)
├── sa-token-dubbo // [插件] Sa-Token 整合 Dubbo (RPC 调用鉴权、状态传递)
├── sa-token-dubbo3 // [插件] Sa-Token 整合 Dubbo3 (RPC 调用鉴权、状态传递)
├── sa-token-temp-jwt // [插件] Sa-Token 整合 jjwt (临时 Token)
├── sa-token-sso // [插件] Sa-Token 实现 SSO 单点登录
├── sa-token-oauth2 // [插件] Sa-Token 实现 OAuth2.0 认证
├── sa-token-apikey // [插件] Sa-Token 实现 API Key 认证
├── sa-token-sign // [插件] Sa-Token 实现 API 参数签名
├── sa-token-redisson // [插件] Sa-Token 整合 Redisson (数据缓存插件)
├── sa-token-redisx // [插件] Sa-Token 整合 Redisx (数据缓存插件)
├── sa-token-serializer-features // [插件] Sa-Token 序列化实现扩展
├── sa-token-redis-template // [插件] Sa-Token 整合 RedisTemplate (数据缓存插件)
├── sa-token-redis-template-jdk-serializer // [插件] Sa-Token 整合 RedisTemplate - 使用 jdk 序列化算法 (数据缓存插件)
├── sa-token-redis-jackson // [插件] Sa-Token 整合 RedisTemplate - 使用 Jackson 序列化算法 (数据缓存插件)
├── sa-token-alone-redis // [插件] Sa-Token 独立 Redis 插件,实现 [ 权限缓存与业务缓存分离 ]
├── sa-token-spring-aop // [插件] Sa-Token 整合 SpringAOP 注解鉴权
├── sa-token-spring-el // [插件] Sa-Token 实现 SpringEL 表达式注解鉴权
├── sa-token-grpc // [插件] Sa-Token 整合 gRPC (RPC 调用鉴权、状态传递)
├── sa-token-quick-login // [插件] Sa-Token 快速注入登录页插件
├── sa-token-redisson-spring-boot-starter // [插件] Sa-Token 整合 Redisson - SpringBoot 自动配置包 (数据缓存插件)
├── sa-token-forest // [插件] Sa-Token 整合 Foresthttp 请求处理器
├── sa-token-okhttps // [插件] Sa-Token 整合 OkHttpshttp 请求处理器
├── sa-token-demo // [示例] Sa-Token 示例合集
├── sa-token-demo-alone-redis // [示例] Sa-Token 集成 alone-redis 模块
├── sa-token-demo-alone-redis-cluster // [示例] Sa-Token 集成 alone-redis 模块、集群模式
├── sa-token-demo-apikey // [示例] Sa-Token API Key 模块示例
├── sa-token-demo-async // [示例] Sa-Token 异步场景示例
├── sa-token-demo-beetl // [示例] Sa-Token 集成 beetl 示例
├── sa-token-demo-bom-import // [示例] Sa-Token bom 包导入示例
├── sa-token-demo-case // [示例] Sa-Token 各模块示例
├── sa-token-demo-device-lock // [示例] Sa-Token 设备锁登录示例 - 后端
├── sa-token-demo-device-lock-h5 // [示例] Sa-Token 设备锁登录示例 - 前端
├── sa-token-demo-dubbo // [示例] Sa-Token 集成 dubbo
├── sa-token-demo-dubbo-consumer // [示例] Sa-Token 集成 dubbo 鉴权,消费端(调用端)
├── sa-token-demo-dubbo-provider // [示例] Sa-Token 集成 dubbo 鉴权,生产端(被调用端)
├── sa-token-demo-dubbo3-consumer // [示例] Sa-Token 集成 dubbo3 鉴权,消费端(调用端)
├── sa-token-demo-dubbo3-provider // [示例] Sa-Token 集成 dubbo3 鉴权,生产端(被调用端)
├── sa-token-demo-freemarker // [示例] Sa-Token 集成 Freemarker 标签方言
├── sa-token-demo-grpc // [示例] Sa-Token 集成 grpc 鉴权
├── client // [示例] Sa-Token 集成 grpc 鉴权,client 端
├── server // [示例] Sa-Token 集成 grpc 鉴权,server 端
├── sa-token-demo-hutool-timed-cache // [示例] Sa-Token 集成 hutool timed-cache
├── sa-token-demo-caffeine // [示例] Sa-Token 集成 Caffeine
├── sa-token-demo-jwt // [示例] Sa-Token 集成 jwt 登录认证
├── sa-token-demo-oauth2 // [示例] Sa-Token 集成 OAuth2.0
├── sa-token-demo-oauth2-client // [示例] Sa-Token 集成 OAuth2.0 (客户端)
├── sa-token-demo-oauth2-client-h5 // [示例] Sa-Token OAuth2 前端测试页
├── sa-token-demo-oauth2-server // [示例] Sa-Token 集成 OAuth2.0 (服务端)
├── sa-token-demo-oauth2-server-h5 // [示例] Sa-Token 集成 OAuth2.0 (服务端 - 前后台分离示例)
├── sa-token-demo-quick-login // [示例] Sa-Token 集成 quick-login 模块
├── sa-token-demo-quick-login-sb3 // [示例] Sa-Token 集成 quick-login 模块 (SpringBoot3)
├── sa-token-demo-remember-me // [示例] Sa-Token 实现 [ 记住我 ] 模式
├── page_project // [示例] Sa-Token 实现 [ 记住我 ] 模式、前端页面
├── sa-token-demo-remember-me-server // [示例] Sa-Token 实现 [ 记住我 ] 模式、后端接口
├── sa-token-demo-solon // [示例] Sa-Token 集成 Solon
├── sa-token-demo-solon-reisson // [示例] Sa-Token 集成 Solon、Reisson
├── sa-token-demo-springboot // [示例] Sa-Token 整合 SpringBoot
├── sa-token-demo-springboot3-redis // [示例] Sa-Token 整合 SpringBoot3 整合 Redis
├── sa-token-demo-springboot-low-version // [示例] Sa-Token 整合 SpringBoot2 低版本
├── sa-token-demo-springboot-redis // [示例] Sa-Token 整合 SpringBoot 整合 Redis
├── sa-token-demo-springboot-redisson // [示例] Sa-Token 整合 SpringBoot 整合 redisson
├── sa-token-demo-sse // [示例] 在 SSE 中使用 Sa-Token
├── sa-token-demo-ssm // [示例] 在 SSM 中使用 Sa-Token
├── sa-token-demo-sso // [示例] Sa-Token 集成 SSO 单点登录
├── sa-token-demo-sso-server // [示例] Sa-Token 集成 SSO单点登录-Server认证中心
├── sa-token-demo-sso1-client // [示例] Sa-Token 集成 SSO单点登录-模式一 应用端 (同域、同Redis)
├── sa-token-demo-sso2-client // [示例] Sa-Token 集成 SSO单点登录-模式二 应用端 (跨域、同Redis)
├── sa-token-demo-sso3-client // [示例] Sa-Token 集成 SSO单点登录-模式三 应用端 (跨域、跨Redis)
├── sa-token-demo-sso3-client-nosdk // [示例] Sa-Token 集成 SSO单点登录-模式三 应用端 (不使用sdk,纯手动对接)
├── sa-token-demo-sso3-client-resdk // [示例] Sa-Token 集成 SSO单点登录-模式三 应用端 (ReSdk 模式,重写部分方法对接任意技术栈)
├── sa-token-demo-sso3-client-anon // [示例] Sa-Token 集成 SSO单点登录-模式三 应用端 (匿名应用接入示例)
├── sa-token-demo-sso-server-h5 // [示例] Sa-Token 集成 SSO单点登录-Server认证中心 (前后端分离)
├── sa-token-demo-sso-client-h5 // [示例] Sa-Token 集成 SSO单点登录-client应用端 (前后端分离-原生h5 版本)
├── sa-token-demo-sso-server-vue2 // [示例] Sa-Token 集成 SSO单点登录-client应用端 (前后端分离-Vue2 版本)
├── sa-token-demo-sso-client-vue3 // [示例] Sa-Token 集成 SSO单点登录-client应用端 (前后端分离-Vue3 版本)
├── sa-token-demo-sso-for-solon // [示例] Sa-Token 集成 SSO 单点登录(Solon 版)
├── sa-token-demo-sso-server-solon // [示例] Sa-Token 集成 SSO单点登录-Server认证中心
├── sa-token-demo-sso1-client-solon // [示例] Sa-Token 集成 SSO单点登录-模式一 应用端 (同域、同Redis)
├── sa-token-demo-sso2-client-solon // [示例] Sa-Token 集成 SSO单点登录-模式二 应用端 (跨域、同Redis)
├── sa-token-demo-sso3-client-solon // [示例] Sa-Token 集成 SSO单点登录-模式三 应用端 (跨域、跨Redis)
├── sa-token-demo-test // [示例] Sa-Token 整合测试项目
├── sa-token-demo-thymeleaf // [示例] Sa-Token 集成 Thymeleaf 标签方言
├── sa-token-demo-webflux // [示例] Sa-Token 整合 WebFlux
├── sa-token-demo-webflux-springboot3 // [示例] Sa-Token 整合 WebFlux SpringBoot3
├── sa-token-demo-websocket // [示例] Sa-Token 集成 Web-Socket 鉴权示例
├── sa-token-demo-websocket-spring // [示例] Sa-Token 集成 Web-SocketSpring封装版) 鉴权示例
├── pom.xml // 示例 pom 文件,用于帮助在 idea 中一键导入所有 demo
├── sa-token-test // [测试] Sa-Token 单元测试合集
├── sa-token-springboot-test // [测试] Sa-Token SpringBoot 整合测试
├── sa-token-jwt-test // [测试] Sa-Token jwt 整合测试
├── sa-token-doc // [文档] Sa-Token 开发文档
├── pom.xml // [依赖] 顶级pom文件
├── LICENSE // 开源协议
├── mvn clean.bat // 一键 mvn clean 核心包+所有示例包
├── mvn test.bat // 一键单元测试
├── preview-doc.bat // 一键预览开发文档
├── README.md // 仓库自述文件
```
其它:
- [sa-token-demo-cross](https://gitee.com/sa-tokens/sa-token-demo-cross)Sa-Token 处理跨域示例。
- [sa-token-three-plugin](https://gitee.com/sa-tokens/sa-token-three-plugin)Sa-Token 第三方插件合集。
- [sa-token-study](https://gitee.com/sa-tokens/sa-token-study)Sa-Token 涉及知识点学习。
+134 -191
View File
@@ -4,9 +4,11 @@
<meta charset="UTF-8">
<title>Sa-Token</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Sa-Token是一个java权限认证框架,功能全面,上手简单,登录认证、权限认证、Session会话、踢人下线、账号封禁、集成Redis、前后端分离、分布式会话、微服务网关鉴权、单点登录、OAuth2.0、临时Token验证、记住我模式、模拟他人账号、临时身份切换、多账号体系、注解式鉴权、路由拦截式鉴权、花式token、自动续签、同端互斥登录、会话治理、密码加密、jwt集成、Spring集成、WebFlux集成...,有了sa-token,你所有的权限认证问题,都不再是问题">
<meta name="description"
content="Sa-Token是一个java权限认证框架,功能全面,上手简单,登录认证、权限认证、Session会话、踢人下线、账号封禁、集成Redis、前后端分离、分布式会话、微服务网关鉴权、单点登录、OAuth2.0、临时Token验证、记住我模式、模拟他人账号、临时身份切换、多账号体系、注解式鉴权、路由拦截式鉴权、花式token、自动续签、同端互斥登录、会话治理、密码加密、jwt集成、Spring集成、WebFlux集成...,有了sa-token,你所有的权限认证问题,都不再是问题">
<meta name="keywords" content="sa-token,sa-token框架,sa-token文档,java权限认证">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="shortcut icon" type="image/x-icon" href="logo.png">
<link rel="stylesheet" href="./static/doc.css">
<link rel="stylesheet" href="./static/vue.css">
@@ -23,7 +25,7 @@
</a>
</div>
<nav class="nav-right">
<div class="sear-box p-none" tabindex="-1" >
<div class="sear-box p-none" tabindex="-1">
<!-- 加载中…… -->
</div>
<select class="select-version p-none" onchange="location.href=this.value">
@@ -86,16 +88,16 @@
<div style="height: 5px;"></div>
<span style="background-color: #FFFFFF;"></span>
<span style="background-color: #f5f5f5;"></span>
<span style="background-color: #f5e5f5;"></span>
<span style="background-color: #F1FAFA;"></span>
<span style="background-color: #f5f5d5;"></span>
<span style="background-color: #d5f5f5;"></span>
<span style="background-color: #f5e5f5;"></span>
<span style="background-color: #E8E8FF;"></span>
<span style="background-color: #f0f9eb;"></span>
<span style="background-color: #d5f5f5;"></span>
<span style="background-color: #ebe5dd;"></span>
<span style="background-color: #e8f4ff;"></span>
<!-- <span style="background-color: #F0DAD2;"></span> -->
<!-- <span style="background-color: #f5d5d5;"></span> -->
<!-- <span style="background-color: #FFFFE0;"></span> -->
@@ -106,7 +108,6 @@
</div>
<a class="wzi" href="index.html">首页</a>
<a class="wzi" href="doc.html">文档</a>
<a class="p-none wzi" href="#/more/link">案例</a>
<div class="zk-box">
<a class="wzi" href="javascript:;">
<span>视频 </span>
@@ -114,24 +115,28 @@
</a>
<div class="zk-context">
<div>
<a href="https://www.bilibili.com/video/BV1fsUVBWEyH/" target="_blank">朱老师的小课堂(7集)</a>
<a href="https://www.bilibili.com/video/BV1NF1FBpEe6/" target="_blank">王清江唷 SSO篇(29集)</a>
<a href="https://www.bilibili.com/video/BV1uZUpYVEst/" target="_blank">fox说技术(7集)</a>
<a href="https://www.bilibili.com/video/BV1eFtRezERp?p=87" target="_blank">架构驿站(11集)</a>
<a href="https://www.bilibili.com/video/BV1Zt421u7gk/" target="_blank">王清江唷(99集)</a>
<a href="https://www.bilibili.com/video/BV1kG411o7Ms/" target="_blank">筑梦信仰-joy20集)</a>
<a href="https://www.bilibili.com/video/BV11u4y197JL/" target="_blank">达达-Java26集)</a>
<a href="https://space.bilibili.com/473679148/video" target="_blank">晒太阳的盐(22集)</a>
<a href="https://space.bilibili.com/473679148/video" target="_blank">晒太阳的盐(22集)</a>
<div class="zk-fengexian"></div>
<a href="javascript: layer.alert('如您有 Sa-Token 相关课程录制,请联系官网文档右侧 < sa-token 小助手 > 进行提交');">
<a href="doc.html#/more/content-cooperation">[ + 课程提交 ]</a>
<!-- <a href="javascript: layer.alert('如您有 Sa-Token 相关课程录制,请联系官网文档右侧 < sa-token 小助手 > 进行提交');">
[ + 课程提交 ]
</a>
</a> -->
</div>
</div>
</div>
<a class="p-none wzi" href="#/more/demand-commit">需求提交</a>
<!-- <a class="p-none wzi" href="#/more/blog">博客</a> -->
<a class="p-none wzi" href="#/more/link">案例</a>
<a class="p-none wzi" href="#/more/join-group">加入讨论群</a>
<a class="p-none wzi" href="#/more/demand-commit">需求提交</a>
<a class="p-none wzi" href="#/more/blog">博客</a>
<a class="p-none wzi" href="#/more/sa-token-donate">赞助</a>
<a class="p-none wzi" href="#/sso/sso-pro">🔥 SSO商业版</a>
<a class="p-none wzi" href="#/pro/st_doc_top">🔥 SSO/OAuth2 商业版</a>
<div class="zk-box">
<a class="wzi" href="javascript:;">
<span>相关资源 </span>
@@ -161,58 +166,46 @@
<h1 class="logo-text">Sa-Token</h1>
</div>
</a> -->
<div class="main-box">
<!-- 内容区 -->
<div id="app">加载中...</div>
<!-- 右边盒子 -->
<div class="doc-right-bj-box">
<div class="doc-right-bj-box-title">目录</div>
<div class="doc-right-more-item">
<!-- ad盒子 -->
<div class="ad-box">
<div class="ad-title">
<span class="ad-tips">推广信息:</span>
<span class="ad-tips ad-close">关闭</span>
</div>
<!-- ssp -->
<div class="top-ad-box" style="margin-bottom: 12px;">
<a href="http://sa-pro.dev33.cn?way=st_r" target="_blank">
<img src="https://oss.dev33.cn/sa-token/ad/sa-sso-pro-s3.png" />
<a href="https://sa-pro.yun94.cn?way=st_r" target="_blank">
<div class="mad-bg-box">
<div class="mad-context-box">
<img class="mad-img" src="/big-file/contact/sspx-ad-11.png" />
<span class="mad-text">
<b>轻松搭建:SSO 单点登录、OAuth2.0 统一认证、API Key、用户数据同步。全源码交付、可二开。</b>
</span>
</div>
</div>
</a>
</div>
<!-- 万维广告div -->
<div class="wwads-cn wwads-horizontal" data-id="88" style="min-height: 0px; border: 1px #eee solid; margin-bottom: 12px;"></div>
<!-- ssp -->
<div class="top-ad-box top-ad-box2" style="margin-bottom: 12px;">
<a href="https://www.codefather.cn/vip?shareCode=qin883" target="_blank">
<img src="https://oss.dev33.cn/sa-token/ad/bianchengdaohang.png" style="height: 26px;" />
</a>
<a href="https://www.mianshiya.com/?shareCode=skmky7" target="_blank">
<img src="https://oss.dev33.cn/sa-token/ad/mianshiya.png" style="height: 26px;" />
</a>
<!-- <a href="http://sa-pro.dev33.cn?from=satop" target="_blank">
<img src="https://oss.dev33.cn/sa-token/ad/sa-sso-pro-s3.png" />
</a> -->
<!--
<a href="http://sa-pro.dev33.cn?from=satop" target="_blank">
<img src="https://oss.dev33.cn/sa-token/ad/sa-sso-pro-s3.png" />
</a>
<a href="http://sa-pro.dev33.cn?from=satop" target="_blank">
<img src="https://oss.dev33.cn/sa-token/ad/sa-sso-pro-s3.png" />
</a> -->
</div>
<div class="wwads-cn wwads-horizontal" data-id="88"
style="min-height: 0px; border: 1px #eee solid; margin-bottom: 12px;"></div>
</div>
<!-- help 按钮 -->
<div class="help-btn">❤️ 技术求助</div>
<!-- <div class="help-btn">❤️ 技术求助</div> -->
<!-- ew-wa -->
<div class="ew-wa">
<p>
@@ -225,16 +218,16 @@
<p>如果 Sa-Token 帮助到了你,希望你可以向同事、朋友推荐了解本框架,这对我们非常重要,感谢支持! <!-- 🤗 --> </p>
<p>加油,工程师!</p>
</div>
</div>
</div>
</div>
<!-- 万维广告div -->
<!-- <div style="position: fixed; right: 0; bottom: 0; z-index: 10000; border: 0px #aaa solid;">
<div class="wwads-cn wwads-vertical" data-id="88" style="max-width:150px"></div>
</div> -->
<!-- 小助手div -->
<!-- <div class="p-none help-btn-box" style="position: fixed; right: 40px; bottom: 330px; z-index: 10000; border: 0px #aaa solid;">
<div class="help-tips" style="position: relative; left: -30px; top: -10px;"></div>
@@ -242,28 +235,34 @@
<span style="font-size: 18px; color: #FFF; line-height: 60px;">Help</span>
</div>
</div> -->
<!-- UI逐渐显现 -->
<style type="text/css">
body{opacity: 0.01; transition: opacity 0.5s; background-color: #FFF;}
body {
opacity: 0.01;
transition: opacity 0.5s;
background-color: #FFF;
}
</style>
<script type="text/javascript">
setTimeout(function() {
document.body.style.opacity = 1;
}, 1);
</script>
<!-- jqeury -->
<script src="static/jquery.min.js"></script>
<script src="static/layer-v3.1.1/layer.js"></script>
<!-- -->
<script src="./static/docsify-plugin.js?v=7"></script>
<script src="./static/is-star-plugin.js?v=7"></script>
<script src="./static/is-fill-in-wj-plugin.js?v=7"></script>
<!-- <script src="./static/is-fill-in-wj-plugin.js?v=7"></script> -->
<link rel="stylesheet" href="./static/custom-docsify-plugins/doc-lock-plugin.css">
<!-- <script src="./static/custom-docsify-plugins/doc-lock-plugin.js"></script> -->
<!-- <script src="./static/custom-docsify-plugins/doc-lock-by-gzh-plugin.js"></script> -->
<script>
var saTokenTopVersion = '1.44.0'; // Sa-Token最新版本
var saTokenTopVersion = '1.44.0'; // Sa-Token最新版本
var name = '<img style="width: 60px; height: 60px; vertical-align: middle;" src="logo.png" alt="logo" /> ';
name += '<b style="font-size: 28px; vertical-align: middle;">Sa-Token</b> <sub>v' + saTokenTopVersion + '</sub>';
window.$docsify = {
@@ -290,11 +289,11 @@
},
// tab选项卡
tabs: {
persist : true, // 是否在刷新页面时重置选项卡
sync : false, // 页面上的多个tab是否同步切换
theme : 'classic', // 主题:'classic', 'material', false
tabComments: true, // 用注释来标注选项卡标题,例如:<!-- tab:SpringBoot -->
tabHeadings: true // 用标题+粗体来定制选项卡
persist: true, // 是否在刷新页面时重置选项卡
sync: false, // 页面上的多个tab是否同步切换
theme: 'classic', // 主题:'classic', 'material', false
tabComments: true, // 用注释来标注选项卡标题,例如:<!-- tab:SpringBoot -->
tabHeadings: true // 用标题+粗体来定制选项卡
},
// 阅读进度
progress: {
@@ -304,7 +303,7 @@
},
// 信息提示框
'flexible-alerts': {
style: 'flat', // 默认风格 callout=浅色,flat=深色
style: 'flat', // 默认风格 callout=浅色,flat=深色
note: {
label: {}
},
@@ -319,24 +318,29 @@
},
},
// 自定义插件
plugins: [myDocsifyPlugin, window.isStarPlugin, window.isFillInWjPlugin],
plugins: [
myDocsifyPlugin, // 基础插件
// window.isStarPlugin, // 是否 star
// window.isFillInWjPlugin // 问卷填写
// window.docLockPlugin, // 章节锁
],
}
</script>
<script src="static/docsify.min.js"></script>
<script src="static/docsify-copy-code.min.js"></script>
<!-- 语言合集:https://cdn.jsdelivr.net/npm/prismjs@1/components/ -->
<script src="static/prism/prism-java.min.js"></script>
<script src="static/prism/prism-gradle.min.js"></script>
<script src="static/prism/prism-yaml.min.js"></script>
<script src="static/prism/prism-properties.min.js"></script>
<!-- 文档阅读进度条 -->
<!-- <script src="static/docsify-plugins/progress.update.js"></script> -->
<!-- 右上角次级导航栏 -->
<script src="static/docsify-plugins/sub-nav-draw.js"></script>
<!-- 搜索框 -->
<script src="static/search.min.js"></script>
<!-- 多 tab 切换 -->
@@ -345,7 +349,7 @@
<script src="static/zoom-image.min.js"></script>
<!-- 好看的提示框 -->
<script src="static/docsify-plugins/docsify-plugin-flexible-alerts.min-1.1.1.js"></script>
<!-- docsify 里一个 md 引入另一个 md-->
<script src="static/docsify-plugins/docsify-betterembed-1.1.1.js"></script>
@@ -353,41 +357,41 @@
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify-sidebar-collapse/dist/sidebar.min.css" />
<script src="https://cdn.jsdelivr.net/npm/docsify-sidebar-collapse/dist/docsify-sidebar-collapse.min.js"></script> -->
<!-- 渲染赞助数据 -->
<script src="static/donate/donate-list.js"></script>
<script src="static/donate/donate-fun.js"></script>
<!-- 广告盒子 -->
<script>
(function(){
(function() {
// 功能6:标题下面的广告
if($(window).width() >= 800) {
if ($(window).width() >= 800) {
// 如果一天内用户点击过关闭广告,则不再展现
let allowJg = 1000 * 60 * 60 * 24 * 1;
// allowJg = 1000 * 10;
try{
try {
const closeAdTime = localStorage.closeAdTime;
if(closeAdTime) {
if (closeAdTime) {
// 点击广告关闭的时间,和当前时间的差距
const closeAdJg = new Date().getTime() - parseInt(closeAdTime);
// 差距小于1天,不再展示
if(closeAdJg < allowJg) {
if (closeAdJg < allowJg) {
console.log('not show ad ...');
$('.ad-box').remove();
return;
}
}
}catch(e){
} catch (e) {
console.error(e);
}
// 添加关闭事件
$('.ad-close').click(function(){
$('.ad-close').click(function() {
console.log('关闭广告');
// $('.top-ad-box').slideUp(); // 折叠收起
layer.confirm('关闭后,一天内不再展现此信息', function(){
layer.confirm('关闭后,一天内不再展现此信息', function() {
$(".ad-box").fadeOut(1000); // 淡出效果
layer.msg('关闭成功');
localStorage.closeAdTime = new Date().getTime();
@@ -396,7 +400,7 @@
}
})();
</script>
<!-- 搜索引擎自动提交 -->
<script>
(function() {
@@ -411,32 +415,33 @@
s.parentNode.insertBefore(bp, s);
})();
</script>
<!-- 万维广告 -->
<script data-mode="hash" type="text/javascript" src="https://cdn.wwads.cn/js/makemoney.js" async></script>
<!-- 百度统计 -->
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?35ad501304eae758ac6139a22a9830f5";
var s = document.getElementsByTagName("script")[0];
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/javascript">
// 预览版提示
if(location.host === 'rc.sa-token.cc') {
const newTips = '<b>当前文档为RC预览版文档,仅做学习测试使用,正式项目请使用正式版:<a href="https://sa-token.cc/" target="_blank">https://sa-token.cc/</a></b>';
if (location.host === 'rc.sa-token.cc') {
const newTips =
'<b>当前文档为RC预览版文档,仅做学习测试使用,正式项目请使用正式版:<a href="https://sa-token.cc/" target="_blank">https://sa-token.cc/</a></b>';
layer.alert(newTips);
}
</script>
<!-- 小助手提示 -->
<script>
$('.help-btn').click(function(){
$('.help-btn').click(function() {
var str = `
<div class="xiaozhushou-intro">
<h2>报错了?搞不懂?别急、莫慌</h2>
@@ -455,13 +460,13 @@
<p style="margin-top: 30px;">打开方式:</p>
<p>1、如果你是使用 PC 端微信,请点此链接:<a href="https://work.weixin.qq.com/kfid/kfcdd45c432fee9655f" target="_blank">https://work.weixin.qq.com/kfid/kfcdd45c432fee9655f</a></p>
<p>2、如果你是使用手机端微信,请扫码:</p>
<p><img src="https://oss.dev33.cn/sa-token/sa-token-xiaozhushou.jpg" width="200px"></p>
<p><img src="/big-file/contact/sa-token-xiaozhushou.jpg" width="200px"></p>
<p>如果您的问题已解决,我们希望您能够花费一点时间将解决方案发布在:<a href="https://gitee.com/dromara/sa-token/issues/I9I9CY" target="_blank">踩坑记录征集</a>,帮助以后遇到同样问题的开发者快速排查,感激不尽!🌹🌹🌹</p>
</div>
`;
layer.alert(str, {
title: '技术求助',
area: '680px',
title: '技术求助',
area: '680px',
offset: '7%',
})
})
@@ -478,120 +483,58 @@
// console.error(e);
// }
// }, 500)
</script>
<!-- 修改背景颜色 -->
<script>
// 绑定修改背景色的按钮事件
$('.theme-box span').click(function() {
let bgColor = this.style.backgroundColor;
setBg(bgColor);
localStorage.setItem('bg-color-value', bgColor)
})
// 读取上次记录
let bgColor = localStorage.getItem('bg-color-value');
if(bgColor) {
setBg(bgColor);
}
// 设置背景颜色
function setBg(bgColor) {
console.log('---- 背景颜色设定为:', bgColor);
// -------- 设置 body 背景
document.body.style.backgroundColor = bgColor;
// -------- 设置 header 头背景
// 如果是 16 进制,转 rgba
if(bgColor.indexOf('#') == 0) {
bgColor = hexToRgba(bgColor, 0.97);
}
// 如果是 rgb,转 rgba
else if(bgColor.match(/\,/g).length == 2) {
bgColor = bgColor.replace(')', ' ,0.97)');
}
document.querySelector('.doc-header').style.backgroundColor = bgColor;
}
// 16进制 转 rgba
function hexToRgba(str, a){
a = a || 1;
var reg = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/
if(!reg.test(str)){return;}
let newStr = (str.toLowerCase()).replace(/\#/g,'')
let len = newStr.length;
if(len == 3){
let t = ''
for(var i=0;i<len;i++){
t += newStr.slice(i,i+1).concat(newStr.slice(i,i+1))
}
newStr = t
}
let arr = []; //将字符串分隔,两个两个的分隔
for(var i =0;i<6;i=i+2){
let s = newStr.slice(i,i+2)
arr.push(parseInt("0x" + s))
}
return 'rgb(' + arr.join(",") + ', ' + a + ')';
}
</script>
<!-- 修改背景颜色 水滴波纹特效 -->
<link rel="stylesheet" href="static/water-change-theme/water-change-theme.css" />
<script src="static/water-change-theme/gsap-3.12.2.min.js"></script>
<script src="static/water-change-theme/water-change-theme.js"></script>
<!-- 赞助页的展开和收缩 -->
<script>
// 展开
function expandZanZhu(){
function expandZanZhu() {
$('.zk-btn--1').hide();
$('.zk-btn--2').show();
$('.zanzhu-box').height($('.zanzhu-box table').height());
}
// 折叠
function foldZanZhu(){
function foldZanZhu() {
$('.zanzhu-box').height(500);
$('.zk-btn--2').hide();
$('.zk-btn--1').show();
}
</script>
<!-- 赞助效果图展示 -->
<!-- 自定义滚动条颜色 -->
<!-- <style>
/* 自定义body滚动条样式 */
body::-webkit-scrollbar { width: 8px; }
/* 滚动条颜色 */
body::-webkit-scrollbar-thumb { background-color: green; border-radius: 3px; }
/* 滚动条上面的和下面的颜色 */
body::-webkit-scrollbar-track {
background: linear-gradient(to bottom,
#42B983 0%,
#42B983 var(--scroll-progress, 0%),
#FCFCFC var(--scroll-progress, 0%),
#FCFCFC 100%);
}
</style>
<script>
function showSyzz(){
layer.photos({
photos: {
title: '',
id: new Date().getTime(),
start: 0,
data: [
{
pid: 1,
alt: 'gitee 开源项目推广,日增 star 74',
src: 'https://oss.dev33.cn/sa-token/more/syzz-xg-1.png',
},
{
pid: 2,
alt: 'gitee 开源项目推广,日增 star 247',
src: 'https://oss.dev33.cn/sa-token/more/syzz-xg-2.png',
},
{
pid: 3,
alt: '某互联网公司线下活动,报名邀请',
src: 'https://oss.dev33.cn/sa-token/more/syzz-xg-3.png',
},
{
pid: 4,
alt: 'gitee 开源项目推广,日增 star 70',
src: 'https://oss.dev33.cn/sa-token/more/syzz-xg-4.png',
}
]
}
,anim: 5 //0-6的选择,指定弹出图片动画类型,默认随机(请注意,3.0之前的版本用shift参数)
});
}
</script>
// 动态更新滚动条颜色
window.addEventListener('scroll', function() {
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
const scrollProgress = (scrollTop / scrollHeight) * 100;
document.body.style.setProperty('--scroll-progress', scrollProgress + '%');
});
// 初始化滚动条状态
window.dispatchEvent(new Event('scroll'));
</script> -->
</body>
</html>
</html>
+3 -3
View File
@@ -4,12 +4,12 @@
### Sa-Token 功能结构图:
![sa-token-rz](https://oss.dev33.cn/sa-token/art/sa-token-js4.png 's-w')
<img class="s-w" src="/big-file/doc/fun/sa-token-js4--2.png" alt="sa-token-rz" />
### Sa-Token 认证流程图:
![sa-token-rz](https://oss.dev33.cn/sa-token/art/sa-token-rz2.png 's-w')
<img class="s-w" src="/big-file/doc/fun/sa-token-rz2.png" alt="sa-token-rz" />
<!-- ![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) 绘制完成)
PS:鼠标右键选择 **`[在新窗口打开图片]`** 即可高清模式查看图片。
@@ -713,13 +713,13 @@ public class StpInterfaceImpl implements StpInterface {
// 加载角色信息
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
public List<String> getRoleList(Object loginId, String loginType) {
return Arrays.asList("admin", "super-admin", "ceo");
}
// 加载权限信息
@Override
public List<String> getRoleList(Object loginId, String loginType) {
public List<String> getPermissionList(Object loginId, String loginType) {
return Arrays.asList("user:add", "user:delete", "user:update");
}
+5 -1
View File
@@ -2,4 +2,8 @@
<!-- 参考:[https://blog.csdn.net/shengzhang_/article/details/119928794](https://blog.csdn.net/shengzhang_/article/details/119928794) -->
参考[https://juejin.cn/post/7491603065944129590](https://juejin.cn/post/7491603065944129590)
参考1: [https://juejin.cn/post/7491603065944129590](https://juejin.cn/post/7491603065944129590)
参考2: [https://mp.weixin.qq.com/s/tbqjCKrTMj-l1lZbeyu81g](https://mp.weixin.qq.com/s/tbqjCKrTMj-l1lZbeyu81g)
参考3: [https://mp.weixin.qq.com/s/8aziIhqGCb_qsr8kLiqzmg](https://mp.weixin.qq.com/s/8aziIhqGCb_qsr8kLiqzmg)
+3 -3
View File
@@ -16,11 +16,11 @@ public String test() {
从浏览器访问此接口,我们可以看到:
![test-curr-domain.png](https://oss.dev33.cn/sa-token/doc/test-curr-domain.png 's-w-sh')
<img class="s-w-sh" src="/big-file/doc/fun/test-curr-domain.png" alt="test-curr-domain.png" />
此 API 在本地开发时一般可以正常工作,然而如果我们在部署时使用 Nginx 做了一层反向代理后,其最终结果可能会和我们预想的有一点偏差:
![test-curr-domain-fxdl.png](https://oss.dev33.cn/sa-token/doc/test-curr-domain-fxdl.png 's-w-sh')
<img class="s-w-sh" src="/big-file/doc/fun/test-curr-domain-fxdl.png" alt="test-curr-domain-fxdl.png" />
不仅是 Nginx,所有包含路由转发的地方都有可能导致上述丢失 uri 的现象,解决方案也很简单,既然程序无法自动识别,我们改成手动获取即可,Sa-Token 提供两个方案:
@@ -29,7 +29,7 @@ public String test() {
##### 1、首先在 Nginx 代理转发的地方增加参数
![nginx-add-header.png](https://oss.dev33.cn/sa-token/doc/nginx-add-header.png 's-w-sh')
<img class="s-w-sh" src="/big-file/doc/fun/nginx-add-header.png" alt="nginx-add-header.png" />
重点是这一句:`proxy_set_header Public-Network-URL http://$http_host$request_uri;`
+12 -12
View File
@@ -3,12 +3,12 @@
2. 滑动右侧页面滑块, 查看页面内容最下方, 评论区上方
3. 找到这一行文字
![在线编辑提示](https://oss.dev33.cn/sa-token/doc/git-pr/online_1.png)
<img src="/big-file/doc/fun/online_1.png" alt="在线编辑提示" />
4. 点击Gitee或GitHub按钮中的任意一个, 国内用户推荐使用 [Gitee](https://gitee.com) (请先注册登录后再往下浏览)
5. 此时会进入当前页面源码预览页面,找到下方按钮组
![按钮组](https://oss.dev33.cn/sa-token/doc/git-pr/online_2.png)
<img src="/big-file/doc/fun/online_2.png" alt="按钮组" />
6. 点击编辑按钮
7. 此时进入待修改页面的源码页面, 按照markdown格式编辑为需要的结果(Ctrl+P可查看最终效果,再次按下可恢复源码界面)
@@ -36,16 +36,16 @@ git config --list
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)
<img src="/big-file/doc/fun/code_1.png" alt="按钮组" />
3. 选择个人仓库并点击确认
4. 此时在你的个人仓库中会多了一个Sa-Token项目
5. 在新的Sa-Token项目中, 点击 ![克隆/下载](https://oss.dev33.cn/sa-token/doc/git-pr/code_2.png) 按钮, 点击弹出框里面的复制按钮
5. 在新的Sa-Token项目中, 点击 <img src="/big-file/doc/fun/code_2.png" alt="克隆/下载" /> 按钮, 点击弹出框里面的复制按钮
6. 在本地某空文件夹下右键选择: git bash here
![git bash](https://oss.dev33.cn/sa-token/doc/git-pr/code_4.png)
<img src="/big-file/doc/fun/code_4.png" alt="git bash" />
![git bash 打开后的图](https://oss.dev33.cn/sa-token/doc/git-pr/code_3.png)
<img src="/big-file/doc/fun/code_3.png" alt="git bash 打开后的图" />
14. 在里面输入如下命令, 按换行后自动下载整个项目
@@ -61,18 +61,18 @@ git clone 这里替换为复制后的链接
### 方式一
1. 在idea中打开项目进入Commit选项
![本地暂存](https://oss.dev33.cn/sa-token/doc/git-pr/code_5.png)
<img src="/big-file/doc/fun/code_5.png" alt="本地暂存" />
2. 勾选需要本地暂存的文件
3. 在同一页面的下方输入提示信息
![提示信息](https://oss.dev33.cn/sa-token/doc/git-pr/code_6.png)
<img src="/big-file/doc/fun/code_6.png" alt="提示信息" />
4. 点击Commit按钮暂存到本地, 点击Commit and Push按钮暂存之后提交到远程
### 方式二
1. 除了点击Commit and Push按钮外,还有一个地方可以提交git
![git按钮](https://oss.dev33.cn/sa-token/doc/git-pr/code_7.png)
<img src="/big-file/doc/fun/code_7.png" alt="git按钮" />
2. 位置在idea右上方的工具栏里面
3. 指向左下箭头为拉取项目,可以随时更新
@@ -82,11 +82,11 @@ git clone 这里替换为复制后的链接
1. 提交后进入Gitee个人仓库中克隆的Sa-Token项目
2. 找到下图的Pull Request按钮
![工具栏](https://oss.dev33.cn/sa-token/doc/git-pr/code_8.png)
<img src="/big-file/doc/fun/code_8.png" alt="工具栏" />
3. 点击提交, 进入如下页面
![提交信息填写页面](https://oss.dev33.cn/sa-token/doc/git-pr/code_9.png 's-width')
<img class="s-width" src="/big-file/doc/fun/code_9.png" alt="提交信息填写页面" />
4. 在这里,你可以选择要提交的分支,一般都是dev开发分支.可以填写合并信息,其他测试审查之类的可以不填写, 最后点击创建即可完成一次提交.
@@ -94,7 +94,7 @@ git clone 这里替换为复制后的链接
1. 有时候主项目更新了,之前克隆的项目代码陈旧,如何处理?
2. 在个人仓库的Sa-Token项目主页面中, 找到下图的圆圈
![更新按钮](https://oss.dev33.cn/sa-token/doc/git-pr/code_10.png)
<img src="/big-file/doc/fun/code_10.png" alt="更新按钮" />
3. 点击右侧圆圈按钮后Gitee会自动同步主项目, 这样就不用像我之前一样,删除项目又重新fork了.
+1 -1
View File
@@ -14,7 +14,7 @@
解决这个问题的关键就在于 `SaTokenContext` 接口,此接口的作用是屏蔽掉不同 Web 框架之间的差异,提供统一的调用API:
![sa-token-context](https://oss.dev33.cn/sa-token/doc/sa-token-context.svg 's-w')
<img class="s-w" src="/big-file/doc/fun/sa-token-context.svg" alt="sa-token-context" />
SaTokenContext只是一个接口,没有工作能力,这也就意味着 SaTokenContext 接口的实现是必须的。
+1 -1
View File
@@ -60,7 +60,7 @@ public static void setContext(HttpServletRequest request, HttpServletResponse re
Sa-Token 为了一套代码对接所有的 Web 框架,就在原生请求对象的基础上又封装了一层 `SaTokenContext` 相关接口,用于屏蔽掉不同 Web 框架之间的差异,提供统一的调用API:
![sa-token-context](https://oss.dev33.cn/sa-token/doc/plugin/sa-token-context-2.svg)
<img src="/big-file/doc/fun/sa-token-context-2.svg" alt="sa-token-context" />
因此,要对接不同的 Web 框架,就要针对不同的 Web 框架封装不同版本的 `SaRequest`、`SaResponse`、`SaStorage` 包装类对象。
+1 -1
View File
@@ -99,7 +99,7 @@ session.updateTimeout(1000); // 参数说明和全局有效期保持一致
**假设三个客户端登录同一账号,且配置了不共享token,那么此时的Session模型是:**
![session-model](https://oss.dev33.cn/sa-token/doc/session-model3.png 's-w')
<img class="s-w" src="/big-file/doc/fun/session-model3.png" alt="session-model" />
简而言之:
- `Account-Session` 以账号 id 为主,只要 token 指向的账号 id 一致,那么对应的Session对象就一致
+1 -1
View File
@@ -2,7 +2,7 @@
---
QQ群经常有小伙伴提问:项目需要搭建统一认证中心,是用 SSO 方便还是 OAuth2.0 方便呢?针对这个问题,我们列出两者的主要区别以供大家参考:
QQ群经常有小伙伴提问:项目需要搭建统一认证中心,是用 SSO 方便还是 OAuth2.0 方便呢?针对这个问题,我们列出两者的主要区别以供大家参考:
| 功能点 | SSO单点登录 | OAuth2.0 |
+11 -11
View File
@@ -12,17 +12,17 @@
<th>个人主页</th>
</tr>
<tr>
<td><img src="https://oss.dev33.cn/sa-token/team/avatar-xiaofengzheng.jpg" /></td>
<td><img src="/big-file/doc/team/avatar-xiaofengzheng.jpg" /></td>
<td>小风筝(作者)</td>
<td><a href="https://gitee.com/click33" target="_blank">https://gitee.com/click33</a></td>
</tr>
<tr>
<td><img src="https://oss.dev33.cn/sa-token/team/avatar-AppleOfGray.png" /></td>
<td><img src="/big-file/doc/team/avatar-AppleOfGray.png" /></td>
<td>AppleOfGray</td>
<td><a href="https://gitee.com/appleOfGray" target="_blank">https://gitee.com/appleOfGray</a></td>
</tr>
<tr>
<td><img src="https://oss.dev33.cn/sa-token/team/avatar-ly-chn.png" /></td>
<td><img src="/big-file/doc/team/avatar-ly-chn.png" /></td>
<td>ly-chn</td>
<td><a href="https://gitee.com/ly-chn" target="_blank">https://gitee.com/ly-chn</a></td>
</tr>
@@ -41,42 +41,42 @@
<th>个人主页</th>
</tr>
<tr>
<td><img src="https://oss.dev33.cn/sa-token/team/avatar-xiaofengzheng.jpg" /></td>
<td><img src="/big-file/doc/team/avatar-xiaofengzheng.jpg" /></td>
<td>刘潇</td>
<td><a href="https://gitee.com/click33" target="_blank">https://gitee.com/click33</a></td>
</tr>
<tr>
<td><img src="https://oss.dev33.cn/sa-token/team/avatar-AppleOfGray.png" /></td>
<td><img src="/big-file/doc/team/avatar-AppleOfGray.png" /></td>
<td>AppleOfGray</td>
<td><a href="https://gitee.com/appleOfGray" target="_blank">https://gitee.com/appleOfGray</a></td>
</tr>
<tr>
<td><img src="https://oss.dev33.cn/sa-token/team/avatar-ly-chn.png" /></td>
<td><img src="/big-file/doc/team/avatar-ly-chn.png" /></td>
<td>ly-chn</td>
<td><a href="https://gitee.com/ly-chn" target="_blank">https://gitee.com/ly-chn</a></td>
</tr>
<tr>
<td><img src="https://oss.dev33.cn/sa-token/team/avatar-moli.jpg" /></td>
<td><img src="/big-file/doc/team/avatar-moli.jpg" /></td>
<td>茉莉</td>
<td><a href="https://gitee.com/kidoldman" target="_blank">https://gitee.com/kidoldman</a></td>
</tr>
<tr>
<td><img src="https://oss.dev33.cn/sa-token/team/avatar-yaoshui.jpg" /></td>
<td><img src="/big-file/doc/team/avatar-yaoshui.jpg" /></td>
<td>药水</td>
<td><a href="https://gitee.com/java_pioneer" target="_blank">https://gitee.com/java_pioneer</a></td>
</tr>
<tr>
<td><img src="https://oss.dev33.cn/sa-token/team/avatar-daimouren.png" /></td>
<td><img src="/big-file/doc/team/avatar-daimouren.png" /></td>
<td>呆某人</td>
<td><a href="https://gitee.com/zhubj0510" target="_blank">https://gitee.com/zhubj0510</a></td>
</tr>
<tr>
<td><img src="https://oss.dev33.cn/sa-token/team/avatar-chunkunqiufa.jpg" /></td>
<td><img src="/big-file/doc/team/avatar-chunkunqiufa.jpg" /></td>
<td>春困夏倦秋乏</td>
<td><a href="https://gitee.com/uncarbon97" target="_blank">https://gitee.com/uncarbon97</a></td>
</tr>
<tr>
<td><img src="https://oss.dev33.cn/sa-token/team/avatar-danmo.jpg" /></td>
<td><img src="/big-file/doc/team/avatar-danmo.jpg" /></td>
<td>淡墨</td>
<td><a href="https://gitee.com/jinan-jimeng-network_0" target="_blank">https://gitee.com/jinan-jimeng-network_0</a></td>
</tr>
+365 -271
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -29,7 +29,7 @@
由于`jwt`模式不在服务端存储数据,对于比较复杂的业务可能会功能受限,因此更加推荐使用方案三
<button class="show-img" img-src="https://oss.dev33.cn/sa-token/doc/g/g3--dcs-session.gif">加载动态演示图</button>
<button class="show-img" img-src="/big-file/doc/micro/g3--dcs-session.gif">加载动态演示图</button>
集成依赖示例:
+9 -1
View File
@@ -18,6 +18,9 @@ Sa-Token提供两种解决方案:
本篇主要讲解方案二 `Same-Token` 模块的整合步骤,其鉴权流程与 OAuth2.0 类似,不过使用方式上更加简洁(希望使用方案一的同学可参考Sa-OAuth2模块,此处不再赘述)
<img class="w-100" src="/big-file/doc/micro/micro-network-isolation.svg" alt="Same-Token_同源系统认证.svg" />
### 二、网关转发鉴权
##### 1、引入依赖
@@ -201,7 +204,12 @@ public interface SpCfgInterface {
Same-Token —— 专门解决同源系统互相调用时的身份认证校验,它的作用不仅局限于微服务调用场景
基本使用流程为:服务调用方获取Token,提交到请求中,被调用方取出Token进行校验:Token一致则校验通过,否则拒绝服务
基本使用流程为:服务调用方获取 Same-Token,提交到请求中,被调用方取出 Same-Token 进行校验:如果一致则校验通过,否则拒绝服务
<img class="w-100" src="/big-file/doc/micro/micro-same-token.svg" alt="Same-Token_同源系统认证.svg" />
首先我们预览一下此模块的相关API
``` java
+143 -3
View File
@@ -1,14 +1,154 @@
# 框架博客
> 此页面收集 Sa-Token 相关技术文章,欢迎大家投稿(不限平台,按照发表日期倒序),
> [投稿链接](https://wj.qq.com/s2/10596458/aa96/)
> 此页面收集 Sa-Token 相关技术文章(不限平台,按照发表日期倒序),
> 如果你也想投稿,请考虑加入:[Sa-Token 内容合作群 ](/more/content-cooperation)
---
- [[ CSDN ] JAVASpring Boot3 集成 Sa-Token 轻量级权限认证](https://shdxhl.blog.csdn.net/article/details/157695326) 2026-2-27
- [[ 公众号 ] 苦 Spring Security 久矣?这款霸榜 Gitee 的权限框架,把优雅做到了极致](https://mp.weixin.qq.com/s/CzBPkeV6jWZ7mpA_6JH09g) 2026-2-27
- [[ 公众号 ] 架构师推荐开源项目:轻量级Java权限认证框架!](https://mp.weixin.qq.com/s/OZtTqYIZNU2l_yyKFvbd9A) 2026-2-24
- [[ 公众号 ] Sa-Token(一)之简介及入门:告别鉴权内耗,让每一位Java开发者都能轻松上手](https://mp.weixin.qq.com/s/HLG1PHnbfOTpC3e-tGutew) 2026-2-24
- [[ CSDN ] Sa-Token SSO 前后端分离实战:SpringBoot + Vue2 单点登录全流程解析](https://blog.csdn.net/weixin_29291863/article/details/158324193) 2026-2-24
- [[ CSDN ] RefreshToken反查踩坑记:Sa-Token 1.42.0临时令牌管理新姿势](https://blog.csdn.net/weixin_28327051/article/details/158301601) 2026-2-23
- [[ 公众号 ] SpringBoot3 + Sa-Token 单点登录|30分钟上手,代码可复制,新手零踩坑](https://mp.weixin.qq.com/s/2jN9HotfYttLFMzHE54XiA) 2026-2-21
- [[ 公众号 ] Sa-Token Session会话:三种模型彻底搞懂,不再傻傻分不清](https://mp.weixin.qq.com/s/gpqogF0QyahuqJIqhs6DUg) 2026-2-21
- [[ 公众号 ] SpringBoot3 + Sa-Token 双Token登录认证实战(避坑版)](https://mp.weixin.qq.com/s/LDSCSZYuUIkQA91MYXhnGQ) 2026-2-20
- [[ CSDN ] SaToken实战:5分钟搞定微信小程序登录功能(附完整代码)](https://blog.csdn.net/weixin_29218509/article/details/158226233) 2026-2-20
- [[ 公众号 ] 告别SpringSecuritySa-Token+Gateway+Nacos极简鉴权实战](https://mp.weixin.qq.com/s/lFcH7XyLRtaNH6q4-TzHbQ) 2026-2-19
- [[ 公众号 ] SpringSecurity、Shiro和Sa-Token,哪个更好?](https://mp.weixin.qq.com/s/BEvk1ohFntL7iorDEvqIpg) 2026-2-18
- [[ CSDN ] Sa-Token 1.42.0实战:5分钟搞定API Key权限隔离与TOTP双因子认证](https://blog.csdn.net/weixin_29271053/article/details/158175327) 2026-2-18
- [[ CSDN ] SaToken权限注解全解析:@SaCheckPermission和@SaCheckRole的20种实战用法](https://blog.csdn.net/weixin_28454475/article/details/158163447) 2026-2-18
- [[ 公众号 ] 5 分钟上手 Sa-TokenSpring Boot 权限认证从未如此简单](https://mp.weixin.qq.com/s/2iRMPQdfBEgqSgeld3crXw) 2026-2-17
- [[ 公众号 ] SpringSecurity、Shiro和Sa-Token,哪个更好?](https://mp.weixin.qq.com/s/QdR1tyIXN8GvWbhN48XTjA) 2026-2-13
- [[ 公众号 ] sa-token前后端分离集成redis与jwt基础案例](https://mp.weixin.qq.com/s/c1UYdxuRjWudGnpQa_A72w) 2026-2-11
- [[ 公众号 ] Sa-Token(一)之简介及入门:告别鉴权内耗,让每一位Java开发者都能轻松上手](https://mp.weixin.qq.com/s/JLQSMAgqK1U0vtrdtRsKlA) 2026-2-11
- [[ 公众号 ] Sa-Token 的极简设计哲学](https://mp.weixin.qq.com/s/Yr48InNXxaVkNfVRbllO7w) 2026-2-10
- [[ 公众号 ] 还在用 @PreAuthorize?聊聊我切换到 Sa-Token 路由拦截后的真实体感](https://mp.weixin.qq.com/s/VMtSZDC1AFquCKRakTxytw) 2026-2-10
- [[ 公众号 ] Sa-Token 实战进阶:从“能用”到“好用”的企业级鉴权方案](https://mp.weixin.qq.com/s/hOY37lIxw01aPvjmdTf6VQ) 2026-2-9
- [[ 公众号 ] Sa-Token:把“登录/鉴权/踢人/SSO/OAuth2”做成一套顺手的 Java 权限方案(附 Spring Boot 快速上手)](https://mp.weixin.qq.com/s/zT8iRNuFfOEqDZfhVFy15w) 2026-2-9
- [[ 公众号 ] 开源、免费、一站式 java 权限认证框架,让鉴权变得简单、优雅!](https://mp.weixin.qq.com/s/FLDwIXHQoa6V2nKPs1r4cw) 2026-2-9
- [[ 公众号 ] Sa-Token 注解鉴权](https://mp.weixin.qq.com/s/72oLlgj-x8oetUpIJhR02A) 2026-2-9
- [[ 公众号 ] 用户投诉账号异常登录,CTO 让我 5 分钟内解决](https://mp.weixin.qq.com/s/pfMSZLxmDKIVYoq6UGUj1Q) 2026-2-4
- [[ 公众号 ] Sa-Token实战:SpringBoot与微服务权限认证极简方案](https://mp.weixin.qq.com/s/yNma6FhHvPLNHUqYFkdPCg) 2026-1-25
- [[ 公众号 ] Sa-Token过期机制](https://mp.weixin.qq.com/s/gFQ8YJT1yg5pTm8Z3uDZHw) 2026-1-22
- [[ 公众号 ] 别再写死权限了!SpringBoot + Sa-Token 实现 RBAC 的最佳姿势](https://mp.weixin.qq.com/s/ZwzAInOoqiQ2h0ogWaOoQg) 2026-1-14
- [[ 公众号 ] 集成sa-token跨域正确姿势](https://mp.weixin.qq.com/s/tbqjCKrTMj-l1lZbeyu81g) 2026-1-9
- [[ 公众号 ] 后端开发必看:最简单的 Java 登录认证框架 Sa-Token 上手指南](https://mp.weixin.qq.com/s/Kk9HEVAG43-FPikiMZKWJw) 2026-1-8
- [[ 公众号 ] Sa-Token:一站式权限认证解决方案的实战指南](https://mp.weixin.qq.com/s/FVkn-3CqWT8dNM6a5kD2oA) 2025-12-30
- [[ 公众号 ] SpringSecurity、Shiro和Sa-Token,哪个更好?](https://mp.weixin.qq.com/s/gtQ7_n9cPJd2-i_Qm-jk1A) 2025-12-28
- [[ 公众号 ] SpringBoot + JWT + Sa-Token:认证鉴权双框架对比,安全登录与权限控制最佳实践](https://mp.weixin.qq.com/s/SDPKdmxtwb4MbOHfg-bF8Q) 2025-12-27
- [[ 公众号 ] 一行代码搞定认证](https://mp.weixin.qq.com/s/UaAw1WdVtumA44SxjFW3yg) 2025-12-24
- [[ 掘金 ] Netty + Sa-Token 实现 WebSocket 握手认证](https://juejin.cn/post/7585490245006950406) 2025-12-21
- [[ 公众号 ] 《第27节》SpringBoot+SaToken实现鉴权功能](https://mp.weixin.qq.com/s/mY_jrQL1dG2yis51rX2i9w) 2025-12-16
- [[ 公众号 ] 告别 Spring SecuritySa-Token + Gateway + Nacos 极简鉴权实战](https://mp.weixin.qq.com/s/s36bdkhi5ACGN2j7hLiD7w) 2025-12-15
- [[ 公众号 ] 《第26节》SpringBoot3+SaToken实现用户注册登录功能](https://mp.weixin.qq.com/s/u7nVa0PJcFWx-9xKq1RNhA) 2025-12-13
- [[ 公众号 ] 《第25节》SpringBoot3之集成sa-token权限认证框架](https://mp.weixin.qq.com/s/sxgzLqiKCf4_fqWAxN8Ozg) 2025-12-12
- [[ 掘金 ] sa-token前后端分离集成redis与jwt基础案例](https://juejin.cn/post/7576843726011645978) 2025-11-26
- [[ 公众号 ] Sa-Token 1.44.0Java权限认证的“轻量级王者”,让鉴权优雅如诗](https://mp.weixin.qq.com/s/UprusTkp9LZOH9TJDTKJRw) 2025-11-20
- [[ 公众号 ] sa-token-rust 项目:高性能的 Rust 认证授权框架](https://mp.weixin.qq.com/s/jlQAX1K1M64DtUgrHrDX1A) 2025-11-17
- [[ 公众号 ] 不会吧,居然还有人没有用过?全网爆火的权限校验框架 Sa-Token 超详细教程它来了](https://mp.weixin.qq.com/s/kNYq0MmlYB_0tRWI-HvU1g) 2025-11-6
- [[ 公众号 ] 若依框架集成 Sa-Token 实现权限认证与会话管理](https://mp.weixin.qq.com/s/JAgL0hxcPeP0E4OW4oy8Yg) 2025-10-21
- [[ 公众号 ] 太强了!Sa-Token 的 Go 版本!](https://mp.weixin.qq.com/s/idfrMeAMY2CeGAZGY9csmw) 2025-10-20
- [[ 公众号 ] 太强了!Sa-Token 的 rust 版本!](https://mp.weixin.qq.com/s/CveVq368Dz5Xw-a2nT3YDw) 2025-10-12
- [[ 公众号 ] 功能最全的Java权限认证框架](https://mp.weixin.qq.com/s/fO5Mm1UIN8oDOwvbq-sQTw) 2025-10-10
- [[ 公众号 ] 从 0 到 1Sa-Token 与 SpringBoot 整合教程,让鉴权优雅到飞起](https://mp.weixin.qq.com/s/PudodYBsIQODdfeweo39fQ) 2025-10-16
- [[ 公众号 ] 一篇搞定!SpringBoot 搭建超安全 Sa-Token 登录鉴权系统](https://mp.weixin.qq.com/s/5fbrNS6jMpuViPPO8z_kMw) 2025-10-3
- [[ 公众号 ] 《Spring Cloud Gateway 从入门到实战》第4篇:安全与认证 —— 基于 Sa-Token 的网关统一鉴权方案](https://mp.weixin.qq.com/s/qeWQufIDNtGPyJ0b7yF4vQ) 2025-9-29
- [[ 公众号 ] SpringBoot整合Sa-Token实现认证与鉴权](https://mp.weixin.qq.com/s/l4OjqdeNpXjMyFCSr3PuWw) 2025-9-25
- [[ 公众号 ] Spring Gateway、Sa-Token、Nacos 认证/鉴权方案](https://mp.weixin.qq.com/s/JpXtI75eANwkRAppQZJG9Q) 2025-9-17
- [[ 公众号 ] 告别 Spring SecuritySa-Token + Gateway + Nacos 极简鉴权实战](https://mp.weixin.qq.com/s/hlBH1H6vX-KIlQGmeY8bIg) 2025-9-15
- [[ 公众号 ] Spring Gateway、Sa-Token、Nacos 认证/鉴权方案,yyds](https://mp.weixin.qq.com/s/OYsxjEmLfkxH0NqZidb4fA) 2025-9-10
- [[ 公众号 ] Ruoyi-vue-plus-5.x第一篇Sa-Token权限认证体系深度解析:1.4 Sa-Token高级特性实现](https://mp.weixin.qq.com/s/0Fex83DyngC-mxIu346X1Q) 2025-8-30
- [[ 公众号 ] Ruoyi-vue-plus-5.x第一篇Sa-Token权限认证体系深度解析:1.3 权限控制与注解使用](https://mp.weixin.qq.com/s/f4iVDeIAZ-nixqR6BNtUfg) 2025-8-30
- [[ 公众号 ] Ruoyi-vue-plus-5.x第一篇Sa-Token权限认证体系深度解析:1.2 登录认证机制详解](https://mp.weixin.qq.com/s/gu5kT93WjEauut7xoXmuag) 2025-8-29
- [[ 公众号 ] Ruoyi-vue-plus-5.x第一篇Sa-Token权限认证体系深度解析:1.1 Sa-Token框架基础](https://mp.weixin.qq.com/s/w8c5fvaap7ipMu2OdXNZng) 2025-8-29
- [[ 公众号 ] 告别 Spring SecuritySa-Token + Gateway + Nacos 极简鉴权实战](https://mp.weixin.qq.com/s/5nmEDAsFgEWk-Ymn_pE74w) 2025-8-22
- [[ 公众号 ] 搭建基于sa-token 的网关权限管理系统](https://mp.weixin.qq.com/s/LM6g3QaklSHpaVJXUz5Gvg) 2025-7-19
- [[ 公众号 ] 别再被 Spring Security 和 Shiro 劝退了!这款国产 Java 权限框架真香!](https://mp.weixin.qq.com/s/2C0WSlM8zpjqQDtgv59Kaw) 2025-7-1
- [[ 公众号 ] 一文精通Java集成Sa-Token实现SSO单点登录](https://mp.weixin.qq.com/s/-fex5XFm4wmTmuzZCtUiRw) 2025-5-22
- [[ 公众号 ] 47.8k star,一款接私活神器,10分钟搞定企业级鉴权!](https://mp.weixin.qq.com/s/KRz3-h6etPaKOwHN4Xz7qg) 2025-5-15
- [[ 公众号 ] Sa-Token17.5k Star!轻量级Java权限认证框架,登录鉴权超简单](https://mp.weixin.qq.com/s/akPTgU8sQkwWmXm4GNSyyQ) 2025-5-11
- [[ 公众号 ] SaToken-微服务认证与授权](https://mp.weixin.qq.com/s/9WYg6iLDST7YDSSsCt1NxQ) 2025-4-3
- [[ 公众号 ] SpringBoot 整合 Sa-Token 快速实现 API 接口签名安全校验](https://mp.weixin.qq.com/s/2NS3axN1CbRYULHrv5Du2w) 2025-3-20
- [[ 公众号 ] SaToken 简化开发的身份认证与权限管理框架](https://mp.weixin.qq.com/s/Yxsswl4Zn8244j4XeV8_VA) 2025-1-24
- [[ 公众号 ] 使用 Sa-Token 平替 Spring Security,告别繁琐的认证与鉴权!](https://mp.weixin.qq.com/s/whKQPm09ApzkVBjlSwO2Tg) 2025-1-15
- [[ 公众号 ] sa-token之@SaIgnore注解失效的真正原因及正确姿势](https://mp.weixin.qq.com/s/c6eckHp2M4oz2x3Hea6pGg) 2025-1-14
- [[ 公众号 ] SpringBoot3.x+Vue3+Sa-Token实现登录认证](https://mp.weixin.qq.com/s/0GkDoOYW8KKxTfzV83J6UA) 2024-12-27
- [[ 公众号 ] 万字雄文:一次说清基于Sa-Token和MaxKey的统一认证中心实现](https://mp.weixin.qq.com/s/Gl2K47F9I6-Il-AieWLplw) 2024-11-15
<!-- 2026-2-3 搜集至 2024-11-15 公众号平台 -->
- [[ 公众号 ] sa-token之@SaIgnore注解失效的真正原因及正确姿势](https://mp.weixin.qq.com/s/c6eckHp2M4oz2x3Hea6pGg) 2025-1-15
- [[ 公众号 ] 集成sa-token前后端分离部署配置corsFliter解决跨域失效的真正原因](https://mp.weixin.qq.com/s/bSS4vmKlKM7ov_CUkjxkBg) 2024-07-08
+104
View File
@@ -0,0 +1,104 @@
# Sa-Token 内容合作群
**好内容值得被看见!**
为感谢 Sa-Token 的内容创作者们,我们特别创建了「Sa-Token 内容合作群」,帮助大家的内容触达更多 Sa-Token 的使用者 (加群方式在最下方)。
---
### 📖 1、一些碎碎念,想和写公众号/录视频的朋友们聊聊
前几天,我在公众号上搜索 “Sa-Token”,想看看有没有人写过相关的教程或者踩坑心得。
说实话,当时没抱太大期望。毕竟我们开发团队这几年来几乎将所有精力都放在了代码开发,而一直疏于内容运营建设。
但结果让我挺意外的 —— **我发现了不少公众号都在写 Sa-Token 的文章,而且其中不少都写得很用心**
有从零开始的入门教程,有深入源码的解析文章,有对框架各个功能的介绍,还有一些结合真实业务场景的实战案例 …… 解决的都是实实在在的问题。
但这些文章的阅读量… 很多都只有几百,有些甚至只有几十。
我知道这很正常。**技术公众号起步非常不容易,粉丝少的时候,再好的内容也很难被看到**。我自己也经历过这个阶段,知道那种 [ 写了一整天,发出来没人看 ] 的感觉。
为了不让这些有价值的内容埋没,我连夜将这些文章整理到了 Sa-Token 官网:[框架博客](/more/blog)。
在整理这些博客的过程中,我突然有了一个想法。
### ✨ 2、一个可能 [三赢] 的想法
我在想,我为什么不拉一个群聊,把这些为 Sa-Token 写文章的博主们,给聚集起来呢?
只要有朋友写了 Sa-Token 相关文章,都可以转发到群里,我们团队会把这个文章转发到 Sa-Token 所有粉丝群里:
这可能是一个三赢的合作:
- **对于 Sa-Token 来说**:能获得更多的优质内容,帮助新用户更快上手,生态也能更丰富。
- **对写文章的朋友来说**:你的好文章能被更多人看到,公众号能涨涨粉,付出的时间更有价值。
- **对 Sa-Token 的用户来说**:能看到更多的技术干货,学到更多知识,找到各种场景的解决方案,不用重复踩坑。
听起来好像…还不错?
所以我打算建个群,名字就叫 「Sa-Token 内容合作群」。不是什么正式的组织,就是一群对技术内容感兴趣的朋友,凑在一起互相帮帮忙。
### 🤝 3、这个群主要用来做什么?
1、如果你写了 Sa-Token 相关的文章(或录制了视频课程),可以分享到群里,我们团队会把文章:
- **转发到 Sa-Token 所有粉丝群里**Sa-Token 目前拥有 30+ 微信粉丝群 (500人)10+ QQ粉丝群 (1000人 or 2000人)。
- **挂载到 Sa-Token 在线文档博客栏目**:Sa-Token 目前在线文档访问量月PV 20万+。
相信这一定可以大大提高文章的曝光量。
2、我们团队偶尔也会为 Sa-Token 撰写技术文章,发到群里:
- 如果你觉得内容不错,想转载到自己的公众号,**直接转就行**。
- 不用专门申请授权,Sa-Token 官方订阅号所有内容均开放版权,任何人都可以自由转载。
### ❤️ 4、几个你可能关心的小问题
#### Q:我没写过 Sa-Token 的文章,可以加入吗?
可以。完全没问题。哪怕你之前从来没写过 Sa-Token 的文章,但只要你有公众号,想试试写相关的内容,都欢迎。
如果你没有公众号,但是在别的平台,比如掘金、CSDN有写过文章,也可以加入。如果你在 B站/抖音录制过视频,也可以加入。
总之:只要你有意向在任意平台创作 Sa-Token 相关内容,就可以加入。
#### Q:有 KPI 吗?是不是进了群就要为 Sa-Token 写文章?
没有。 想写就写,不想写就不写。哪怕加群后一篇都不写,也没关系。**这就是个「互助群」,不是「任务群」**。
#### Q:文章有什么要求吗?
就一点:认真写。
可以是入门教程、源码解析、实战心得、bug排查、常见踩坑、对比测评…什么形式都行。不需要多长的篇幅,能把一个知识点讲清楚就好。唯一的要求是:不要是纯 AI 生成的粗制滥造文,不要写明显错误的技术观点。
#### Q:对粉丝量有要求吗?
没有要求。 我自己也是从小号做起来的,完全理解起步的难处。群里不分大号小号,只看内容用不用心。
#### Q:除了转发,还有别的吗?
有。 我偶尔会分享一些 Sa-Token 的更新动态、设计思路,或者我发现的其他好的技术文章。大家也可以互相**聊聊技术写作、视频制作的心得,分享好用的工具**。就是个普通的交流群,只不过主题稍微聚焦一点。
说到底,我希望这个群不只是一个内容分发渠道,更是一个 「Sa-Token内容共创伙伴」的聚集地。我们一起,让好的技术方案被更多人看见和使用。
### 👋 5、怎么加群?
你可以在 [加入讨论群](/more/join-group) 处,添加我们的微信账号, **请在添加时备注或者加好友成功后发送以下信息:[申请加入 Sa-Token 内容合作群]**
一定要备注以上信息,否则我们团队人员只会把你拉入到普通粉丝交流群。
<img class="s-w" src="/big-file/contact/show/wx-sthzq-show.png" style="max-width: 50%;" alt="Sa-Token 生态合作群" />
+12 -8
View File
@@ -6,29 +6,32 @@
### 1、加入QQ交流群
<!-- ![QQ群](https://oss.dev33.cn/sa-token/qr/qq-group-5-gsa.png ':size=180') -->
<!-- QQ交流群:685792424 [点击加入](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Y05Ld4125W92YSwZ0gA8e3RhG9Q4Vsfx&authKey=IomXuIuhP9g8G7l%2ByfkrRsS7i%2Fna0lIBpkTXxx%2BQEaz0NNEyJq00kgeiC4dUyNLS&noverify=0&group_code=685792424)
-->
<!-- QQ交流群:936523917 [点击加入](https://qm.qq.com/q/xfoMJA5Az0) -->
QQ交流群:823181187 [点击加入](https://qm.qq.com/q/EBIJVZBVGE)
QQ交流群:1081649142 [点击加入](https://qm.qq.com/q/SCAaZ6Ros2)
### 2、加入微信交流群:
<!-- ![微信群](https://oss.dev33.cn/sa-token/wx-qr-300.png ':size=180') -->
<!-- <img class="s-w" src="/big-file/contact/wx-qr-300.png" style="width: 180px;" alt="微信群" /> -->
![微信群](https://oss.dev33.cn/sa-token/qr/i-wx-qr2.png ':size=175')
<img class="s-w" src="/big-file/contact/i-wx-qr2.jpg" style="width: 180px;" alt="微信群" />
扫码添加微信备注:sa,邀您加入群聊
PS扫码添加微信 (备注:sa),邀您加入群聊
<br>
<img class="s-w" src="/big-file/contact/show/wx-group-show3.png" style="max-width: 50%;" alt="微信群" />
加入群聊的好处:
- 第一时间收到框架更新通知。
- 第一时间收到框架 bug 通知。
- 第一时间收到新增开源案例通知。
- 和众多大佬一起互相 (huá shuǐ) 交流 (mō yú)。
- 和众多大佬一起互相 (huá shuǐ) 交流 (mō yú) 🖐️🐟️
### 3、群规(碎碎念):
@@ -67,5 +70,6 @@ QQ群聊号码:939849926
(例如:xx开源项目作者集成了 sa-token,申请加入群聊),如果字数太多无法写完,也可在开源交流群里@管理员协助交流
### 5、Sa-Token 内容合作群
专门为 Sa-Token 内容创作者们准备的交流群:[Sa-Token 内容合作群](/more/content-cooperation)
+87 -11
View File
@@ -1,12 +1,31 @@
# 使用 Sa-Token 的开源项目
> 集成 Sa-Token 的开源案例收集,取自 [Awesome-Sa-Token](https://gitee.com/sa-tokens/awesome-sa-token),定期同步
> 集成 Sa-Token 的开源案例收集,取自 Awesome-Sa-Token,定期同步
> [Gitee](https://gitee.com/sa-tokens/awesome-sa-token)、
> [GitHub](https://github.com/sa-tokens/awesome-sa-token)、
> [AtomGit](https://atomgit.com/sa-tokens/awesome-sa-token)
---
### 后台管理
### 📊 后台管理
- [[ art-design-pro-java ]](https://github.com/anganing/art-design-pro-java)SpringBoot17+Sa-token+Art-Design-Pro+Unibest 技术栈的企业级后台开发管理系统。
- [[ wemirr-platform ]](https://gitee.com/battcn/wemirr-platform)JDK17、SCA2023、SC2024、Sa-Token、VBen5.x 全网最炫酷,功能最多,最优雅地真开源 多租户、SAAS 微服务项目。
- [[ Lucky-Admin-Vue ]](https://gitee.com/xiaodu6/lucky-admin-vue):一个基于vue-admin-template的后台管理框架,集成了动态角色权限,动态路由,角色权限动态配置,日志框架,代码生成,Sa-Token权限校验,快速构建一个后台的开发框架。
- [[ 灯灯]](https://github.com/dromara/lamp-cloud):基于java + SpringCloudAlibaba +SpringBoot 开发的微服务中后台快速开发平台,专注于多租户 (SaaS架构) 解决方案,亦可作为普通项目(非SaaS架构)的基础开发框架使用,目前已实现 数据源隔离、字段隔离、无租户隔离 等几种模式。
- [[ 橙单 ]](https://gitee.com/orangeform/orange-admin):技术栈Boot3 + Flowable7 + Sa-Token + Mybatis-Flex/Mybatis-Plus + Vue3,支持开箱即用且功能完成的工作流和在线表单功能,提供高颜值的流程和表单编辑器全部前后端源码。
- [[ Sz-Admin ]](https://github.com/feiyuchuixue):一个开源RBAC中后台框架,专为现代应用设计。它结合了最新的技术栈,包括后端的Spring Boot 3、JDK 21、Mybatis Flex、Sa-Token、Knife4j和Flyway,以及前端的Vue 3、Vite5、TypeScript和Element Plus,致力于为您提供一个直观、流畅且功能强大的开发体验。
- [[ newbie-boot3 ]](https://github.com/zhangyuge7/newbie-boot3):企业级中大型项目快速开发平台,后端使用JDK21+SpringBoot3+SaToken+MybatisPlus等,前端基于FiveAdminV2后台管理系统模板开发,使用js+vue3+vite5+ElementPlus等最新技术栈。
- [[ EuBackend ]](https://gitee.com/zhaoeryu/eu-backend)EuBackend 是一套全部开源的前后端分离 Java EE 企业级快速开发平台,基于最新技术栈SpringBoot、Sa-Token、MyBatisPlus等作为后端框架,使用RBAC作为权限控制模型,并且毫无保留给个人及企业免费使用。
- [[ srppms ]](https://gitee.com/cai-bin00/srppms):基于SpringBoot+Vue+sa-token前后端分离的科研项目管理平台。
@@ -28,8 +47,6 @@
- [[ vue-satoken-admin ]](https://gitee.com/niluni/vue-satoken-admin):基于Vue2和Sa-Token1.18.0的后台权限系统。
- [[ 人事管理系统后端 ]](https://gitee.com/sdones_1512/personnel-management-system-back-end):人事管理系统后端,框架:springboot,持久层:mybatis,缓存:redis,权限:sa-token
- [[ bootx-platform ]](https://gitee.com/bootx/bootx-platform):包含支付收单(支付宝、微信、聚合、组合支付)、工作流(Flowable)、三方对接(微信、钉钉、企微、短信)等模块,前端基于Vue2和Vue3分别打造,可应用在不同业务场景中,目标是致力实现媲美商业版应用脚手架。
- [[ spba-admin ]](https://gitee.com/qkdja/spring-boot-admin):基于SpringBoot、Vue开发的通用后台管理系统,做到开箱即用,为新项目开发省去了基础功能开发的步骤。主要使用Sa-Token权限认证、MyBatis-Plus、MySQL、Redis、validation、七牛云等技术。
@@ -38,7 +55,7 @@
- [[ ExciteCMS-Layui ]](https://gitee.com/ExciteTeam/ExciteCMS-SpringBoot-Layui)ExciteCMS 快速开发脚手架:一款后端基于 SpringBoot2 + Sa-Token + Mybatis-Plus,前端基于 Layuimini 的内容管理系统,具备RBAC、日志管理、代码生成等功能,并集成常用的支付、OSS等第三方服务,拥有详细的开发文档
- [[ sss-rbac-admin ]](https://gitee.com/momoljw/sss-rbac-admin):基于springboot,整合satoken、sqltoy的权限管理系统
- [[ sra-admin ]](https://github.com/CoCoTeaNet/sra-admin):快速开发脚手架,核心依赖:springboot3+sqltoy+satoken+hutool | 轻量级 | 只实现了用户、字典、角色、权限等常见功能,能够快速搭建一个web项目。
- [[ QuickBuild ]](https://gitee.com/CodeLiQing/custom-quick-build-platform): 快速构建 | 基于springboot+sa-token+neety+代码生产器(生成vue页面和增删改查代码)| 以及前端vue3和字节arco.design框架整合
@@ -58,7 +75,7 @@
- [[ RuoYi-Vue-CMS ]](https://gitee.com/liweiyi/RuoYi-Vue-CMS)RuoYi-Vue-CMS是前后端分离的内容管理系统,支持站群管理、多平台静态化、元数据模型扩展、多语言、全文检索,能轻松组织各种复杂内容形态。技术栈:SpringBoot3 + VUE2 + MybatisPlus + Sa-Token + xxl-job + Freemarker + ES + Redis + MySQL。
- [[ springboot-multi-tenant-sa-token ]](https://gitee.com/willf/springboot-multi-tenant-sa-token):轻量的多租户权限管理系统脚手架(SpringBootSa-Tokenmybatis-plusVue & Element)。
- [[ springboot-multi-tenant-sa-token ]](https://gitee.com/willf/springboot-multi-tenant-sa-token):轻量的多租户后台管理系统脚手架(SpringBootSa-Tokenmybatis-plusVue & Element)。
- [[ solon_angis_beetlsql ]](https://gitee.com/smartcity/solon_angis_beetlsql):并元国产开发平台 solon、sa-token、beetlsql、smart-http
@@ -66,8 +83,29 @@
- [[ nebula-swagger-demo ]](https://gitee.com/flgitee/nebula-swagger-demo)springboot+nebula 集成knife4j案例
- [[ warm-sun]](https://gitee.com/min290/warm-sun):基于solon+vue3开发,jdk17+satoken+redisx/redisson+mybaits-flex+hutool+jackson+mapstruct+poi
### 微服务相关
- [[ContiNew Admin]](https://gitee.com/Charles7c/continew-admin)ContiNew Admin 中后台管理框架/脚手架,Continue New Admin,持续以最新流行技术栈构建,拥抱变化,迭代优化。当前采用的技术栈:Vue3、TypeScript、Arco Design Vue、Spring Boot3JDK17)、Undertow、Sa-Token、JWT、MariaDB、MyBatis Plus、Redis、Redisson、Easy Excel、Hutool 等。
- [[laymini-admin]](https://gitee.com/wlf213/laymini-admin):基于layuimini前端框架开发的一个简单的后台管理前后端不分离框架,主体技术mybatisplus+sa_token+springboot+freemarker,主要功能:RABC认证授权,后台管理功能,集成Quartz动态定时任务。
- [[ Smart-Admin ]](https://gitee.com/lab1024/smart-admin)SmartAdmin国内首个以「高质量代码」为核心,「简洁、高效、安全」中后台快速开发平台;基于SpringBoot + Sa-Token + Mybatis-Plus 和 Vue3 + Vite5 + Ant Design Vue 4.x (同时支持JavaScript和TypeScript双版本);满足国家三级等保要求、支持登录限制、接口数据国产加解密、高防SQL注入等一系列安全体系。
- [[ Halcyon-Admin ]](https://github.com/hhfb8848/halcyon-springboot):基于 Spring Boot 3 和 Vue 3 的通用后台管理系统,专注于提供基本的管理功能,而非特定的部门管理或业务功能。
- [ breeze-boot-satoken-xxx系统 ]breeze-boot-satoken-xxx 是一个开源免费(前后端分离)中后台管理系统基础解决方案,前端技术栈:( Vue3、 TypeScript、Element Plus、Pinia 、Vite)后端技术栈:(jdk17、 springboot3、SaToken、MybatisPlus等)
- SSO 版本,后端:https://gitee.com/breeze-boot/breeze-boot-satoken-sso
- SSO 版本,前端:https://gitee.com/breeze-boot/breeze-vite-ui-satoken-sso
- OAUTH 版本,后端:https://gitee.com/breeze-boot/breeze-boot-satoken-oauth
- OAUTH 版本,前端: https://gitee.com/breeze-boot/breeze-vite-ui-satoken-oauth
- [[ Summer-Flowers · 夏花 ]](https://gitee.com/Luv404/summer-flowers):基于 **Spring Boot 3 + JPA + QueryDSL + Sa-Token** 的企业级后台开发框架,前端采用 **SoybeanAdmin**。不同于常见 MyBatis 体系,Summer-Flowers 以 **Entity 作为业务第一表达**,通过 QueryDSL 实现类型安全的复杂查询,配合代码生成器与模块化架构,显著降低中长期项目的维护成本。
### 🚀 微服务相关
- [[ XHan Admin ]](https://gitee.com/sun-xiaohan/xh-admin-frontend)XHan Admin 是一个开源免费(前后端分离)中后台管理系统基础解决方案, 无专业版收费,所有功能毫无保留的贡献给开源社区,使用最新技术栈全新开发,无任何历史代码包袱。
- [[ RuoYi-Cloud-Plus ]](https://gitee.com/dromara/RuoYi-Cloud-Plus):重写RuoYi-Cloud所有功能 整合 SpringCloudAlibaba + Sa-Token + Dubbo + Mybatis-Plus + Xxl-Job 全方位升级 定期同步
@@ -93,8 +131,12 @@
- [[ quick-boot ]](https://github.com/csx-bill/quick-boot):一款基于 Spring Cloud 2022 、Spring Boot 3、AMIS 和 APIJSON 的低代码系统。
- [[ linkin-platform ]](https://gitee.com/paohaizi/linkin-platform)Springboot + Springcloud + nacos + Mybatis Plus + Sa-Token + Vue3 + ElementPlus
微服务下使用Sa-Token的样例,是一套比较简洁的后台系统。
### 商城
- [[ LangChat ]](https://github.com/TyCoding/langchat)( OpenAI / Gemini / Ollama / Azure / 智谱 / 阿里通义大模型 / 百度千帆大模型), Java生态下AI大模型产品解决方案,快速构建企业级AI知识库、AI机器人应用
### 🛒 商城
- [[ litemall-plus ]](https://gitee.com/ysling-org/litemall-plus):微信小程序SaaS商城系统,可支持多小程序同时运行。
@@ -103,7 +145,7 @@
- [[ Huanxing-mall ]](https://gitee.com/lijiaxing_boy/huanxing-mall)HuanXing 商城基于SpringCloud 2021 & Alibaba + Sa-token,前端基于 Vue3 +Element plus 的微服务商城
### 博客
### 📝 博客
- [[ jthink ]](https://gitee.com/wtsoftware/jthink) 一个基于 SpringBoot + Sa-Token + Thymeleaf 的博客系统
@@ -111,17 +153,34 @@
- [[ June 12 ]](https://gitee.com/hanshaung/ants)June 12 是一个纯开源免费的资讯/博客类网站,基于Spring Boot + Sa-Token + Vue开发。
- [[ YuanBlog ]](https://gitee.com/wlf213/yuan-blog):一款代码简单,功能丰富的多人社交博客平台。前后端分离,Vue+SpringBoot3,博客前端使用Quasar,后台管理前端使用NaiveUI,博客后端,后台管理后端分为两个系统,均使用Sa-Token进行认证授权。支持邮箱验证码登录。
- [[ 鸢尾博客 ]](https://gitee.com/lxwise/iris-blog_parent):鸢尾博客是一个基于Spring Boot+Vue3 + TypeScript + Vite+JavaFx的客户端和服务器端的博客系统。项目采用前端与后端分离,支持移动端自适应,配有完备的前台和后台管理功能。后端使用Sa-Token进行权限管理,支持动态菜单权限,服务健康监控,数据流量统计,支持QQ、微博、码云、GitHub等三方登录。
### 插件
### 🔌 插件
- [[ Sa-Token-Plugin ]](https://gitee.com/bootx/sa-token-plugin)Sa-Token第三方插件实现,基于Sa-Token-Core,提供一些与官方不同实现机制的的插件集合,作为Sa-Token开源生态的补充
- [[ quarkus-sa-token ]](https://github.com/quarkiverse/quarkus-sa-token) quarkus 整合 Sa-Token。
### 🌐 多语言
### 其它
- Rust[[ sa-token-rust ]](https://github.com/sa-tokens/sa-token-rust) 一个轻量级、高性能的 Rust 认证授权框架。
- Go[[ sa-token-go ]](https://github.com/sa-tokens/sa-token-go) 一个轻量级、高性能的 Go 权限认证框架。
- PHP[[ real-token ]](https://gitee.com/jinan-jimeng-network_0/real-token) 一个轻量级 thinkphp6 权限认证框架,让鉴权变得简单、优雅!
### 📦 其它
- [[ Glowxq-OJ ]](https://github.com/glowxq/glowxq-oj)Glowxq-OJ 专业开源在线编程测评系统 | 基于Spring Boot 3.x + Java 21 + Vue 3构建 | 支持ACM/ICPC竞赛、信奥赛训练、编程教育 | 多语言判题、实时竞赛、在线IDE | Docker一键部署 | Modern Online Judge Platform for Competitive Programming & Coding Education。
- [[ FlyFlow ]](https://gitee.com/junyue/flyflow):基于SaToken开发的开源工作流系统:FlyFlow借鉴了钉钉与飞书的界面设计理念,致力于打造一款用户友好、快速上手的工作流程工具。
- [[ Sa-Token-Study ]](https://gitee.com/sa-tokens/sa-token-study):以demo示例的方式讲解 Sa-Token 源码涉及到的技术点,连载中……
@@ -130,3 +189,20 @@
- [[ iot-kit ]](https://gitee.com/iotkit-open-source/iotkit-parent):一个轻量级低门槛的物联网平台,包含了多协议设备接入、规则引擎、第三方平台接入、智能家居小程序等模块的项目,基于SpringBoot架构并集成了Sa-Token的OAuth2认证。
- [[ cubic ]](https://gitee.com/dromara/cubic):一站式问题定位平台,实时线程栈监控、线程池监控、动态arthas命令集、依赖分析等等等,助你快速定位问题。
- [[ ChatGPT-WEB ]](https://github.com/dulaiduwang003/ChatGPT-WEB):基于JDK17+SpringBoot3+UniApp 绘图 聊天 充值应用。(Web版本)
- [[ SuperBot-ChatGPTApp ]](https://github.com/dulaiduwang003/SuperBot-ChatGPTApp):基于JDK17+SpringBoot3+UniApp 绘图 聊天 充值应用。(小程序版本)
- [[ ScribbleHub ]](https://github.com/dulaiduwang003/ScribbleHub):基于SpringBoot+satoken+wxss开发的博客小程序
- [[ TIME-SEA-chatgpt ]](https://github.com/dulaiduwang003/TIME-SEA-chatgpt):基于SpringBoot+satoken+vue3+uniapp开发的多端Ai平台应用
- [[ SUPERBOT-GPT]](https://github.com/dulaiduwang003/SUPERBOT-GPT):基于SpringBoot3+satoken+uniapp开发的流量主小程序
- [[ DaxPay ]](https://gitee.com/dromara/dax-pay):一款免费开源的支付网关系统,支持支付宝、微信、云闪付等通道,提供收单、退款、聚合支付、对账、分账等功能。
- [[ Dinky ]](https://github.com/DataLinkDC/dinky):基于Apache Flink的实时数据开发平台,实现敏捷的数据开发、部署和运维
- [[ mldong ]](https://gitee.com/mldong/mldong)SpringBoot + Vue3 快速开发平台、自研工作流引擎
+7 -1
View File
@@ -41,16 +41,22 @@ Sa-Token 无意发明任何晦涩概念提升逼格,但在处理 issue 、Q群
- 单地登录:指同一时间只能在一个地方登录,新登录会挤掉旧登录,也可以叫:单端登录。
- 多地登录:指同一时间可以在不同地方登录,新登录会和旧登录共存,也可以叫:多端登录。
- 同端互斥登录:在同一类型设备上只允许单地点登录,在不同类型设备上允许同时在线,参考腾讯QQ的登录模式:手机和电脑可以同时在线,但不能两个手机同时在线。
- 限量登录:限定账号登录设备总数量,低于此数量时可以正常登录,高于此数量后每次登录自动清退一个之前的登录。
- 记住我模式:指在一个设备终端登录成功,该设备重启之后依然保持登录状态。
- 单点登录:在进入多个系统时,只需要登录一次即可。解决用户在不同系统间频繁登录的问题。
- 同端多登录:指在一个终端可以同时登录多个账号。
- 记住我模式:指在一个设备终端登录成功,该设备重启之后依然保持登录状态。
#### 几种注销策略:
- 单端注销:只在调用登录的一端注销。
- 全端注销:一端注销,全端下线。
- 同端注销:发起注销后,同类型设备端一起下线,不同设备类型不受影响。
- 单点注销:与单点登录对应,一个系统注销,所有系统一起下线。
<p><a class="case-btn case-btn-video" href="https://www.bilibili.com/video/BV1XrzABbEa1/" target="_blank">
视频讲解:如何设计出最优的登录会话策略?单端登录、强制下线、多端互踢、记住我登录
</a></p>
#### 几种鉴权方式:
- 代码鉴权:在代码里直接调用 `StpUtil.checkXxx` 相关 API 进行鉴权。
+2 -2
View File
@@ -10,7 +10,7 @@ Sa-Token 采用 Apache-2.0 开源协议,**承诺框架本身与官网文档永
您可以在项目 [Gitee](https://gitee.com/dromara/sa-token) 主页进行捐赠
![gitee-zanzhu2.png](https://oss.dev33.cn/sa-token/doc/gitee-zanzhu2.png)
<!-- ![gitee-zanzhu2.png](https://oss.dev33.cn/sa-token/doc/gitee-zanzhu2.png) -->
@@ -176,7 +176,7 @@ Sa-Token 目前总计14+微信交流群(每个群人数大约400以上,总
- 拒绝接受违反法律法规、以及灰色相关的产品推广,为避免不必要的麻烦,目前也拒绝推广IP代理、上网工具等等。
- 为避免过多打扰群友,目前一天内至多在群里推广两次,超过次数的可顺延到第二天。
有意见合作者可直接在 gitee 发起赞赏后,将您的产品信息发至 [ sa-pro 小助手 ] ,加好友链接点 [这里](http://sa-pro.dev33.cn/),点击后往下拉有二维码,添加时请备注:Sa-Token 商业赞助。
有意见合作者可直接在 gitee 发起赞赏后,将您的产品信息发至 [ sa-pro 小助手 ] ,加好友链接点 [这里](https://sa-pro.yun94.cn/),点击后往下拉有二维码,添加时请备注:Sa-Token 商业赞助。
注:在群发消息时,我们会明确对群友声明,此条消息为赞助商的产品,属于硬广信息。如您介意这一点,我们将暂时无法与您合作。
+5 -4
View File
@@ -2,16 +2,17 @@
---
Sa-Token 采用 Apache-2.0 开源协议,**承诺框架本身与官网文档永久免费开放**
Sa-Token 采用 Apache-2.0 开源协议,<green>**承诺框架本身与在线文档永久免费开放**</green>
但是框架的日常更新与社区运营需要付出大量的精力,靠爱发电难以长久,如果 Sa-Token 帮助到了您,您可以友情支持一下 Sa-Token。
### 友情赞助
您可以在项目 [Gitee](https://gitee.com/dromara/sa-token) 主页进行捐赠
<!-- 您可以在项目 [Gitee](https://gitee.com/dromara/sa-token) 主页进行捐赠
![gitee-zanzhu2.png](https://oss.dev33.cn/sa-token/doc/gitee-zanzhu2.png)
<img src="/big-file/doc/more/gitee-zanzhu2.png" alt="gitee-zanzhu2.png" /> -->
<img src="/big-file/contact/wx-zsm.png" alt="微信赞赏码" style="width: 280px;" />
**已捐赠列表:**
@@ -58,7 +59,7 @@ Sa-Token 目前总计18+微信交流群(每个群人数大约400以上,总
- 拒绝接受违反法律法规、以及灰色相关的产品推广,为避免不必要的麻烦,目前也拒绝推广IP代理、上网工具等等。
- 为避免过多打扰群友,目前一天内至多在群里推广一次,超过次数的可顺延到第二天。
有意见合作者可直接在 gitee 发起赞赏后,将您的产品信息发至 [ sa-pro 小助手 ] ,加好友链接点 [这里](http://sa-pro.dev33.cn/),点击后往下拉有二维码,添加时请备注:Sa-Token 商业赞助。
有意见合作者可直接在 gitee 发起赞赏后,将您的产品信息发至 [ sa-pro 小助手 ] ,加好友链接点 [这里](https://sa-pro.yun94.cn/),点击后往下拉有二维码,添加时请备注:Sa-Token 商业赞助。
注:在群发消息时,我们会明确对群友声明,此条消息为赞助商的产品,属于硬广信息。如您介意这一点,我们将暂时无法与您合作。
+2 -2
View File
@@ -33,7 +33,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
当不知情的小红被诱导访问了这个 URL 时,它将被重定向至百度首页。
![oauth2-ticket-jc](https://oss.dev33.cn/sa-token/doc/oauth2-new/oauth2-ticket-jc.png 's-w-sh')
<img class="s-w-sh" src="/big-file/doc/oauth2-new/oauth2-ticket-jc.png" alt="oauth2-ticket-jc" />
可以看到,代表着用户身份的 code 授权码也显现到了URL之中,借此漏洞,攻击者完全可以构建一个 URL 将小红的 code 授权码自动提交到攻击者自己的服务器,伪造小红身份登录网站。
@@ -64,7 +64,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
再次访问上述链接:
![oauth2-feifa-rf](https://oss.dev33.cn/sa-token/doc/oauth2-new/oauth2-feifa-rf.png 's-w-sh')
<img class="s-w-sh" src="/big-file/doc/oauth2-new/oauth2-feifa-rf.png" alt="oauth2-feifa-rf" />
URL 没有通过校验,拒绝授权!
+2 -2
View File
@@ -6,7 +6,7 @@
### 1、如何自定义 OAuth-Server 端的登录视图?
重写 `cfg.notLoginView` 策略:
重写 `SaOAuth2Strategy.instance.notLoginView` 策略:
``` java
@Autowired
@@ -60,7 +60,7 @@ public SaResult ss(String name, String pwd) {
### 3、如何自定义 OAuth-Server 端的确认授权视图?
重写 `cfg.confirmView` 策略:
重写 `SaOAuth2Strategy.instance.confirmView` 策略:
``` java
@Autowired
+2 -2
View File
@@ -117,11 +117,11 @@ public class SaOAuth2ServerH5Controller {
在前端 ide 中导入 demo 案例的 `sa-token-demo-oauth2-server-h5` 项目,然后直接预览 `oauth2-authorize.html` 页面,如图所示:
![sa-oauth2-server-authorize-h5.png](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-server-authorize-h5.png 's-w-sh')
<img class="s-w-sh" src="/big-file/doc/oauth2-new/sa-oauth2-server-authorize-h5.png" alt="sa-oauth2-server-authorize-h5.png" />
复制上述地址,然后将其配置到 “OAuth2前端测试页” 的 “OAuth2 Server 授权页地址” 选项中,其它选项保持默认不变:
![sa-oauth2-client-test-h5-page-setting.png](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-client-test-h5-page-setting.png 's-w-sh')
<img class="s-w-sh" src="/big-file/doc/oauth2-new/sa-oauth2-client-test-h5-page-setting.png" alt="sa-oauth2-client-test-h5-page-setting.png" />
然后根据 “OAuth2前端测试页” 的页面提示进行测试即可,此处不再赘述。
+5 -6
View File
@@ -227,14 +227,14 @@ http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=10
2、由于首次访问,我们在OAuth-Server端暂未登录,会被转发到登录视图
![sa-oauth2-server-login-view](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-server-login-view--v43.png 's-w-sh')
<img class="s-w-sh" src="/big-file/doc/oauth2-new/sa-oauth2-server-login-view--v43.png" alt="sa-oauth2-server-login-view" />
3、输入 `sa/123456` 进行登录之后,会提示我们确认授权
![sa-oauth2-server-scope](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-server-scope.png 's-w-sh')
<img class="s-w-sh" src="/big-file/doc/oauth2-new/sa-oauth2-server-scope.png" alt="sa-oauth2-server-scope" />
4、点击同意授权之后,我们会被重定向至 redirect_uri 页面,并携带了code参数
![sa-oauth2-server-code](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-server-code.png 's-w-sh')
<img class="s-w-sh" src="/big-file/doc/oauth2-new/sa-oauth2-server-code.png" alt="sa-oauth2-server-code" />
4、我们拿着code参数,访问以下地址:
``` url
@@ -259,7 +259,6 @@ http://sa-oauth-server.com:8000/oauth2/token?grant_type=authorization_code&clien
}
```
<!-- ![sa-oauth2-server-token](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-server-token.png 's-w-sh') -->
测试完毕
@@ -272,7 +271,7 @@ http://sa-oauth-server.com:8000/oauth2/token?grant_type=authorization_code&clien
依次启动`OAuth2-Server``OAuth2-Client`,然后从浏览器访问:[http://sa-oauth-client.com:8002](http://sa-oauth-client.com:8002)
![sa-oauth2-client-index](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-client-index.png 's-w-sh')
<img class="s-w-sh" src="/big-file/doc/oauth2-new/sa-oauth2-client-index.png" alt="sa-oauth2-client-index" />
如图,可以针对OAuth2.0四种模式进行详细测试
@@ -286,7 +285,7 @@ OAuth2 前端测试页:
此示例允许你在前端自由配置 OAuth-Client 端所需的各个参数,方便对 OAuth2 四种模式的测试。
![sa-oauth2-client-index](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-client-test-h5-page.png 's-w-sh')
<img class="s-w-sh" src="/big-file/doc/oauth2-new/sa-oauth2-client-test-h5-page.png" alt="sa-oauth2-client-index" />
<p><a class="case-btn case-btn-video" href="https://www.bilibili.com/video/BV13LSMYzEmE/" target="_blank">
参考视频:OAuth2 四种模式 前端测试页
+1 -1
View File
@@ -23,7 +23,7 @@ OAuth2.0 与 SSO 相比,增加了对应用授权范围的控制,减弱了应
3. 密码式(Password):Client 端直接拿着用户的账号密码换取授权 `Access-Token`
4. 客户端凭证(Client Credentials):Server 端针对 Client 级别的 Token,代表应用自身的资源授权。
![https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-setup.png](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-setup.png)
<img src="/big-file/doc/oauth2-new/sa-oauth2-setup.png" alt="sa-oauth2-setup.png" />
接下来我们将通过简单示例演示如何在 Sa-Token-OAuth2 中完成这四种模式的对接: [搭建OAuth2-Server](/oauth2/oauth2-server)

Some files were not shown because too many files have changed in this diff Show More