1
0
mirror of synced 2026-05-22 14:43:15 +00:00

Compare commits

..

43 Commits

Author SHA1 Message Date
click33 f243f105ea 优化 readme 2026-05-22 22:37:55 +08:00
click33 4da5f2381f docs: 优化 readme 2026-05-22 21:38:10 +08:00
刘潇 2929f5cbc9 !363 docs: 根据群友(144684xxxx)订正SSO-Client:接收消息推送地址的路由
Merge pull request !363 from Uncarbon/N/A
2026-05-22 09:26:55 +00:00
Uncarbon 074c790db5 docs: 根据群友(144684xxxx)订正SSO-Client:接收消息推送地址的路由
Signed-off-by: Uncarbon <4840454+uncarbon97@user.noreply.gitee.com>
2026-05-22 09:25:38 +00:00
click33 79b0de9cd6 docs(blog): 注释掉一篇博客文章链接 2026-05-19 13:27:20 +08:00
click33 6f4af4351f feat(doc): 新增 star-guide 引导组件
在 doc.html 和 index.html 中引入 star-guide.js,
新增 sa-token-doc/a/star-guide/ 目录,包含引导组件页面与脚本文件。
2026-05-19 04:47:54 +08:00
click33 97ddf27d00 docs: 同步博客文章 2026-05-19 02:19:12 +08:00
click33 c229282e4a docs(donate): 新增捐赠记录(森林雨,2026-05-17) 2026-05-17 16:01:43 +08:00
click33 be104951d9 chore: update .gitignore and add donor record
- 新增 .gitignore 规则,忽略 AI 工具配置目录(.qoder、.soloncode/、.cursor、.agents、.github)及 AGENTS.md、CLAUDE.md
- 捐赠列表新增捐赠记录(朱毛毛,2026-05-13)
2026-05-17 15:29:18 +08:00
click33 d4700d8693 docs: 同步最新赞助者列表 & 博客列表 2026-05-10 20:03:49 +08:00
click33 ec8d7651a5 docs: 同步最新赞助者名单 2026-05-03 12:39:54 +08:00
click33 449713a2ad docs: 同步最新博客列表 2026-04-26 09:45:11 +08:00
click33 9a54822d2e docs: 格式优化 2026-04-24 19:26:02 +08:00
click33 613304b088 docs: Sa-Token 集成 Demos 示例大全下载 2026-04-22 23:54:35 +08:00
click33 dbd0f42547 docs: 新增 sa-token 集成 demo 示例大全下载 2026-04-22 23:28:11 +08:00
click33 8b9cd0c813 docs: 同步最新博客列表 2026-04-20 14:36:44 +08:00
click33 76591b5243 docs: 更新赞助者名单等 2026-04-13 12:13:05 +08:00
click33 cc2ced9fdd docs: 新增多语言版本链接 2026-04-09 13:34:30 +08:00
click33 c30da2ceb9 docs: 文档优化 2026-04-04 16:46:12 +08:00
click33 b736f8a4f4 chore: 新增 MEMO sa-token pom.xml 依赖关系草稿 2026-04-03 12:08:27 +08:00
click33 1a90d8270a docs: 同步赞助者名单 2026-04-02 18:54:02 +08:00
click33 0d2f2d42bd docs: 同步最新博客列表 2026-04-02 17:05:41 +08:00
click33 d7ee47f59d docs: 同步最新赞助者名单 2026-04-02 16:26:42 +08:00
click33 7e5c56c8e4 docs: 同步最新博客列表 2026-04-02 16:12:37 +08:00
click33 c0f781a9fb feat(sso): 补全最新版 SSO NoSdk 模式实现 2026-04-01 05:25:48 +08:00
click33 47dff7059d docs: http -> https 2026-03-11 18:34:27 +08:00
click33 3bc7daea87 docs: 优化文档 2026-03-11 18:02:35 +08:00
click33 def8106fb4 docs: 优化 readme. 2026-03-11 17:59:06 +08:00
click33 3e3b505a3d docs: 优化描述 2026-03-11 14:44:24 +08:00
click33 f8557c24dd docs: 添加合适的 emoji 2026-03-11 14:21:13 +08:00
click33 368bbe9ce0 docs: 优化 readme 描述 2026-03-11 14:20:03 +08:00
click33 edc46ef182 docs: 添加合适的 emoji 2026-03-11 14:18:01 +08:00
click33 73df9def9e docs: 优化需求提交文案 2026-03-11 14:15:55 +08:00
click33 b81c61cc6f docs: OAuth2 章节新增对数据互通之后的 token 过期策略说明。
Closes #ICN069
2026-03-11 13:46:38 +08:00
click33 f6f420e107 docs: 优化了仓库 description 2026-03-11 13:36:08 +08:00
click33 41f3f1d4dd docs: 优化 readme 2026-03-11 13:30:39 +08:00
click33 9278251696 docs: 添加 readme 前言部分 2026-03-11 13:11:37 +08:00
click33 c0dbb1ecc8 docs: 合适的地方添加 ⚠️ 标识 2026-03-11 10:44:02 +08:00
click33 98a5f7934d docs: 优化 /start/example.md 章节文案 2026-03-11 08:34:38 +08:00
click33 6332c794aa chore: 更新 skill 描述文案 2026-03-10 17:56:20 +08:00
click33 7e37ef63cd refactor: 将 .cursor/skills/ 目录替换为更通用的 .agents/skills/ 目录下 2026-03-10 17:41:32 +08:00
click33 78599a3a87 docs: 增加 sa-token-alone-redis-by-spring-boot4 包使用说明 2026-03-10 16:24:01 +08:00
click33 c582e24ffa docs: 同步最新博客列表、赞助者名单 2026-03-09 17:01:39 +08:00
59 changed files with 903 additions and 1163 deletions
+38
View File
@@ -0,0 +1,38 @@
sa-token-bom 定义 sa-token 所有自身依赖的版本
sa-token-dependencies 定义 sa-token 所有第三方依赖版本,并继承 sa-token-bom
sa-token-spring-boot2-dependencies 定义 spring-boot2 相关依赖版本
sa-token-spring-boot3-dependencies 定义 spring-boot3 相关依赖版本
sa-token-spring-boot4-dependencies 定义 spring-boot4 相关依赖版本
sa-token-parent 父级 pom.xml 引入 sa-token-dependencies
sa-token-spring-boot-webmvc-reactor-v2v3v4-common import sa-token-spring-boot2-dependencies
sa-token-spring-boot-starter import sa-token-spring-boot2-dependencies
sa-token-reactor-spring-boot-starter import sa-token-spring-boot2-dependencies
sa-token-spring-boot-webmvc-v3v4-common import sa-token-spring-boot3-dependencies
引入 sa-token-spring-boot-webmvc-reactor-v2v3v4-common
sa-token-spring-boot3-starter import sa-token-spring-boot3-dependencies
sa-token-spring-boot4-starter import sa-token-spring-boot4-dependencies
引入 sa-token-spring-boot-webmvc-v3v4-common
A 定义了 <dependencyManagement>
B通过如下方式引入 A
<dependencies>
<dependency>
A
</dependency>
</dependencies>
那么 A 里定义的 <dependencyManagement>,在 B 里生效吗?
不生效
-87
View File
@@ -1,87 +0,0 @@
# 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) |
| `upgrade-version` | 将项目版本号从旧版本升级到新版本,批量修改 pom、常量、Demo 及文档 | 升级版本、修改版本号、version bump | [SKILL.md](upgrade-version/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 脚本快速扫描。
#### upgrade-version
将 Sa-Token 项目版本号从旧版本升级到新版本。批量修改根 POM、BOM、SaTokenConsts、所有 Demo 子项目 pom.xml 及文档(README、index.html、doc.html、new-version.md 等)中的版本引用。明确排除历史记录、@since 标注、更新日志等不应修改的文件。
## 快速使用
在 Cursor 对话中,直接描述你的需求即可自动触发相应 skill:
```
用户:帮我生成 commit message
→ 自动使用 commit-message skill 分析 git 变更并生成提交信息
用户:整理一下更新日志
→ 自动使用 organize-update-log skill 生成更新日志
用户:清理一下冗余 import
→ 自动使用 remove-redundancy-import skill 扫描并清理未使用的 import
用户:把版本从 v1.44.0 升级到 v1.45.0
→ 自动使用 upgrade-version skill 批量修改版本号
```
## 新增 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` 获取更多使用帮助
-110
View File
@@ -1,110 +0,0 @@
---
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
git diff
```
**必须包含的变更范围**
- **staged 变更**`git diff --staged`
- **unstaged 变更**:若无 staged,则用 `git diff` 查看工作区修改
- **未跟踪文件**`git status` 中的 Untracked files 也要纳入分析,生成 commit message 时需一并考虑
若存在未跟踪的新增文件(如新 skill、新配置等),应在 message 中体现,或给出「包含全部变更」与「仅已修改文件」两种方案供用户选择。
### 第二步:分析变更类型
根据变更内容选择 type 前缀:
| type | 适用场景 |
|------|----------|
| feat | 新增功能、新模块、新插件 |
| fix | 修复 bug、修正错误 |
| refactor | 重构、优化结构、重命名、移除冗余 |
| perf | 性能优化(与 refactor 区分:侧重性能) |
| docs | 文档更新、README、错别字、同步链接 |
| style | 代码格式调整(缩进、空格等,不影响逻辑) |
| chore | 构建配置、.gitignore、注释修复、依赖更新 |
| test | 单元测试、测试用例 |
| demo | 示例项目、demo 相关 |
| memo | 备忘录、内部记录 |
| revert | 回滚某次提交 |
| AI | AI 创建的 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 插件` |
| 修复 bug | `fix: 修复 StpUtil.getLoginIdByTokenNotThinkFreeze 方法缺少 static 的问题` |
| 性能优化 | `perf: 优化 StrFormatter 常量封装` |
| 重构模块 | `refactor: 重构模块依赖层级` |
| 移除冗余 | `refactor: 移除冗余导包` |
| 文档更新 | `docs: 同步最新文章列表、赞助者名单` |
| 注释修复 | `chore: 修复注释错别字` |
| 新增 skill | `AI: 新增 skills/commit-message/SKILL.md,用于根据 git 变更生成符合项目风格的 commit message` |
-97
View File
@@ -1,97 +0,0 @@
# 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 ,用于格式化整理版本更新日志信息
```
@@ -1,38 +0,0 @@
# 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
@@ -1,123 +0,0 @@
---
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 片段
@@ -1,108 +0,0 @@
# 更新日志格式参考
本文档提供 `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篇等)。」
@@ -1,76 +0,0 @@
---
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` 验证
@@ -1,63 +0,0 @@
# 冗余 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*;
```
@@ -1,96 +0,0 @@
#!/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()
-77
View File
@@ -1,77 +0,0 @@
---
name: upgrade-version
description: 将 Sa-Token 项目版本号升级到指定新版本。每次调用时先读取当前版本并提示用户,待用户输入目标版本后再执行批量修改。修改范围:pom.xml、核心常量、Demo 子项目及文档。当用户要求升级版本、修改版本号、或 version bump 时使用。
---
# Sa-Token 版本升级
将项目版本号升级到用户指定的新版本。每次调用时**先读取当前版本并询问目标版本**,用户确认后再批量修改核心构建、Demo 项目及文档中的版本引用。
## 使用时机
- 用户要求升级项目版本、修改版本号
- 用户说「版本从 vX.Y.Z 升级到 vX.Y.Z」「bump version」等
## 工作流程
### 第零步:询问目标版本(必须执行,不得跳过)
1. **读取当前版本**:从 `pom.xml``<revision>``SaTokenConsts.java``VERSION_NO` 中读取当前版本号
2. **提示用户**:明确告知「当前版本号是:xxx」
3. **等待输入**:询问「请输入要升级到的目标版本号(如 1.46.0):」
4. **确认后再执行**:**必须**等用户明确回复目标版本号后,才能执行后续修改步骤。若用户仅说「升级版本」而未给出目标版本,先完成本步骤再继续
### 第一步:核心构建配置(3 个文件)
使用「当前版本」「目标版本」进行替换:
| 文件 | 修改内容 |
|------|----------|
| `pom.xml` | `<revision>当前版本</revision>` → 目标版本 |
| `sa-token-bom/pom.xml` | `<revision>当前版本</revision>` → 目标版本 |
| `sa-token-core/.../SaTokenConsts.java` | `VERSION_NO = "v当前版本"``"v目标版本"` |
### 第二步:Demo 项目(sa-token-demo 下所有 pom.xml
-`<sa-token.version>当前版本</sa-token.version>` 改为目标版本
- **sa-token-demo-bom-import** 额外修改:`<dependencyManagement>``sa-token-bom``<version>当前版本</version>` → 目标版本
**查找方式**`grep "当前版本" sa-token-demo --output-mode files_with_matches` 定位所有需修改的 pom.xml。
### 第三步:文档(6 个文件)
| 文件 | 修改内容 |
|------|----------|
| `README.md` | 标题 `v当前版本``v目标版本`Maven 依赖 `<version>当前版本</version>` → 目标版本 |
| `sa-token-doc/README.md` | 标题 `v当前版本``v目标版本` |
| `sa-token-doc/index.html` | `<small>v当前版本</small>``v目标版本` |
| `sa-token-doc/doc.html` | `<sub>v当前版本</sub>``saTokenTopVersion = '当前版本'` → 目标版本 |
| `sa-token-doc/start/new-version.md` | 文案及 Maven 示例中的当前版本 → 目标版本 |
### 第四步:不修改的文件
以下为历史记录或示例,**保持原样**:
- `.cursor/skills/` 下的示例(format-reference.md、SKILL.md 等)
- `MEMO/` 下的历史备忘录
- `sa-token-core/.../*.java` 中的 `@since X.Y.Z`(表示 API 引入版本,不随发布升级)
- `sa-token-doc/more/update-log.md`:更新日志应**新增**新版本条目,而非修改旧条目
- `sa-token-doc/more/blog.md`:历史博客链接
## 替换规则
- **pom.xml**`当前版本``目标版本`
- **Java**`"v当前版本"``"v目标版本"`
- **HTML/MD**`v当前版本``当前版本` 按上下文分别替换为目标版本
## 执行顺序建议
1. 第零步:读取当前版本 → 提示用户 → 等待用户输入目标版本
2. 根 POM、sa-token-bom
3. SaTokenConsts.java
4. 批量修改 Demo pom.xml
5. 修改文档
## 验证
执行完成后,用 `grep "被替换的版本号"` 在项目根目录搜索,确认仅剩「不修改」列表中的文件仍含该版本(即修改已生效)。
+9
View File
@@ -18,3 +18,12 @@ sa-token-three-plugin/
sa-token-doc/big-file/
.flattened-pom.xml
.qoder
.soloncode/
.cursor
.agents
.github
AGENTS.md
CLAUDE.md
+60 -2
View File
@@ -19,10 +19,20 @@
---
### 📝 前言:
回望 2020 年初,我为 Sa-Token 提交第一行代码之际,彼时市面上 Java 缺少的不仅是一个简洁好用的鉴权框架,更是一整套清晰、自洽的权限架构设计思想。
因此,这几年间我将大量时间倾注在 Sa-Token 的文档编写,几乎每一章节、每一句话、每一个字都经过反复修改、精细打磨,以求做到最清晰、干练、易懂的表述。用心阅读文档,你学习到的将不止是 Sa-Token 框架本身,更是绝大多数场景下权限设计的最佳实践。
### 🛠️ Sa-Token 介绍
Sa-Token 是一个轻量级 Java 权限认证框架,目前拥有五大核心模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。
**还在手搓以下功能?Stop ⚠️ 交给 Sa-Token 吧!**
![sa-token-jss](https://sa-token.cc/big-file/index/intro/sa-token-jss--tran.png)
要在 SpringBoot 项目中使用 Sa-Token,你只需要在 pom.xml 中引入依赖:
@@ -36,7 +46,7 @@ Sa-Token 是一个轻量级 Java 权限认证框架,目前拥有五大核心
</dependency>
```
除了支持 SpringBoot2、Sa-Token 还为 SpringBoot3、Solon、JFinal 等常见 Web 框架提供集成包,做到真正的开箱即用。
除了支持 SpringBoot2、Sa-Token 还为 SpringBoot3/4、Solon、JFinal 等常见 Web 框架提供集成包,做到真正的开箱即用。
<details>
@@ -217,6 +227,33 @@ Sa-Token 采用 Apache-2.0 开源协议,承诺框架本身与在线文档永
我们将定期同步赞助者名单到在线文档展示。(您需要注意的一点是:该赞助仅为友情赞助,不提供任何商业交换)
**6、Sa-Token 是封装的 SpringSecurity 吗?是套壳 ApacheShiro 吗?**
不是。Sa-Token 不是一个后台模板,也不是针对 xx 框架的二次封装套壳,而是从 0 开始的纯血自研框架,核心包零依赖,完全自主可控的架构内核 + 众多主流框架的集成适配。
#### 证书 ⭐ 奖杯 🏆 荣誉展示
<table align="center">
<tr>
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/gvp.jpg" title="GVP - Gitee 最有价值开源项目" /></td>
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/g-star.jpg" title="GitCode G-Star 优质开源项目" /></td>
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/osc-2021.jpg" title="OSCHINA 2021 人气指数 TOP 30 开源项目" /></td>
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/osc-2022--chang.jpg" title="OSCHINA 2022 年度最火热中国开源项目社区" /></td>
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/kexin.jpg" title="可信开源社区共同体预备成员" /></td>
</tr>
<tr>
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/gitee-star-5000.jpg" title="Gitee 5000 star 专属奖杯" /></td>
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/gitee-2025--chang.jpg" title="Gitee 2025年度开源项目 Web应用开发 Top 2" /></td>
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/dromara.jpg" title="Dromara 组织顶尖项目(之一)" /></td>
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/kaifangyuanzi2--chang.jpg" title="开放原子基金会2023快速成长开源项目" /></td>
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/dromara-2024-tzds.jpg" title="Dromara 荣获《2024中国互联网发展创新与投资大赛(开源)》二等奖" /></td>
</tr>
</table>
### 🚀 优秀开源集成案例
@@ -232,6 +269,17 @@ Sa-Token 采用 Apache-2.0 开源协议,承诺框架本身与在线文档永
还有更多优秀开源案例无法逐一展示,请参考:[Awesome-Sa-Token](https://gitee.com/sa-token/awesome-sa-token)
### 🌍 其它语言版本
Sa-Token 社区成员贡献了多语言实现版本:
- Rust 版本:[https://gitee.com/sa-tokens/sa-token-rust](https://gitee.com/sa-tokens/sa-token-rust)
- Go 版本:[https://gitee.com/sa-tokens/sa-token-go](https://gitee.com/sa-tokens/sa-token-go)
- PHP 版本:[https://gitee.com/jinan-jimeng-network_0/real-token](https://gitee.com/jinan-jimeng-network_0/real-token)
我们诚邀对上述语言较为熟练的开发者,一起建设相关版本。🤝
### 🔗 友情链接
- [[ OkHttps ]](https://gitee.com/ejlchina-zhxu/okhttps):轻量级 http 通信框架,API无比优雅,支持 WebSocket、Stomp 协议
- [[ Forest ]](https://gitee.com/dromara/forest):声明式与编程式双修,让天下没有难以发送的 HTTP 请求
@@ -252,6 +300,16 @@ Sa-Token 采用 Apache-2.0 开源协议,承诺框架本身与在线文档永
### 📚 示例大全
**我们为框架几乎所有技术点均单独制作了对应的集成示例,此压缩包共计 60+ Demo**:涵盖 Sa-Token 登录认证、权限认证、SSO 单点登录、OAUth2 统一认证、微服务鉴权、API Key 认证、JWT集成、跨系统调用参数签名校验 等鉴权认证的方方面面。
下载地址:[https://sa-token.cc/doc.html#/more/download-demos](https://sa-token.cc/doc.html#/more/download-demos)
<img class="s-w" src="https://sa-token.cc/big-file/contact/show/sa-token-demos-pre-liubai.png" />
### 💬 交流群
<!-- QQ交流群:685792424 [点击加入](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Y05Ld4125W92YSwZ0gA8e3RhG9Q4Vsfx&authKey=IomXuIuhP9g8G7l%2ByfkrRsS7i%2Fna0lIBpkTXxx%2BQEaz0NNEyJq00kgeiC4dUyNLS&noverify=0&group_code=685792424)-->
@@ -267,7 +325,7 @@ 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="微信群" />
<img class="s-w" src="https://sa-token.cc/big-file/contact/show/wx-group-show3--liubai.png" style="max-width: 50%;" alt="微信群" />
加入群聊的好处:
+1 -1
View File
@@ -12,7 +12,7 @@
<!-- 项目介绍 -->
<name>sa-token</name>
<description>A Java Web lightweight authority authentication framework, comprehensive function, easy to use</description>
<description>An open-source, free, and one-stop Java authentication framework that makes authentication simple and elegant!</description>
<url>https://github.com/dromara/sa-token</url>
+1 -1
View File
@@ -13,7 +13,7 @@
<name>sa-token-core</name>
<artifactId>sa-token-core</artifactId>
<description>A Java Web lightweight authority authentication framework, comprehensive function, easy to use</description>
<description>An open-source, free, and one-stop Java authentication framework that makes authentication simple and elegant!</description>
<dependencies>
<!-- Zero Dependence -->
@@ -47,6 +47,18 @@ sa-token:
push-url: http://sa-sso-client1.com:9003/sso/pushC
# 接口调用秘钥 (如果不配置则使用全局默认秘钥)
secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# 应用 sso-client3-nosdk:采用 NoSdk 模式对接 (不依赖 Sa-Token 客户端 SDK,手动实现协议)
sso-client3-nosdk:
# 应用名称
client: sso-client3-nosdk
# 允许授权地址
allow-url: "*"
# 是否接收消息推送
is-push: true
# 消息推送地址
push-url: http://sa-sso-client1.com:9004/sso/pushC
# 接口调用秘钥 (如果不配置则使用全局默认秘钥)
secret-key: SSO-C3-NoSdk-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# 应用 sso-client3-resdk:采用 ReSdk 模式对接
sso-client3-resdk:
# 应用名称
@@ -16,9 +16,6 @@ public class SaSsoClientNoSdkApplication {
System.out.println("测试访问应用端三: http://sa-sso-client3.com:9004");
System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456");
System.out.println();
System.err.println("自 v1.43.0 版本起,Sa-Token SSO 不再维护 NoSdk 示例,此项目仅做留档");
System.err.println("如您需要非 Sa-Token 技术栈项目接入 SSO-Server 认证中心,请参考 ReSdk 版本示例");
}
}
@@ -1,22 +1,22 @@
package com.pj.sso;
import java.io.IOException;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.pj.sso.util.AjaxJson;
import com.pj.sso.util.MyHttpSessionHolder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.pj.sso.util.AjaxJson;
import com.pj.sso.util.MyHttpSessionHolder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
/**
* SSO Client端 Controller
* SSO Client端 Controller
* @author click33
*/
@RestController
@@ -25,162 +25,166 @@ public class SsoClientController {
// SSO-Client端:首页
@RequestMapping("/")
public String index(HttpSession session) {
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话登录账号:" + session.getAttribute("userId") + "</p>" +
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a>" +
" <a href='/sso/logout?back=' + + encodeURIComponent(location.href);>注销</a>" +
Object userId = session.getAttribute("userId");
String str = "<h2>Sa-Token SSO-Client 应用端 (模式三-NoSdk)</h2>" +
"<p>当前会话是否登录:" + (userId != null) + " (" + userId + ")</p>" +
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a>" +
" <a href='/sso/logout?back=' + + encodeURIComponent(location.href);>注销</a>" +
" <a href='/sso/myInfo' target=\"_blank\">获取资料</a></p>";
return str;
}
// SSO-Client端:单点登录地址
// SSO-Client端:单点登录地址
@RequestMapping("/sso/login")
public Object ssoLogin(String ticket, @RequestParam(defaultValue = "/") String back,
public Object ssoLogin(String ticket, @RequestParam(defaultValue = "/") String back,
HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException {
// 如果已经登录,则直接返回
if(session.getAttribute("userId") != null) {
// 如果已经登录,则直接返回
if (session.getAttribute("userId") != null) {
response.sendRedirect(back);
return null;
}
/*
* 此时有两种情况:
* 情况1ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
* 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
* 此时有两种情况:
* 情况1ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
* 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
*/
if(ticket == null) {
String currUrl = request.getRequestURL().toString();
String clientLoginUrl = currUrl + "?back=" + SsoRequestUtil.encodeUrl(back);
String serverAuthUrl = SsoRequestUtil.authUrl + "?redirect=" + clientLoginUrl;
if (ticket == null) {
// ------- 情况 1
// 当前 url,形如:http://sso-client.com/sso/login?back=xxx
String clientLoginUrl = request.getRequestURL().toString() + "?back=" + SsoRequestUtil.encodeUrl(back);
// 最终授权地址,形如:http://sso-server.com/sso/auth?client=xxx&redirect=http://sso-client.com/sso/login?back=xxx
String serverAuthUrl = SsoRequestUtil.authUrl
+ "?client=" + SsoRequestUtil.clientId
+ "&redirect=" + clientLoginUrl;
response.sendRedirect(serverAuthUrl);
return null;
} else {
// 获取当前 client 端的单点注销回调地址
String ssoLogoutCall = "";
if(SsoRequestUtil.isSlo) {
ssoLogoutCall = request.getRequestURL().toString().replace("/sso/login", "/sso/logoutCall");
}
// 校验 ticket
String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳
String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串
String sign = SsoRequestUtil.getSignByTicket(ticket, ssoLogoutCall, timestamp, nonce); // 参数签名
String checkUrl = SsoRequestUtil.checkTicketUrl +
"?timestamp=" + timestamp +
"&nonce=" + nonce +
"&sign=" + sign +
"&ticket=" + ticket +
"&ssoLogoutCall=" + ssoLogoutCall;
AjaxJson result = SsoRequestUtil.request(checkUrl);
// 200 代表校验成功
if(result.getCode() == 200 && SsoRequestUtil.isEmpty(result.getData()) == false) {
// 登录上
Object loginId = result.getData();
session.setAttribute("userId", loginId);
// ------- 情况 2
// 构建 checkTicket 请求参数,以 ticket 查询 userId
Map<String, String> params = new LinkedHashMap<>();
params.put("msgType", "checkTicket");
params.put("client", SsoRequestUtil.clientId);
params.put("ticket", ticket);
SsoSignUtil.addSignParams(params);
String pushUrl = SsoRequestUtil.buildUrl(SsoRequestUtil.pushSUrl, params);
AjaxJson result = SsoRequestUtil.request(pushUrl);
// 200 代表校验成功
if (result.getCode() == 200 && !SsoRequestUtil.isEmpty(result.getData())) {
// 登录上
session.setAttribute("userId", result.getData());
// 返回 back 地址
response.sendRedirect(back);
return null;
} else {
// 将 sso-server 回应的消息作为异常抛出
// 将 sso-server 回应的消息作为异常抛出
throw new RuntimeException(result.getMsg());
}
}
}
// SSO-Client端:单点注销地址
@RequestMapping("/sso/logout")
public Object ssoLogout(@RequestParam(defaultValue = "/") String back,
public Object ssoLogout(@RequestParam(defaultValue = "/") String back,
HttpServletResponse response, HttpSession session) throws IOException {
// 如果未登录,则无需注销
if(session.getAttribute("userId") == null) {
// 如果未登录,则无需注销
if (session.getAttribute("userId") == null) {
response.sendRedirect(back);
return null;
}
// 调用 sso-server 认证中心单点注销API
Object loginId = session.getAttribute("userId"); // 账号id
String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳
String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串
String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce); // 参数签名
String url = SsoRequestUtil.sloUrl +
"?loginId=" + loginId +
"&timestamp=" + timestamp +
"&nonce=" + nonce +
"&sign=" + sign;
AjaxJson result = SsoRequestUtil.request(url);
// 校验响应状态码,200 代表成功
if(result.getCode() == 200) {
// 极端场景下,sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀
}
// 调用 sso-server 认证中心单点注销 API
Object loginId = session.getAttribute("userId");
Map<String, String> params = new LinkedHashMap<>();
params.put("msgType", "signout");
params.put("client", SsoRequestUtil.clientId);
params.put("loginId", String.valueOf(loginId));
SsoSignUtil.addSignParams(params);
String pushUrl = SsoRequestUtil.buildUrl(SsoRequestUtil.pushSUrl, params);
AjaxJson result = SsoRequestUtil.request(pushUrl);
// 校验响应状态码,200 代表成功
if (result.getCode() == 200) {
// 极端场景下,sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀
session.removeAttribute("userId");
// 返回 back 地址
response.sendRedirect(back);
return null;
} else {
// 将 sso-server 回应的消息作为异常抛出
// 将 sso-server 回应的消息作为异常抛出
throw new RuntimeException(result.getMsg());
}
}
// SSO-Client端:单点注销回调地址
@RequestMapping("/sso/logoutCall")
public Object ssoLogoutCall(String loginId, String autoLogout, String timestamp, String nonce, String sign) {
// 校验签名
String calcSign = SsoRequestUtil.getSignByLogoutCall(loginId, autoLogout, timestamp, nonce);
if(calcSign.equals(sign) == false) {
System.out.println("无效签名,拒绝应答:" + sign);
return AjaxJson.getError("无效签名,拒绝应答" + sign);
// SSO-Server 端消息推送接收地址(单点注销回调等)
@RequestMapping("/sso/pushC")
public Object ssoPushC(HttpServletRequest request) {
// 将请求参数收集为 Map<String, String>
Map<String, String> params = new LinkedHashMap<>();
for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
params.put(entry.getKey(), entry.getValue()[0]);
}
// 注销这个账号id
for (HttpSession session: MyHttpSessionHolder.sessionList) {
Object userId = session.getAttribute("userId");
if(Objects.equals(String.valueOf(userId), loginId)) {
session.removeAttribute("userId");
// 校验签名
if (!SsoSignUtil.verifySign(params)) {
return AjaxJson.getError("无效签名,拒绝应答");
}
// 按 msgType 分发处理
String msgType = params.get("msgType");
// 单点注销回调
if ("logoutCall".equals(msgType)) {
// 注销这个账号 id 在本 client 端的所有会话
String loginId = params.get("loginId");
for (HttpSession session : MyHttpSessionHolder.sessionList) {
Object userId = session.getAttribute("userId");
if (Objects.equals(String.valueOf(userId), loginId)) {
session.removeAttribute("userId");
}
}
return AjaxJson.getSuccess("账号id=" + loginId + " 注销成功");
}
return AjaxJson.getSuccess("账号id=" + loginId + " 注销成功");
// 其它消息类型
// if("xxx".equals(msgType)) {
// // 处理 xxx 消息
// }
return AjaxJson.getError("未知消息类型:" + msgType);
}
// 查询我的账号信息 (调用此接口的前提是 sso-server 端开放了 /sso/userinfo 路由)
// 查询我的账号信息(调用 sso-server 端 userinfo 消息处理器)
@RequestMapping("/sso/myInfo")
public Object myInfo(HttpSession session) {
// 如果尚未登录
if(session.getAttribute("userId") == null) {
// 如果尚未登录
if (session.getAttribute("userId") == null) {
return "尚未登录,无法获取";
}
// 组织 url 参数
Object loginId = session.getAttribute("userId"); // 账号id
String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳
String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串
String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce); // 参数签名
String url = SsoRequestUtil.getDataUrl +
"?loginId=" + loginId +
"&timestamp=" + timestamp +
"&nonce=" + nonce +
"&sign=" + sign;
AjaxJson result = SsoRequestUtil.request(url);
// 返回给前端
Object loginId = session.getAttribute("userId");
Map<String, String> params = new LinkedHashMap<>();
params.put("msgType", "userinfo");
params.put("client", SsoRequestUtil.clientId);
params.put("loginId", String.valueOf(loginId));
SsoSignUtil.addSignParams(params);
String pushUrl = SsoRequestUtil.buildUrl(SsoRequestUtil.pushSUrl, params);
AjaxJson result = SsoRequestUtil.request(pushUrl);
// 返回给前端
return result;
}
// 全局异常拦截
// 全局异常拦截
@ExceptionHandler
public AjaxJson handlerException(Exception e) {
e.printStackTrace();
e.printStackTrace();
return AjaxJson.getError(e.getMessage());
}
}
@@ -5,61 +5,47 @@ import com.pj.sso.util.AjaxJson;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.Map;
import java.util.Random;
/**
* 封装一些 sso 共用方法
*
* 封装一些 sso 共用方法
*
* @author click33
* @since 2022-4-30
*/
public class SsoRequestUtil {
/**
* SSO-Server端主机地址
* SSO-Server 端主机地址
*/
public static String serverUrl = "http://sa-sso-server.com:9000";
/**
* SSO-Server端 统一认证地址
* SSO-Server 端统一认证地址
*/
public static String authUrl = serverUrl + "/sso/auth";
/**
* SSO-Server端 ticket校验地址
* SSO-Server 端统一消息推送地址(ticket校验、单点注销、获取用户信息等均通过此入口)
*/
public static String checkTicketUrl = serverUrl + "/sso/checkTicket";
public static String pushSUrl = serverUrl + "/sso/pushS";
/**
* 单点注销地址
* 当前应用的客户端标识(需与 sso-server 端 clients 配置一致)
*/
public static String sloUrl = serverUrl + "/sso/signout";
public static String clientId = "sso-client3-nosdk";
/**
* SSO-Server端 查询userinfo地址
* 接口调用秘钥(需与 sso-server 端对应 client 配置一致)
*/
public static String getDataUrl = serverUrl + "/sso/getData";
public static String secretKey = "SSO-C3-NoSdk-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor";
// -------------------------- 工具方法
/**
* 打开单点注销功能
*/
public static boolean isSlo = true;
/**
* 接口调用秘钥
*/
public static String secretKey = "kQwIOrYvnXmSDkwEiFngrKidMcdrgKor";
// -------------------------- 工具方法
/**
* 发出请求,并返回 SaResult 结果
* @param url 请求地址
* @return 返回的结果
* 发出请求,并返回 AjaxJson 结果
* @param url 请求地址(含查询参数)
* @return 返回的结果
*/
public static AjaxJson request(String url) {
Map<String, Object> map = Forest.post(url).executeAsMap();
@@ -67,75 +53,28 @@ public class SsoRequestUtil {
}
/**
* 根据参数计算签名
* @param loginId 账号id
* @param timestamp 当前时间戳,13位
* @param nonce 随机字符串
* @return 签名
* 将参数 Map 拼接到 baseUrl 后面(值进行 URL 编码),返回完整 URL
* @param baseUrl 基础 URL
* @param params 请求参数
* @return 拼接后的完整 URL
*/
public static String getSign(Object loginId, String timestamp, String nonce) {
return md5("loginId=" + loginId + "&nonce=" + nonce + "&timestamp=" + timestamp + "&key=" + secretKey);
}
// 单点注销回调时构建签名
public static String getSignByLogoutCall(Object loginId, String autoLogout, String timestamp, String nonce) {
return md5("autoLogout=" + autoLogout + "&loginId=" + loginId + "&nonce=" + nonce + "&timestamp=" + timestamp + "&key=" + secretKey);
}
// 校验ticket 时构建签名
public static String getSignByTicket(String ticket, String ssoLogoutCall, String timestamp, String nonce) {
return md5("nonce=" + nonce + "&ssoLogoutCall=" + ssoLogoutCall + "&ticket=" + ticket + "&timestamp=" + timestamp + "&key=" + secretKey);
public static String buildUrl(String baseUrl, Map<String, String> params) {
StringBuilder sb = new StringBuilder(baseUrl).append("?");
for (Map.Entry<String, String> entry : params.entrySet()) {
sb.append(entry.getKey()).append("=").append(encodeUrl(entry.getValue())).append("&");
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
/**
* 指定元素是否为null或者空字符串
* @param str 指定元素
* @param str 指定元素
* @return 是否为null或者空字符串
*/
public static boolean isEmpty(Object str) {
return str == null || "".equals(str);
}
/**
* md5加密
* @param str 指定字符串
* @return 加密后的字符串
*/
public static String md5(String str) {
str = (str == null ? "" : str);
char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
try {
byte[] btInput = str.getBytes();
MessageDigest mdInst = MessageDigest.getInstance("MD5");
mdInst.update(btInput);
byte[] md = mdInst.digest();
int j = md.length;
char[] strA = new char[j * 2];
int k = 0;
for (byte byte0 : md) {
strA[k++] = hexDigits[byte0 >>> 4 & 0xf];
strA[k++] = hexDigits[byte0 & 0xf];
}
return new String(strA);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 生成指定长度的随机字符串
*
* @param length 字符串的长度
* @return 一个随机字符串
*/
public static String getRandomString(int length) {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
/**
* URL编码
@@ -0,0 +1,102 @@
package com.pj.sso;
import java.security.MessageDigest;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
/**
* SSO 签名工具类:签名生成、注入与校验
*
* @author click33
*/
public class SsoSignUtil {
// -------------------------- 签名方法
/**
* 计算签名:将 params(排除 sign 字段)按 key 字典序升序排列,
* 拼接为 k=v&k=v 后追加 &key={secretKey},整体 MD5
* @param params 请求参数(不含 sign
* @return 签名值
*/
public static String computeSign(Map<String, String> params) {
TreeMap<String, String> sorted = new TreeMap<>(params);
sorted.remove("sign");
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : sorted.entrySet()) {
if (sb.length() > 0) sb.append("&");
sb.append(entry.getKey()).append("=").append(entry.getValue());
}
sb.append("&key=").append(SsoRequestUtil.secretKey);
return md5(sb.toString());
}
/**
* 向参数 Map 中注入 timestamp、nonce、sign 三个签名参数
* @param params 请求参数(已填好业务参数,此方法自动追加签名参数)
*/
public static void addSignParams(Map<String, String> params) {
params.put("timestamp", String.valueOf(System.currentTimeMillis()));
params.put("nonce", getRandomString(20));
params.put("sign", computeSign(params));
}
/**
* 校验请求中的 sign 参数是否合法
* @param params 包含 sign 的请求参数
* @return 签名是否合法
*/
public static boolean verifySign(Map<String, String> params) {
String sign = params.get("sign");
if (sign == null) return false;
return sign.equals(computeSign(params));
}
// -------------------------- 基础工具
/**
* MD5 加密
* @param str 指定字符串
* @return 加密后的字符串
*/
public static String md5(String str) {
str = (str == null ? "" : str);
char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
try {
byte[] btInput = str.getBytes();
MessageDigest mdInst = MessageDigest.getInstance("MD5");
mdInst.update(btInput);
byte[] md = mdInst.digest();
int j = md.length;
char[] strA = new char[j * 2];
int k = 0;
for (byte byte0 : md) {
strA[k++] = hexDigits[byte0 >>> 4 & 0xf];
strA[k++] = hexDigits[byte0 & 0xf];
}
return new String(strA);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 生成指定长度的随机字符串
* @param length 字符串的长度
* @return 一个随机字符串
*/
public static String getRandomString(int length) {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(str.charAt(random.nextInt(62)));
}
return sb.toString();
}
}
+29 -1
View File
@@ -20,7 +20,9 @@
为了保证新同学不迷路,请允许我唠叨一下:无论您从何处看到本篇文章,最新开发文档永远在:[https://sa-token.cc](https://sa-token.cc)
建议收藏在浏览器书签,如果您已经身处本网站下,则请忽略此条说明。
本文档将会尽力讲解每个功能的设计原因、应用场景,用心阅读文档,你学习到的将不止是 `Sa-Token` 框架本身,更是绝大多数场景下权限设计的最佳实践
回望 2020 年初,我为 Sa-Token 提交第一行代码之际,彼时市面上 Java 缺少的不仅是一个简洁好用的鉴权框架,更是一整套清晰、自洽的权限架构设计思想
因此,这几年间我将大量时间倾注在 Sa-Token 的文档编写,几乎每一章节、每一句话、每一个字都经过反复修改、精细打磨,以求做到最清晰、干练、易懂的表述。用心阅读文档,你学习到的将不止是 Sa-Token 框架本身,更是绝大多数场景下权限设计的最佳实践。
## 🛠️ Sa-Token 介绍
@@ -166,6 +168,22 @@ Sa-Token 采用 Apache-2.0 开源协议,承诺框架本身与在线文档永
我们将定期同步赞助者名单到在线文档展示。(您需要注意的一点是:该赞助仅为友情赞助,不提供任何商业交换)
**6、Sa-Token 是封装的 SpringSecurity 吗?是套壳 ApacheShiro 吗?**
不是。Sa-Token 不是一个后台模板,也不是针对 xx 框架的二次封装套壳,而是从 0 开始的纯血自研框架,核心包零依赖,完全自主可控的架构内核 + 众多主流框架的集成适配。
## 🌍 其它语言版本
Sa-Token 社区成员贡献了多语言实现版本:
- Rust 版本:[https://gitee.com/sa-tokens/sa-token-rust](https://gitee.com/sa-tokens/sa-token-rust)
- Go 版本:[https://gitee.com/sa-tokens/sa-token-go](https://gitee.com/sa-tokens/sa-token-go)
- PHP 版本:[https://gitee.com/jinan-jimeng-network_0/real-token](https://gitee.com/jinan-jimeng-network_0/real-token)
我们诚邀对上述语言较为熟练的开发者,一起建设相关版本。🤝
## 📈 开源仓库 Star 趋势
@@ -184,6 +202,16 @@ Sa-Token 采用 Apache-2.0 开源协议,承诺框架本身与在线文档永
## 📚 示例大全
**我们为框架几乎所有技术点均单独制作了对应的集成示例,此压缩包共计 60+ Demo**:涵盖 Sa-Token 登录认证、权限认证、SSO 单点登录、OAUth2 统一认证、微服务鉴权、API Key 认证、JWT集成、跨系统调用参数签名校验 等鉴权认证的方方面面。
下载地址:[Sa-Token 集成示例大全下载](/more/download-demos)
<img class="s-w" src="/big-file/contact/show/sa-token-demos-pre.png" />
## 💬 交流群
加入 Sa-Token 框架 QQ、微信讨论群:[点击加入](/more/join-group.md)
+2 -1
View File
@@ -6,7 +6,7 @@
- [在 WebFlux 环境集成](/start/webflux-example)
- [在 Solon 环境集成](/start/solon-example)
- [其它环境集成示例](/start/download)
- [Sa-Token 集成示例大全下载](/more/download-demos)
- **基础**
- [登录认证](/use/login-auth)
@@ -125,6 +125,7 @@
- [框架博客](/more/blog)
- [推荐公众号](/more/tj-gzh)
- [加入讨论群](/more/join-group)
<!-- - [下载 demo 示例](/more/download-demos) -->
- [Sa-Token 内容合作群](/more/content-cooperation)
- [赞助 Sa-Token](/more/sa-token-donate)
- [需求提交](/more/demand-commit)
+98
View File
@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>点击 Star 支持我们</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
background: #fff;
}
.container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform-origin: 100% 0;
transform: scale(1);
}
.container img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.img-before {
z-index: 2;
}
.img-after {
z-index: 1;
}
</style>
</head>
<body>
<div class="container">
<img class="img-before" src="https://sa--file.oss-cn-beijing.aliyuncs.com/sa-token/doc/a/star-guide/gitee-star-guide-1.png" alt="img 1">
<img class="img-after" src="https://sa--file.oss-cn-beijing.aliyuncs.com/sa-token/doc/a/star-guide/gitee-star-guide-2.png" alt="img 2">
<!-- <img class="img-before" src="https://sa--file.oss-cn-beijing.aliyuncs.com/sa-token/doc/a/star-guide/github-star-guide-1.png" alt="img 1">
<img class="img-after" src="https://sa--file.oss-cn-beijing.aliyuncs.com/sa-token/doc/a/star-guide/github-star-guide-2.png" alt="img 2"> -->
<!-- <img class="img-before" src="https://sa--file.oss-cn-beijing.aliyuncs.com/sa-token/doc/a/star-guide/atomgit-star-guide-1.png" alt="img 1">
<img class="img-after" src="https://sa--file.oss-cn-beijing.aliyuncs.com/sa-token/doc/a/star-guide/atomgit-star-guide-2.png" alt="img 2"> -->
</div>
<script>
const container = document.querySelector('.container');
const imgBefore = document.querySelector('.img-before');
function startCycle() {
// ============================================================
// 第3秒末 → 第0秒初:暴力瞬间复位,无任何过渡
// 原理:先在当前帧写入 transition:none
// requestAnimationFrame 保证下一帧浏览器已提交该样式,
// 再在下一帧改变属性值,此时过渡已关闭,改值不会触发动画。
// ============================================================
container.style.transition = 'none';
imgBefore.style.transition = 'none';
requestAnimationFrame(() => {
// ← 此处已是新的一帧,transition:none 确保已生效
container.style.transform = 'scale(1)'; // 瞬间复位缩放
imgBefore.style.opacity = '1'; // 瞬间复位透明度
// 以下各阶段动画均有过渡效果
// 0.5s~1.5s:放大(1s 过渡)
setTimeout(() => {
container.style.transition = 'transform 1s ease-in-out';
container.style.transform = 'scale(2.5)';
}, 500);
// 1.5s~2.0s:仅淡出图片1,窗口保持放大不动
setTimeout(() => {
imgBefore.style.transition = 'opacity 0.5s ease-in-out';
imgBefore.style.opacity = '0';
}, 1500);
// 第3s:进入下一轮,再次暴力复位(scale 和 opacity 同时瞬间归位)
setTimeout(startCycle, 3000);
});
}
startCycle();
</script>
</body>
</html>
+78
View File
@@ -0,0 +1,78 @@
// 判断当前是否已弹出
function isShowStarGuide() {
// 非PC端不检查
if(document.body.offsetWidth < 800) {
console.log('small screen ... isShowStarGuide ');
return;
}
// 检查成功后,多少天不再检查
const alertAllowDisparity = 1000 * 60 * 60 * 24 * 30; // 30天
// const allowDisparity = 1000 * 10;
// 判断是否近期已经判断过了
const SAVE_KEY = 'isShowStarGuide';
try{
const showAlert = localStorage[SAVE_KEY];
if(showAlert) {
// 记录 star 的时间,和当前时间的差距
const disparity = new Date().getTime() - parseInt(showAlert);
// 差距小于一月,不再检测,大于一月,再检测一下
if(disparity < alertAllowDisparity) {
console.log('checked ... wj ');
return;
}
}
}catch(e){
console.error(e);
}
// 本次打开页面的内存内已经弹出了的话,也不再弹了
if(window.isYtcXsjfkasjda3232) {
return;
}
window.isYtcXsjfkasjda3232 = true;
// 弹出弹框,邀请填写
const tipStr = `
<div style="color: #000;">
<div>
<iframe src="./a/star-guide/index.html"
style="width:100%; height:250px; border:2px solid #ddd; border-radius: 2px;"></iframe>
</div>
<p style="margin-top: 18px;">
<b style="color: green;">Sa-Token 采用 Apache-2.0 开源协议,承诺框架本身与在线文档永久免费开放</b>。
</p>
<p style="margin-top: 14px;">
如果 Sa-Token 帮助到了你,希望你可以为项目点个 star ⭐,这对我们非常重要,感谢支持!
</p>
</div>
`;
const index = layer.confirm(tipStr, {
title: '支持项目',
btn: ['确定'],
area: '570px',
offset: '10%',
cancel: function() {
localStorage[SAVE_KEY] = new Date().getTime();
}
},
// 点击确定
function(index) {
layer.close(index);
localStorage[SAVE_KEY] = new Date().getTime();
open('https://gitee.com/dromara/sa-token');
// open('https://github.com/dromara/sa-token');
// open('https://atomgit.com/dromara/sa-token');
},
// 点击取消
function(){
localStorage[SAVE_KEY] = new Date().getTime();
}
);
}
isShowStarGuide();
+12 -34
View File
@@ -116,6 +116,7 @@
</a>
<div class="zk-context">
<div>
<a href="https://www.bilibili.com/video/BV1PF9QBXEet/?p=53" target="_blank">抓蛙师(23集)</a>
<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>
@@ -200,18 +201,23 @@
</div>
<!-- 万维广告div -->
<div class="wwads-cn wwads-horizontal" data-id="88"
style="min-height: 0px; border: 1px #eee solid; margin-bottom: 12px;"></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">
<a href="https://sa-pro.yun94.cn/index-st.html?way=st_qyzx" target="_blank">
❤️ Sa-Token 企业开发咨询服务
</a>
</div>
<!-- ew-wa -->
<div class="ew-wa">
<p>
<a href="https://pan.quark.cn/s/d5abda720e88" target="_blank">离线版文档</a>
<a href="https://pan.quark.cn/s/fea7e5ec72ee" target="_blank">历史所有版本文档</a>
<a href="doc.html#/more/download-demos">Demo 示例大全下载</a>
</p>
</div>
<!-- ew-wa -->
@@ -418,7 +424,7 @@
</script>
<!-- 万维广告 -->
<script data-mode="hash" type="text/javascript" src="https://cdn.wwads.cn/js/makemoney.js" async></script>
<!-- <script data-mode="hash" type="text/javascript" src="https://cdn.wwads.cn/js/makemoney.js" async></script> -->
<!-- 百度统计 -->
<script>
@@ -442,35 +448,6 @@
<!-- 小助手提示 -->
<script>
$('.help-btn').click(function() {
var str = `
<div class="xiaozhushou-intro">
<h2>报错了?搞不懂?别急、莫慌</h2>
<div style="margin-top: 20px; color: green;">
<p>👉 你的问题可能很多人都碰到过了!这有一份高频报错速查文档:<a href="doc.html#/more/common-questions" onclick="layer.closeAll()">常见问题排查</a></p>
<p>👉 几乎每个功能点都有对应的最简示例 Demo,或许可以给你一份参考:<a href="https://gitee.com/dromara/sa-token/tree/master/sa-token-demo" target="_blank" >sa-token-demo</a></p>
<p>👉 复杂功能玩不转?来看看这些优秀开源案例是怎么集成 Sa-Token 的:<a href="https://gitee.com/sa-tokens/awesome-sa-token" target="_blank" >awesome-sa-token</a></p>
</div>
<div style="margin-top: 20px; color: red;">上述方案没有解决你的问题?那你可以来“麻烦”一下我们的小助手了:</div>
<p>1、你在使用 Sa-Token 时遇到任何技术难题,可以向 < sa-token 小助手 > 求助咨询。</p>
<p>2、该小助手不属于商业运营,求助咨询完全免费。</p>
<p>3、目前该小助手属于试运营阶段,每天只能提供大约 1 小时的求助时间。</p>
<p>4、根据运营效果反馈,我们日后可能会提高求助时间,但也可能关闭此功能。</p>
<p>5、该小助手由企业微信提供平台支持,感谢企业微信。</p>
<p>6、不是 AI 是真人,不是 AI 是真人,不是 AI 是真人,重说三!</p>
<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="/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',
offset: '7%',
})
})
// setTimeout(function(){
// try{
// // 给个小提示
@@ -537,5 +514,6 @@
</script> -->
<script src="./a/star-guide/star-guide.js"></script>
</body>
</html>
@@ -1493,7 +1493,7 @@ public AjaxJson doLogin(String username, String password) {
// 登录
StpUtil.login(user.getId());
StpUtil.getSession().set("user", user);
return AjaxJson.getSuccess("登录成功").set("satoken", StpUtil.getTokenValue()); // 关键代码
return AjaxJson.getSuccess("登录成功").set("satoken", StpUtil.getTokenValue()); // ⚠️ 关键代码
}
```
@@ -1580,8 +1580,8 @@ public AjaxJson doLogin(String username, String password) {
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username, password));
String token = subject.getSession().getId().toString(); // 关键代码
return AjaxJson.getSuccess("登录成功!").set("token", token); // 关键代码
String token = subject.getSession().getId().toString(); // ⚠️ 关键代码
return AjaxJson.getSuccess("登录成功!").set("token", token); // ⚠️ 关键代码
} catch (AuthenticationException e) {
e.printStackTrace();
return AjaxJson.getError(e.getMessage());
+4
View File
@@ -62,6 +62,7 @@
</a>
<div class="zk-context">
<div>
<a href="https://www.bilibili.com/video/BV1PF9QBXEet/?p=53" target="_blank">抓蛙师(23集)</a>
<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>
@@ -1347,6 +1348,9 @@
</script>
<script src="./a/star-guide/star-guide.js"></script>
<!-- 自定义滚动条颜色 -->
<!-- <style>
/* 自定义body滚动条样式 */
+1 -1
View File
@@ -24,7 +24,7 @@
- 方案一:性能消耗太大,不太考虑
- 方案二:需要从网关处动手,与框架无关
- 方案三:Sa-Token 整合`Redis`非常简单,详见章节:[集成 Redis](/up/integ-redis)
- 方案四:详见官方仓库中 Sa-Token 整合`jwt`的示例
- 方案四:详见官方仓库中 Sa-Token 整合`jwt`的示例(示例压缩包:[Sa-Token 集成示例大全下载](/more/download-demos)
由于`jwt`模式不在服务端存储数据,对于比较复杂的业务可能会功能受限,因此更加推荐使用方案三
+1 -1
View File
@@ -191,7 +191,7 @@ public class FeignInterceptor implements RequestInterceptor {
*/
@FeignClient(
name = "sp-home", // 服务名称
configuration = FeignInterceptor.class, // 请求拦截器 (关键代码)
configuration = FeignInterceptor.class, // 请求拦截器 ⚠️ 关键代码)
fallbackFactory = SpCfgInterfaceFallback.class // 服务降级处理
)
public interface SpCfgInterface {
+121
View File
@@ -5,6 +5,125 @@
---
- [[ 公众号 ] 9 大框架统一接入,sa-token-rust 为什么值得关注](https://mp.weixin.qq.com/s/kLx5ff2DNIACXnSclv00ZA) 2026-5-7
- [[ CSDN ] Sa-Token基础篇](https://blog.csdn.net/2401_87882047/article/details/160829277) 2026-5-6
- [[ 公众号 ] Sa-Token:一行代码搞定登录,Java鉴权框架还能这么简单](https://mp.weixin.qq.com/s/gB89MRuBOaElBdUXtP8OjQ) 2026-5-4
- [[ 公众号 ] Sa-Token 核心流程](https://mp.weixin.qq.com/s/Y_8neMzyUg5danEU3Y1PdQ) 2026-5-1
- [[ 51cto ] SpringBoot结合Sa-token实现权限认证(3](https://blog.51cto.com/u_16099187/14575721) 2026-4-29
- [[ 掘金 ] Spring @Async 异步无法获取当前登录用户?Sa-Token 1.34.0 终极踩坑解决方案](https://juejin.cn/post/7633614065114316851) 2026-4-28
- [[ 掘金 ] 把 Sa-Token 搬到 NestJS 生态:xlt-token 1.0 的几个设计取舍](https://juejin.cn/post/7632620169131343914) 2026-4-26
- [[ 公众号 ] Sa-Token 的 switchTo,不是 "管理员冒充用户"](https://mp.weixin.qq.com/s/Uqh1sDhjnJIBEkNwoxqA-g) 2026-4-24
- [[ 公众号 ] 权限框架怎么选?SpringSecurity、Shiro、Sa-Token 全面对比](https://mp.weixin.qq.com/s/ilOjwq92KK3FIOHUwjKOLA) 2026-4-24
- [[ CSDN ] 别再只写@SaCheckPermission了!手把手教你自定义Sa-Token权限校验逻辑(附源码)](https://blog.csdn.net/weixin_42568801/article/details/160469012) 2026-4-24
- [[ 公众号 ] Sa-Token权限框架](https://mp.weixin.qq.com/s/X-Ew5ig_nQUoGw5uPSPLgw) 2026-4-23
- [[ 掘金 ] Sa-Token 使用方法及实现原理](https://juejin.cn/post/7630724767640928290) 2026-4-20
- [[ CSDN ] 【RuoYi-Vue-Plus】Sa-Token 拦截器升级实战:从源码拆解 SaInterceptor 的设计哲学与性能优化](https://blog.csdn.net/weixin_42568801/article/details/160306227) 2026-4-19
- [[ 公众号 ] SpringBoot + JWT + Sa-Token:认证鉴权双框架对比,安全登录与权限控制最佳实践](https://mp.weixin.qq.com/s/NoO5wd-B1Jl4exlLYX8y2g) 2026-4-18
- [[ 公众号 ] 别再盲目上 SSO 了!Sa-Token 多端跳转的「作弊级」解法](https://mp.weixin.qq.com/s/x828OMUIjQMf7RqiwGIgcw) 2026-4-17
- [[ CSDN ] Sa-Token vs Spring Security权限认证对比:Ruoyi-vue-plus项目该选哪个?](https://blog.csdn.net/weixin_42563415/article/details/160171603) 2026-4-15
- [[ 掘金 ] 在nest.js中我想把Java的Sa-Token搬来](https://juejin.cn/post/7628910305003946011) 2026-4-15
- [[ 公众号 ] 真相:别再复制粘贴了!Sa-Token 全链路权限架构,一次落地所有项目](https://mp.weixin.qq.com/s/3Msd1UNA0GgY81fioDDahQ) 2026-4-14
- [[ 公众号 ] 基于 Sa-Token 搭建大模型开放平台:生产级 API Key 认证与限流实战](https://mp.weixin.qq.com/s/B9QttdYnkN3Au8cbhuo5Vw) 2026-4-14
- [[ CSDN ] JWT与Sa-Token认证方案对比](https://blog.csdn.net/t194978/article/details/160158394) 2026-4-14
- [[ 公众号 ] 告别SpringSecuritySa-Token+Gateway+Nacos极简鉴权实战](https://mp.weixin.qq.com/s/-mKBH70Z0zDYR3JT9YN7Qw) 2026-4-13
- [[ 公众号 ] Sa-Token + WebSocket 鉴权:别让"长连接"成了安全漏洞!](https://mp.weixin.qq.com/s/m34rq63GHgZMxvzkJYRy2w) 2026-4-10
- [[ 公众号 ] 使用Sa-Token解决WebSocket握手身份认证](https://mp.weixin.qq.com/s/ieCtD3-SW748k5Ri-iSmkg) 2026-4-10
- [[ 公众号 ] 苦 Spring Security 久矣?这款霸榜 Gitee 的权限框架,把优雅做到了极致](https://mp.weixin.qq.com/s/yJDy7o6ZnNLEvn_9hHah4Q) 2026-4-10
- [[ 公众号 ] Sa-Token 真的好用:告别 Spring Security 的"配置地狱",真香!](https://mp.weixin.qq.com/s/-uuj2CxGLRtwg55RxwKwDA) 2026-4-9
- [[ 公众号 ] 别只会 MD5!API 签名这几种才是大厂标配(Sa-Token 实战版)](https://mp.weixin.qq.com/s/hxMQCJrraGLaZk8JpU_XGA) 2026-4-9
- [[ 公众号 ] 基于 Sa-Token 搭建大模型开放平台:生产级 API Key 认证与限流实战](https://mp.weixin.qq.com/s/AXiOnEI_mbhOCtv3ZezcQQ) 2026-4-7
- [[ 公众号 ] 一行代码搞定鉴权!1.8万Star的Sa-TokenJava权限认证框架](https://mp.weixin.qq.com/s/nFrVbDcRMVOAuxNMmqyWkw) 2026-4-6
- [[ 公众号 ] 一文彻底搞懂 Sa-Token safe-auth:它不是"再登录一次",而是给危险接口加一道门](https://mp.weixin.qq.com/s/zdhZh9LPwcF0wXF7bFzNHA) 2026-4-3
- [[ 公众号 ] Sa-Token:一行代码搞定登录权限、优雅“踢人”,有github源码,开箱即可踢,优雅!](https://mp.weixin.qq.com/s/M6tXTCsHZr-ONwfn3v9JeA) 2026-4-2
- [[ 公众号 ] 登录不是接口调用,而是一套账号生命周期策略](https://mp.weixin.qq.com/s/pcGcTmU6zu3GDsnpNXZ88A) 2026-3-27
- [[ 公众号 ] Sa-Token:轻量级 Java 权限认证框架推荐](https://mp.weixin.qq.com/s/qTLYmx2otKcd46j_0iBP1A) 2026-3-27
- [[ 公众号 ] Sa-Token 记住我机制,讲透 Cookie 和 Token](https://mp.weixin.qq.com/s/dMekJRRRhOgGPLpEvPmh1g) 2026-3-26
- [[ 公众号 ] Sa-Token + JWT:强强联合,打造前后端分离认证的最佳实践](https://mp.weixin.qq.com/s/NMmMcHRxPEthV6FRBlDxDg) 2026-3-26
- [[ 公众号 ] 多端登录不是功能,是账号策略](https://mp.weixin.qq.com/s/HNWo8qoE-RRiylnMaBMqeg) 2026-3-25
- [[ 公众号 ] 16、Spring Boot 3.2.5 整合 Sa-Token 1.44.0 完整教程](https://mp.weixin.qq.com/s/A_LqOzTHJcZGNqTBxT8hKA) 2026-3-25
- [[ 公众号 ] 多租户 SaaS 别再乱答了!数据隔离就这三层,有github代码](https://mp.weixin.qq.com/s/AcoEOB_RzgY227RwaulHsA) 2026-3-25
- [[ 公众号 ] 七年磨一剑,一款开源、免费、一站式 java 权限认证框架,让鉴权变得简单](https://mp.weixin.qq.com/s/YQW4r5BmfHjUY-TBm9FDng) 2026-3-24
- [[ 公众号 ] 告别繁琐配置:Sa-Token 如何让 Java 鉴权回归"一行代码"的优雅](https://mp.weixin.qq.com/s/sWw7JU91Bk8gHf0Z3CHfdQ) 2026-3-22
- [[ 公众号 ] Sa-Token 整合用户认证授权(完整实战指南)](https://mp.weixin.qq.com/s/8yiu-ij0Vk8dAWw3qxAGpg) 2026-3-22
- [[ 公众号 ] 微服务鉴权不用愁!Sa-Token 网关统一鉴权 + 分布式Session 实战+源码](https://mp.weixin.qq.com/s/9WW67naQQTRtTCb_cHg8LA) 2026-3-21
- [[ 公众号 ] 告别Spring SecuritySa-Token一行代码搞定登录+权限(有料有源码)](https://mp.weixin.qq.com/s/ciTXQTSCeihR6axa8MxiJg) 2026-3-20
- [[ 掘金 ] 基于 Spring Cloud Gateway + Sa-Token 的架构为例,Token 异常的执行链路](https://juejin.cn/post/7619201247540166675) 2026-3-20
- [[ 公众号 ] 面试官:你能手写一个 SSO 单点登录流程吗?](https://mp.weixin.qq.com/s/abhYTjpshCQo4Tbyv71GYw) 2026-3-19
- [[ 公众号 ] 面试官:"你们项目用的什么权限框架,为什么这么选?",用了Sa-Token,上手快开箱即用。。。](https://mp.weixin.qq.com/s/kojBf_Qo5a6giBlU9Y18vg) 2026-3-18
- [[ PHP中文网 ] 怎样在Java中搭建Sa-Token权限认证环境_Java安全新选型](https://www.php.cn/faq/2211433.html) 2026-3-16
<!-- - [[ 公众号 ] SpringBoot3 + Sa-Token 双Token登录认证实战(避坑版)](https://mp.weixin.qq.com/s/rOZ8LvghG64ZSTsIokiUhA) 2026-3-12 -->
- [[ 公众号 ] Sa-Token 多端会话管控解析:构建统一身份认证秩序,解决多端登录难题](https://mp.weixin.qq.com/s/mpoOxgz4jDPcFBoGHVhoiw) 2026-3-11
- [[ 公众号 ] 诈尸级更新?停更9个月,Sa-Token 1.45.0 携 Spring Boot 4.x 适配回来了!](https://mp.weixin.qq.com/s/Wj83wEbbYoUQU0M7IgfVnA) 2026-3-10
- [[ 公众号 ] Kaleido-AI教程(九)基于Sa-Token实现多账户认证体系 ](https://mp.weixin.qq.com/s/ihW1sM8DvQJS-1ZKhFr9KQ) 2026-3-8
- [[ 公众号 ] Sa-Token 的 token-prefix 和 token-style,到底谁管谁? ](https://mp.weixin.qq.com/s/1_PaPxvEui-16Is6Urw3CA) 2026-3-7
- [[ 公众号 ] JAVASpring Boot3 集成 Sa-Token 轻量级权限认证 ](https://mp.weixin.qq.com/s/cjk9ad9tj397Bd0hAyCsyA) 2026-3-6
- [[ 公众号 ] Sa-Token(二)之从入门到实战——一篇文章助你真正了解掌握Sa-Token ](https://mp.weixin.qq.com/s/9-CLoSJZBrfTF2tul-M8Ww) 2026-3-6
- [[ 公众号 ] 不用 Cookie,鉴权照样稳 ](https://mp.weixin.qq.com/s/k8DC-GiYYPbofGDD_4jlPQ) 2026-3-6
- [[ CSDN ] Sa-Token登录策略全解析:从单地登录到同端互斥,这些配置项你都知道吗? ](https://blog.csdn.net/weixin_29284885/article/details/158675177) 2026-3-5
- [[ 公众号 ] 18.6k vs 9.5k Star!若依认证该选谁? ](https://mp.weixin.qq.com/s/BVFWWPiYloa1nuZ4MZ8XZg) 2026-3-4
- [[ 公众号 ] SaToken 支持使用 JSON body验签 ](https://mp.weixin.qq.com/s/0Rr9PuDBUJwaEolhtJxBwA) 2026-3-3
- [[ 公众号 ] 明明接了 Redis,重启后会话还是丢了? ](https://mp.weixin.qq.com/s/-O1qwR0I30wngurGo-qOuw) 2026-3-3
- [[ 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
@@ -89,6 +208,8 @@
- [[ 公众号 ] sa-token-rust 项目:高性能的 Rust 认证授权框架](https://mp.weixin.qq.com/s/jlQAX1K1M64DtUgrHrDX1A) 2025-11-17
- [[ 知乎 ] Sa-Token 与 Spring Security 对比分析](https://zhuanlan.zhihu.com/p/1975162115833754745) 2025-11-12
- [[ 公众号 ] 不会吧,居然还有人没有用过?全网爆火的权限校验框架 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
+9
View File
@@ -14,3 +14,12 @@
- 对框架新增特性功能但比较复杂,会延后几个版本制定相应的计划后进行开发。
- 与框架设计理念不太相符,或超出权限认证范畴,将会视需求人数决定是否开发。
### 其它反馈途径
除了问卷提交,你还可以从以下渠道向我们提交反馈:
- Gitee[issue 提交](https://gitee.com/dromara/sa-token/issues)
- GitHub[issue 提交](https://github.com/dromara/sa-token/issues)
- AtomGit[issue 提交](https://atomgit.com/dromara/sa-token/issues)
- 交流群:[加群链接](/more/join-group)
请大胆提交、大胆咨询,请在交流群中大胆艾特我们,请不要有任何害羞 🤭。就算我们不实现,你也不会损失什么,对吧!
+25
View File
@@ -0,0 +1,25 @@
# Sa-Token 集成 Demos 示例大全下载
**我们为框架几乎所有技术点均单独制作了对应的集成示例,此压缩包共计 60+ Demo**:涵盖 Sa-Token 登录认证、权限认证、SSO 单点登录、OAUth2 统一认证、微服务鉴权、API Key 认证、JWT集成、跨系统调用参数签名校验 等鉴权认证的方方面面。
<img class="s-w-sh" src="/big-file/contact/show/sa-token-demos-pre.png" />
### 下载方式
加入 Sa-Token 交流群,即可在群公告查看下载地址:
加入QQ交流群:1081649142 [点击加入](https://qm.qq.com/q/SCAaZ6Ros2)
加入微信交流群:
<img class="s-w" src="/big-file/contact/i-wx-qr2.jpg" style="width: 180px;" alt="微信群" />
PS:扫码添加微信 (备注:sa),邀您加入群聊。
<br>
<img class="s-w" src="/big-file/contact/show/sa-token-demos-notice.png" style="max-width: 70%;" alt="微信群" />
+1 -1
View File
@@ -60,11 +60,11 @@
- 新增:新增 organize-update-log SKILL,用于格式化整理版本更新日志信息。
- 新增:新增 commit-message SKILL,用于整理 git commit 日志信息。
- 新增:新增 upgrade-version SKILL,用于统一升级修改版本号。
- 新增:新增 remove-redundancy-import SKILL,用于检查 Java 类中无效冗余导包并移除。
- 其它:
- 新增:readme 增加快问快答区域。
- 新增:增加忽略 .vscode 目录。
- 优化:注释优化。
- 新增:新增 `skills/remove-redundancy-import` 技能,用于检查 Java 类中无效冗余导包并移除。
- 重构:备忘录重构为专门的文件夹。
- 重构:调整项目发布配置至 Maven Central Portal。merge: [pr 792](https://github.com/dromara/Sa-Token/pull/792)
- 优化:部分构建配置升级到最新版。
+2 -2
View File
@@ -108,9 +108,9 @@ public class SaOAuth2ServerH5Controller {
### 3、新建前端项目
既然是前后台分离,那肯定要有一个独立的前端项目,所需代码比较冗长,不便于在文档处直接展示,大家可以参考在线仓库示例:
既然是前后台分离,那肯定要有一个独立的前端项目,所需代码比较冗长,不便于在文档处直接展示,大家可以参考仓库示例`/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server-h5`(完整压缩包见 [Sa-Token 集成示例大全下载](/more/download-demos)
[sa-token-demo-oauth2-server-h5/](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server-h5/)
<!-- [sa-token-demo-oauth2-server-h5/](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server-h5/) -->
### 4、运行测试
@@ -43,6 +43,7 @@ public void configOAuth2Server(SaOAuth2ServerConfig oauth2Server) {
> 应该根据自己的架构合理分析是否应该整合数据互通。
### OAuth2-Client 数据互通
除了Server端,Client端也可以打通 `access_token` 与 `satoken` 会话。做法是在 Client 端拿到 `access_token` 后进行登录时,使用 `SaLoginParameter` 预定登录生成的 Token 值
@@ -58,6 +59,13 @@ StpUtil.login(uid, new SaLoginParameter().setToken(access_token));
**疑问:数据互通后,两个 token 的过期策略是什么?**
会话 token 由 `sa-token.timeout` 决定,`access_token` 由 `sa-token.oauth2-server.access-token-timeout` 决定。
数据互通只是将 token 拷贝一份进行复用,动作完成之后两者不再有任何联系。
+1 -1
View File
@@ -115,7 +115,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
return new SaClientModel()
.setClientId("xxxx")
.setClientSecret("xxxx")
.setSubjectId("1000001") // 关键代码:主体 id (可选)
.setSubjectId("1000001") // ⚠️ 关键代码:主体 id (可选)
// ....
;
}
+1 -1
View File
@@ -11,7 +11,7 @@
### 2、引入依赖
创建SpringBoot项目 `sa-token-demo-oauth2-server`(不会的同学自行百度或参考仓库示例),引入 `pom.xml` 依赖:
创建SpringBoot项目 `sa-token-demo-oauth2-server`(不会的同学自行百度或参考仓库示例[Sa-Token 集成示例大全下载](/more/download-demos) 获取全套 Demo),引入 `pom.xml` 依赖:
<!---------------------------- tabs:start ---------------------------->
<!-------- tab:Maven 方式 -------->
+2
View File
@@ -27,6 +27,8 @@ OAuth2.0 与 SSO 相比,增加了对应用授权范围的控制,减弱了应
接下来我们将通过简单示例演示如何在 Sa-Token-OAuth2 中完成这四种模式的对接: [搭建OAuth2-Server](/oauth2/oauth2-server)
若需下载官方仓库内全套可运行 Demo 压缩包,请见:[Sa-Token 集成示例大全下载](/more/download-demos) 。
### OAuth2.0 第三方开放平台完整开发流程参考
+4 -3
View File
@@ -17,10 +17,11 @@ Sa-Token默认的Redis集成方式会把权限数据和业务缓存放在一起
### 1、首先引入Alone-Redis依赖
!--
> [!NOTE| label:Spring Boot 4 用户]
> [!WARNING| label:Spring Boot 4 用户]
> 若使用 Spring Boot 4.x,请引入 `sa-token-alone-redis-by-spring-boot4` 替代 `sa-token-alone-redis`。
-->
> 注:当前版本下(v1.45.0),此包尚未发布到 Maven 中央仓库,如需使用请下载源码手动自行打包或直接将源码复制到你的项目中进行使用。
<!---------------------------- tabs:start ---------------------------->
<!-------- tab:Maven 方式 -------->
+2 -2
View File
@@ -300,7 +300,7 @@ public SaResult addMoney(long userId, long money, long timestamp, String nonce,
@RequestMapping("addMoney")
public SaResult addMoney(long userId, long money, long timestamp, String nonce, String sign) {
// 1、检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值)
// 1、检查 timestamp 是否超出允许的范围 (⚠️ 重点一:此处需要取绝对值)
long timestampDisparity = Math.abs(System.currentTimeMillis() - timestamp);
if(timestampDisparity > 1000 * 60 * 15) {
return SaResult.error("timestamp 时间差超出允许的范围,请求无效");
@@ -312,7 +312,7 @@ public SaResult addMoney(long userId, long money, long timestamp, String nonce,
// 3、验证签名
// 代码同上,不再赘述
// 4、将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2
// 4、将 nonce 记入缓存,防止重复使用(⚠️ 重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2
CacheUtil.set("nonce_" + nonce, "1", (1000 * 60 * 15) * 2);
// 5、业务代码 ...
+1 -1
View File
@@ -174,7 +174,7 @@ sa.copr=true
<br>
**注:**示例源码在`/sa-token-demo/sa-token-demo-quick-login`目录下,可结合源码查看学习
**注:**示例源码在`/sa-token-demo/sa-token-demo-quick-login`目录下,可结合源码查看学习。[Sa-Token 集成示例大全下载](/more/download-demos) 。
+1
View File
@@ -27,6 +27,7 @@ Sa-Token-SSO 由简入难划分为三种模式,解决不同架构下的 SSO
| 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | [文档](/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client) |
| 前端不同域 + 后端不同 Redis | 模式三 | Http请求获取会话 | [文档](/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client) |
集成示例还可下载全套 Demo 压缩包:[Sa-Token 集成示例大全下载](/more/download-demos) 。
1. 前端同域:就是指多个系统可以部署在同一个主域名之下,比如:`c1.domain.com``c2.domain.com``c3.domain.com`
2. 后端同Redis:就是指多个系统可以连接同一个Redis。PS:这里并不需要把所有项目的数据都放在同一个Redis中,Sa-Token提供了 **`[权限缓存与业务缓存分离]`** 的解决方案,详情戳: <a href="#/plugin/alone-redis" target="_blank">Alone独立Redis插件</a>。
+1 -1
View File
@@ -116,7 +116,7 @@ public class SsoClientController {
}
// SSO-Client:接收消息推送地址
@RequestMapping("/sso/ssoPushC")
@RequestMapping("/sso/pushC")
public Object ssoPushC() {
return SaSsoClientProcessor.instance.ssoPushC();
}
+2 -2
View File
@@ -140,11 +140,11 @@ public SaResult getData(String apiType, String loginId) {
System.out.println("apiType=" + apiType);
System.out.println("loginId=" + loginId);
// ↓↓↓ 重点代码 ↓↓↓
// ↓↓↓ ⚠️ 重点代码 ↓↓↓
// 校验签名:只有拥有正确秘钥发起的请求才能通过校验
String client = SaHolder.getRequest().getHeader("client");
SaSsoServerProcessor.instance.ssoServerTemplate.getSignTemplate(client).checkRequest(SaHolder.getRequest());
// ↑↑↑ 重点代码 ↑↑↑
// ↑↑↑ ⚠️ 重点代码 ↑↑↑
// 自定义返回结果(模拟)
return SaResult.ok()
+9 -11
View File
@@ -12,14 +12,13 @@ NoSdk 模式(不使用SDK):通过 http 工具类调用接口的方式来
参考 demo[sa-token-demo-sso3-client-nosdk](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk)
该 demo 假设应用端没有使用任何“权限认证框架”,使用最基础的 ServletAPI 进行会话管理,模拟了 `/sso/login``/sso/logout``/sso/logoutCall` 三个接口的处理逻辑。
该 demo 假设应用端没有使用任何“权限认证框架”,使用最基础的 ServletAPI 进行会话管理,模拟了 `/sso/login``/sso/logout``/sso/pushC` 三个接口的处理逻辑。
> [!INFO| label:NoSdk 模式优缺点]
> - 1、支持客户端使用任意技术栈。
> - 2、代码简单易懂,流程直观清晰。
> - 3、用 http 工具类模拟 Sa-Token SSO 内部实现,样版代码较多,略显冗余。
> [!WARNING| label:NoSdk 示例不再主维护]
> 基于以下原因:
> - 1、NoSdk demo 相当于通过 http 工具类再次重写了一遍 Sa-Token SSO 模块代码,繁琐且冗余。
> - 2、重写的代码无法拥有 Sa-Token SSO 模块全部能力,仅能完成基本对接,算是一个简化版 SDK。
>
> 自 v1.43.0 版本起,不再主维护 NoSdk 模式,仓库示例仅做留档参考,大家可以转为 ReSdk 模式。
### ReSdk 模式
@@ -28,11 +27,10 @@ ReSdk 模式(重写SDK部分方法):通过重写框架关键步骤点,
参考 demo[sa-token-demo-sso3-client-resdk](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-resdk)
> [!INFO| label:ReSdk 模式优点]
> - 1、依然支持客户端使用任意技术栈。
> [!INFO| label:ReSdk 模式优点]
> - 1、支持客户端使用任意技术栈。
> - 2、仅重写少量部分关键代码,即可完成对接。几乎可以得到 Sa-Token SSO 模块全量能力。
建议新项目首选 ReSdk 模式作为参考。
> - 3、此模式需要对 Sa-Token SSO 内部实现较为熟悉,才可以驾驭。
+1 -1
View File
@@ -3,7 +3,7 @@
在开始SSO三种模式的对接之前,我们必须先搭建一个 SSO-Server 认证中心
> [!TIP| label:demo]
> 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/`,如遇到难点可结合源码进行测试学习,demo里有制作好的登录页面
> 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/`,如遇到难点可结合源码进行测试学习。[Sa-Token 集成示例大全下载](/more/download-demos) demo里有制作好的登录页面
---
+1 -1
View File
@@ -66,7 +66,7 @@ sa-token.cookie.domain=stp.com
### 4、搭建 Client 端项目
> 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso1-client/`,如遇到难点可结合源码进行测试学习。
> 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso1-client/`,如遇到难点可结合源码进行测试学习。[Sa-Token 集成示例大全下载](/more/download-demos) 。
#### 4.1、引入依赖
+1 -1
View File
@@ -49,7 +49,7 @@
### 3、搭建 Client 端项目
> [!TIP| label:demo | style:callout]
> 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/`,如遇到难点可结合源码进行测试学习
> 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/`,如遇到难点可结合源码进行测试学习。[Sa-Token 集成示例大全下载](/more/download-demos) 。
#### 3.1、去除 SSO-Server 的 Cookie 作用域配置
在SSO模式一章节中我们打开了配置:
+1 -1
View File
@@ -17,7 +17,7 @@
> [!TIP| label:demo | style:callout]
> 模式三的 Demo 示例地址:`/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/`
> [源码链接](https://gitee.com/dromara/sa-token/tree/dev/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client),如遇难点可参考示例
> ,如遇难点可参考示例。[Sa-Token 集成示例大全下载](/more/download-demos) 。
### 2、在Client 端更改 Ticket 校验方式
+3 -1
View File
@@ -1,6 +1,8 @@
# 其它环境引入 Sa-Token 的示例
目前已实现的对接框架综合
目前已实现的对接框架综合
如需一次性获取官方仓库内全部可运行示例的压缩包,请见:[Sa-Token 集成示例大全下载](/more/download-demos) 。
------
+3 -2
View File
@@ -1,8 +1,9 @@
# SpringBoot 集成 Sa-Token 示例
本篇带你从零开始集成 Sa-Token,从而快速熟悉框架的使用姿势。
整合示例在官方仓库的`/sa-token-demo/sa-token-demo-springboot`文件夹下,如遇到难点可结合源码进行学习测试
本篇带你从零开始集成 Sa-Token,只需简单 5 步,你就可以快速熟悉框架的使用姿势
整合示例在官方仓库的`/sa-token-demo/sa-token-demo-springboot`文件夹下,如遇到难点可结合源码进行学习测试。[Sa-Token 集成示例大全下载](/more/download-demos) 。
---
+1 -1
View File
@@ -2,7 +2,7 @@
本篇介绍在 Solon 应用中如何集成 Sa-Token。
整合示例在官方仓库的 `/sa-token-demo/sa-token-demo-solon` 文件夹下,如遇到难点可结合源码进行学习测试。
整合示例在官方仓库的 `/sa-token-demo/sa-token-demo-solon` 文件夹下,如遇到难点可结合源码进行学习测试。[Sa-Token 集成示例大全下载](/more/download-demos) 。
> [!tip| label:Solon 是什么?]
> Solon 是一个高效的国产应用开发框架:更快、更小、更简单。
+1 -1
View File
@@ -3,7 +3,7 @@
**Reactor** 是一种非阻塞的响应式模型,本篇将以 **WebFlux** 为例,展示 Sa-Token 与 Reactor 响应式模型框架相整合的示例,
**你可以用同样方式去对接其它 Reactor 模型框架(例如 SpringCloud Gateway**
整合示例在官方仓库的`/sa-token-demo/sa-token-demo-webflux`文件夹下,如遇到难点可结合源码进行测试学习
整合示例在官方仓库的`/sa-token-demo/sa-token-demo-webflux`文件夹下,如遇到难点可结合源码进行测试学习。[Sa-Token 集成示例大全下载](/more/download-demos) 。
> [!WARNING| label:小提示 ]
+3 -1
View File
@@ -484,7 +484,9 @@ body {
/* 帮助按钮 */
.help-btn{transition: all 0.5s; text-align: center; border: 1px #42b983 solid; background-color: rgba(255, 255, 255, 0.5); cursor: pointer; font-size: 13px; color: #42b983; line-height: 40px;}
.help-btn{transition: all 0.5s; text-align: center; background-color: rgba(255, 255, 255, 0.5); cursor: pointer; font-size: 13px; line-height: 40px;}
.help-btn{ margin-top: 20px; border: 1px #42b983 solid; color: green;}
.help-btn a{ text-decoration: none !important; color: inherit; height: 100%; width: 100%; display: inline-block; }
.help-btn:hover{box-shadow: 0 0 20px #D1EEE1 !important;}
.xiaozhushou-intro p{line-height: 14px;}
+99
View File
@@ -1468,5 +1468,104 @@ var donateList = [
"msg": '感谢您的开源项目!',
"date": "2026-02-26"
},
{
"name": "美人鱼(微信打赏)",
"link": "",
"money": 18.88,
"msg": '感谢您的开源项目!',
"date": "2026-03-3"
},
{
"name": "G_M(赞赏码)",
"link": "",
"money": 3,
"msg": '感谢,确实很好用',
"date": "2026-03-13"
},
{
"name": "Li(赞赏码)",
"link": "",
"money": 20,
"msg": '感谢您的开源项目,加油',
"date": "2026-03-17"
},
{
"name": "123(赞赏码)",
"link": "",
"money": 18.88,
"msg": '感谢您开源的项目,中文文档太棒了!',
"date": "2026-03-17"
},
{
"name": "石嘉懿(赞赏码)",
"link": "",
"money": 20,
"msg": '23级赞助',
"date": "2026-03-18"
},
{
"name": "Ma.chine_M(微信打赏)",
"link": "",
"money": 20.00,
"msg": '喝杯咖啡!',
"date": "2026-03-20"
},
{
"name": "四云(赞赏码)",
"link": "",
"money": 200,
"msg": '感谢您的开源项目',
"date": "2026-03-27"
},
{
"name": "小火(赞赏码)",
"link": "",
"money": 0.05,
"msg": '感谢您的开源项目',
"date": "2026-03-29"
},
{
"name": "Right(赞赏码)",
"link": "",
"money": 1.0,
"msg": '感谢,ZZT.',
"date": "2026-04-09"
},
{
"name": "糍粑(赞赏码)",
"link": "",
"money": 1.0,
"msg": '加油',
"date": "2026-04-28"
},
{
"name": "jerry(赞赏码)",
"link": "",
"money": 42.9,
"msg": '非常棒的项目!',
"date": "2026-04-29"
},
{
"name": "七(赞赏码)",
"link": "",
"money": 1.11,
"msg": '没啥钱的正在工作的毕业生,表示一下',
"date": "2026-05-08"
},
{
"name": "朱毛毛",
"link": "",
"money": 10.00,
"msg": '感谢您的开源',
"date": "2026-05-13"
},
{
"name": "森林雨",
"link": "",
"money": 6.66,
"msg": '加油,看好你们',
"date": "2026-05-17"
},
]
+1 -1
View File
@@ -56,7 +56,7 @@ uni.request({
url: 'https://www.example.com/request', // 仅为示例,并非真实接口地址。
header: {
"content-type": "application/x-www-form-urlencoded",
"satoken": uni.getStorageSync('tokenValue') // 关键代码, 注意参数名字是 satoken
"satoken": uni.getStorageSync('tokenValue') // ⚠️ 关键代码, 注意参数名字是 satoken
},
success: (res) => {
console.log(res.data);