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

Compare commits

...

214 Commits

Author SHA1 Message Date
click33 0b230e290f 优化 readme 2026-05-22 22:47:49 +08:00
click33 51d0378274 优化 readme 2026-05-22 22:43:38 +08:00
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
click33 ad837c9640 Merge branch 'master' of https://gitee.com/dromara/sa-token into dev
# Conflicts:
#	sa-token-demo/sa-token-demo-solon/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java
2026-03-09 08:16:58 +08:00
click33 aa3f77bb3c chore: 修正注释 2026-03-08 20:18:03 +08:00
click33 80c7362bfa feat: 新增 sa-token-alone-redis-by-spring-boot4 插件,实现 SpringBoot4 下权限缓存与数据缓存分离 2026-03-08 20:14:47 +08:00
click33 91dd4bb30a docs: v1.45.0 更新 2026-03-08 20:13:36 +08:00
click33 f4a7ef6e00 chore: 完善 pom.xml 信息 2026-03-08 20:08:24 +08:00
click33 79245edb5b chore: 统一 POM 的 name 与 artifactId,并补充 description 2026-03-08 14:47:36 +08:00
click33 98f4de6514 docs: 补全 StpUtil API 说明文档 2026-03-08 13:35:30 +08:00
click33 024d6e0491 docs: 优化 redis 引入章节相关说明 2026-03-08 13:21:01 +08:00
click33 5c675d0a37 docs: 修正全局的 redis 相关集成引入说明 2026-03-08 13:01:03 +08:00
click33 1858306fde docs: 添加 SpringBoot4 引入说明 2026-03-08 12:48:13 +08:00
click33 4c69912af0 docs: 添加 loveqq-boot 引入说明 2026-03-08 12:29:15 +08:00
click33 a24a5b2271 docs: 新增 json 序列化插件展示说明 2026-03-08 12:16:24 +08:00
click33 0e551cfd69 docs: 补全缺失目录说明 2026-03-08 12:15:43 +08:00
click33 2d37f76a42 docs: 新增 replacedLoginExitMode 配置项功能说明 2026-03-08 11:33:41 +08:00
click33 643f422ac8 release: v1.45.0 2026-03-08 04:09:40 +08:00
click33 3f9b07c25e AI: 新增 skills/upgrade-version/SKILL.md,用于一键更新版本号 2026-03-08 04:07:42 +08:00
click33 6cbf628c0f Merge branch 'dev' of https://github.com/dromara/Sa-Token into dev 2026-03-08 03:17:56 +08:00
click33 33047fd760 AI: 优化 commit-message/SKILL.md 2026-03-08 03:17:41 +08:00
click33 3f3263fe33 Merge pull request #792 from xuxiaowei-com-cn/xuxiaowei/Maven-Central-Portal
🔧 use Maven Central Portal
2026-03-08 03:15:06 +08:00
click33 e966f6cec1 Merge branch 'dev' into xuxiaowei/Maven-Central-Portal 2026-03-08 03:14:46 +08:00
click33 d3bdd6319c AI: 新增 skills/README.md 文档 2026-03-05 14:44:58 +08:00
click33 22e4d7e80b AI: 新增 commit-message skill,用于根据 git 变更生成符合项目规范的 commit message 2026-03-05 14:30:32 +08:00
click33 890a8f860e chore: 升级 flatten-maven-plugin 至 1.7.3 2026-03-05 14:22:06 +08:00
click33 f9ebc52a39 refactor: 移除冗余导包 2026-03-02 09:21:35 +08:00
click33 34c6b752d7 AI: 新增 skills/remove-redundancy-import/SKILL.md,用于检查项目中的java类无效冗余导包信息并移除 2026-03-02 09:21:18 +08:00
click33 46ab4d7849 Merge pull request #889 from aotemiao/fix/dubbo-context-cleanup
refactor: SaTokenDubboContextFilter 改为使用 SaTokenContextDubboUtil 清理上下文
2026-03-02 08:04:31 +08:00
click33 bd21035cf1 Merge pull request #851 from coder-xiaomo/dev
fix: 修正一处代码注释错误:SaTokenDao 注释中 数据有效期 应为 小于等于-2 (掉了等于)
2026-03-02 07:57:06 +08:00
click33 465dfaddeb Merge pull request #827 from AdRainty/dev
fix: SaOAuth2Strategy中removeGrantTypeHandler的引用有误
2026-03-02 07:54:31 +08:00
click33 af81ccac75 Merge pull request #816 from font-C/dev
fix: Bearer 全局统一大小写。
2026-03-02 07:48:58 +08:00
click33 b97e60661d Merge pull request #807 from GodBlessWorld/dev
文档:更正一些专业技术术语及清晰表述问题
2026-03-02 07:37:45 +08:00
click33 5e0ca81b9e Merge branch 'dev' into dev 2026-03-02 07:37:33 +08:00
click33 1f4ff1ede4 Merge pull request #793 from 1411430556/patch-1
Update LICENSE
2026-03-02 07:29:41 +08:00
click33 d75d5de438 Merge pull request #907 from 07heco/perf/optimize-pattern-cache-and-eliminate-magic-value
refactor: optimize constant specification and encapsulation for StrFormatter
2026-03-02 07:07:25 +08:00
click33 ab362c87e0 fix: 修复 StpUtil.getLoginIdByTokenNotThinkFreeze 方法缺少 static 的问题 2026-03-02 05:14:26 +08:00
刘潇 0c23371058 !356 添加 sa-token-snack4
Merge pull request !356 from 西东/dev
2026-03-01 21:08:05 +00:00
刘潇 197ba44ce1 !355 修复sa-token-oauth2组件使用sa-token-fastjson2序列化导致的类型转换问题
Merge pull request !355 from 陆健伟/dev
2026-03-01 21:06:48 +00:00
刘潇 c8a7b50cb8 !354 docs: 订正文档错别字
Merge pull request !354 from Uncarbon/N/A
2026-03-01 21:05:41 +00:00
刘潇 9715526c34 !351 loveqq-framework版本更新
Merge pull request !351 from kfyty/dev
2026-03-01 21:04:21 +00:00
click33 22302760c8 refactor: 重命名 SaRepeatLoginsMode -> SaReplacedLoginExitMode 2026-03-02 04:59:09 +08:00
刘潇 466c215506 !349 添加重复登录处理策略,当同一账号不允许多客户端同时登录时支持选择是踢人下线还是拦截本次登录
Merge pull request !349 from 石泽旭/dev
2026-03-01 19:37:12 +00:00
刘潇 7e1d810667 !347 文档内代码示例有点小问题
Merge pull request !347 from Tang/N/A
2026-03-01 19:17:17 +00:00
click33 809232d656 docs: 同步最新文章列表、赞助者名单 2026-03-01 21:34:44 +08:00
click33 2c442f4c0a memo: 备忘录重构为专门的文件夹 2026-03-01 12:10:22 +08:00
click33 dbbb47a1a9 refactor: 优化项目构建配置 2026-03-01 12:09:33 +08:00
click33 4b176d49d5 refactor: 优化 OAuth2 模块在请求中读取 Client 信息算法 2026-03-01 12:08:46 +08:00
刘潇 750c346353 !346 修改ClientIdSecretModel的读取构建逻辑
Merge pull request !346 from ziy001/dev
2026-02-28 22:39:02 +00:00
click33 1a37efef22 docs: 为 sa-token-sso 模块定义 STS 协议 2026-02-28 21:29:22 +08:00
click33 d9168711a0 AI: 新增 SKILL: organize-update-log ,用于格式化整理版本更新日志信息 2026-02-28 05:02:02 +08:00
click33 1a89c68e68 chore: 修复注释错别字 2026-02-28 04:49:02 +08:00
click33 22e9ee80d0 refactor: 优化模块依赖关系 2026-02-28 03:24:24 +08:00
07heco 80f132c1f8 refactor: optimize constant specification and encapsulation for StrFormatter 2026-02-27 18:42:08 +08:00
click33 6a96d2fabd refactor: 重构模块依赖层级 2026-02-27 12:14:01 +08:00
click33 f40f0d3adf refactor: sa-token-dependencies 重构为 sa-token-basic-dependencies 2026-02-27 10:04:44 +08:00
click33 6398ef4ca6 refactor: 重构 springboot reactor 相关集成包,优化依赖关系 2026-02-27 06:18:06 +08:00
click33 e8fe3a83c7 demo: 新增 sa-token-demo-webflux-springboot4 示例 2026-02-27 03:47:12 +08:00
click33 8c0177c598 feat: 新增 sa-token-reactor-spring-boot4-starter 集成包 2026-02-27 03:45:54 +08:00
click33 fc27e2a49d refactor: 移除 sa-token-spring-boot-autoconfig 模块 2026-02-27 03:22:14 +08:00
click33 7c95ec51fa refactor: 重构 springboot 相关集成包,优化依赖关系 2026-02-27 00:25:12 +08:00
click33 9b7efbbcc8 demo: 新增 SpringBoot4 整合 demo 示例 2026-02-26 01:22:32 +08:00
click33 2d671dc783 feat: 新增 sa-token-spring-boot4-starter 集成包 2026-02-26 01:19:41 +08:00
click33 f6c5131ad2 test: 新增 sa-token-jackson3 单元测试 2026-02-25 19:55:09 +08:00
click33 9c07f7a007 feat: 添加 sa-token-jackson3 插件 2026-02-25 19:13:18 +08:00
click33 c2becdf498 docs: 公众号文章列表同步 2026-02-23 16:42:26 +08:00
click33 defb2a0052 docs: 优化 readme 2026-02-20 19:29:15 +08:00
click33 007bf2aa47 docs: 优化 readme. 2026-02-20 19:19:31 +08:00
click33 7725322002 docs: 优化案例库展示 2026-02-20 19:00:24 +08:00
click33 91f97089b5 chore: 增加忽略 .vscode 目录 2026-02-20 19:00:03 +08:00
click33 ef9a0ac3d4 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2026-02-16 01:38:27 +08:00
click33 60a348e903 docs: 同步最新博客链接 2026-02-16 01:37:44 +08:00
刘潇 2eeb47034b !358 update sa-token-doc/start/maven-pull.md.
Merge pull request !358 from QuLing/N/A
2026-02-10 04:30:24 +00:00
QuLing c83ccee894 update sa-token-doc/start/maven-pull.md.
通过新开项目下载依赖后,解决原父子项目无法下载依赖的问题

Signed-off-by: QuLing <x2995924975@163.com>
2026-02-10 04:29:27 +00:00
click33 eba5294beb docs: 同步最新博客列表、赞助者名单 2026-02-09 23:48:36 +08:00
click33 96cb8c5ad3 docs: 添加 Sa-Token 内容合作者群 2026-02-09 02:19:19 +08:00
click33 a552e84488 docs: 优化框架 Slogan 2026-02-08 01:21:55 +08:00
click33 82158d1ee1 docs: 优化 slogan 2026-02-06 18:36:16 +08:00
click33 26c4957020 docs: 新增 《Gitee 2025年度开源项目 Web应用开发 Top 2》 证书展示 2026-02-05 23:59:56 +08:00
click33 49ac796460 docs: 完善网站底部版权栏 2026-02-05 03:56:42 +08:00
click33 9f8a2b29ba docs: 错别字修复 2026-02-05 03:13:03 +08:00
click33 e608272f3f docs: 错别字修复 /sso/sso-questions.md 2026-02-05 02:59:50 +08:00
click33 8be310be15 docs: 错别字修复 2026-02-05 02:50:30 +08:00
click33 9090eddd8d docs: 框架博客展示 2026-02-04 23:56:17 +08:00
click33 34c0775fa2 docs: 同步博客列表 2026-02-04 19:20:29 +08:00
click33 4c737cb342 docs: 同步最新赞助者名单 2026-02-04 19:19:42 +08:00
click33 2294d89db6 docs: 更换 GitCode 为 AtomGit 2026-02-03 17:09:34 +08:00
click33 9444520ad3 docs: 同步最新赞助者名单 2026-02-03 17:02:38 +08:00
click33 d0135299d1 docs: 同步开源案例 2026-02-03 16:45:16 +08:00
click33 dce14ce384 docs: 增加 '/up/*', '/micro/*', '/plugin/*' 章节锁定 2026-02-03 16:44:58 +08:00
click33 150781e94f docs: 更换 gitcode 为 atomgit 2026-02-03 16:43:59 +08:00
click33 192a66aed2 docs: 更换qq群链接 2026-01-29 14:49:04 +08:00
click33 7c4865d0e1 docs: 在线文档地址新增渠道参数 2026-01-27 13:40:28 +08:00
click33 f431473c90 docs: 增加部分章节锁定功能 2026-01-27 13:38:02 +08:00
click33 6387b7eede docs: readme & 文档 细节优化 2026-01-25 14:55:13 +08:00
click33 7eb2772484 docs: 优化 README.md 2026-01-25 14:45:35 +08:00
click33 21df9682b6 docs: 文档细节优化 2026-01-25 14:35:30 +08:00
click33 b707205607 docs: 优化readme 2026-01-25 14:18:20 +08:00
click33 d4a3eef5c7 docs: 文档优化 2026-01-24 11:34:44 +08:00
click33 0e45a9067a docs: 更改部分项目展示 logo 2026-01-24 11:03:53 +08:00
click33 b3fbfa35d4 docs: 同步最新 dromara 项目列表 2026-01-24 09:46:03 +08:00
click33 18f8ed74ff docs: 更换微信群聊展示图 2026-01-24 07:39:38 +08:00
click33 ac74945004 docs: 增加微信群聊信息展示 2026-01-24 07:35:21 +08:00
click33 2fc76c767e docs: 新增赞赏码展示 2026-01-24 05:30:22 +08:00
click33 cefa368f4d docs: 文档首页新增 stars 对比图 2026-01-24 04:19:42 +08:00
click33 84795e9014 docs: 新增解决跨域专题文章 2026-01-24 04:18:28 +08:00
click33 f8d57189e0 docs: 移除 Gitee 2025 投票提示 2026-01-24 04:17:54 +08:00
aotemiao 43ae08bd3c refactor: SaTokenDubboContextFilter 改为使用 SaTokenContextDubboUtil 清理上下文 2026-01-06 08:41:12 +08:00
click33 4cbb0849a7 perf: 优化 readme 2025-12-30 22:59:06 +08:00
click33 b1723e5424 refactor: 注释优化 2025-12-29 02:08:12 +08:00
click33 196d34be20 docs: team.md 优化 2025-12-29 02:00:20 +08:00
click33 ff3ce7daa5 docs: 图片地址更换为本地文件 (附录篇) 2025-12-29 01:44:28 +08:00
click33 f61d8369ac docs: 图片地址更换为本地文件 (插件篇) 2025-12-29 01:19:15 +08:00
click33 a27739f344 docs: 图片地址更换为本地文件 (微服务篇) 2025-12-29 01:02:23 +08:00
click33 9378da7a91 docs: 图片地址更换为本地文件 (OAuth2篇) 2025-12-29 00:41:34 +08:00
click33 47e8e91056 docs: 图片地址更换为本地文件 (SSO篇) 2025-12-29 00:25:06 +08:00
click33 e7c09e81d7 docs: 图片地址更换为本地文件 (深入篇) 2025-12-29 00:03:35 +08:00
click33 6cd765ce91 docs: 图片地址更换为本地文件 (基础篇) 2025-12-28 23:49:34 +08:00
click33 d646099127 docs: index.html & doc.html 引用图片替换为本地 2025-12-28 22:44:15 +08:00
click33 5708657f86 docs: 文档优化 [路由拦截鉴权] 篇 2025-12-28 17:53:30 +08:00
click33 7e9201584d docs: 文档优化 [权限认证] 篇 2025-12-28 12:32:02 +08:00
click33 a6a1b9b865 docs: 文档优化 [登录认证] 篇 2025-12-27 16:47:49 +08:00
click33 4b7e6d651f doc: 补全登记企业信息 2025-12-27 16:46:37 +08:00
click33 f6d6e50a16 docs: 补全企业登记案例 2025-12-26 12:26:46 +08:00
click33 fdead8cfa6 docs: 补全全局策略说明 2025-12-25 20:18:24 +08:00
click33 aa15d56082 docs: 优化文档排版 2025-12-25 01:37:41 +08:00
click33 30ffe1713f docs: 调整主题色块顺序,使之切换时对比更突出 2025-12-24 23:43:26 +08:00
click33 74db9e997e docs: 文档主题切换增加水滴特效 2025-12-24 23:31:40 +08:00
noear 6264c37780 feat: 添加 sa-token-snack4 2025-12-24 21:51:43 +08:00
click33 f4fa77edd0 docs: 补全数据结构说明 2025-12-24 01:14:50 +08:00
click33 2cf899e019 docs: 增加 Same-Token 同源系统认证图示说明 2025-12-22 20:37:46 +08:00
click33 c625a29df8 docs: 子服务外网隔离章节增加示意图 2025-12-22 17:42:27 +08:00
noear d0c2ee7ffd feat: 添加 sa-token-snack4 2025-12-09 17:16:33 +08:00
click33 39b468b150 docs: 功能结构图增加点击事件跳转到对应功能文档 2025-12-01 01:00:17 +08:00
click33 a16552c3fe docs(sso): 单点登录消息推送模块增加 msgType 参数说明 2025-11-18 12:03:30 +08:00
click33 ed35640765 docs: 目录树增加专门栏目记录项目架构设计 2025-11-18 11:39:59 +08:00
click33 b85b2815ea docs: 添加 SSO 模块视频讲解链接 2025-11-12 15:53:22 +08:00
click33 5b8b99e0d0 feat(sso): 新增 sso-server 前后端分离模式下 平台中心模式 demo 示例 2025-11-11 14:51:38 +08:00
click33 75256e3329 docs: 修正部分信息 2025-11-10 22:55:42 +08:00
noear a59c42802c feat: 添加 sa-token-snack4 2025-11-06 17:54:19 +08:00
徐晓伟 6a21591e61 🔧 use Maven Central Portal
https://github.com/alibaba/nacos/issues/13358

settings.xml servers use:

<server>
  <id>central</id>
  <username>${CENTRAL_USERNAME}</username>
  <password>${CENTRAL_PASSWORD}</password>
</server>
2025-10-30 16:42:40 +08:00
noear 27c1a0b1a2 Merge branch 'dev' of https://gitee.com/noear_admin/sa-token into dev 2025-10-27 22:08:50 +08:00
noear 7a534d5aa4 feat: 添加 sa-token-snack4 2025-10-27 22:08:01 +08:00
click33 43728fb6cc docs(sso): 补全 SSO 模块内置消息处理器相关文档 2025-10-23 18:41:40 +08:00
lujw 3c558c2472 修复sa-token-oauth2组件使用sa-token-fastjson2序列化导致的类型转换问题 2025-10-23 17:02:58 +08:00
Uncarbon ae1bd30245 docs: 订正文档错别字
Signed-off-by: Uncarbon <4840454+uncarbon97@user.noreply.gitee.com>
2025-10-23 02:15:04 +00:00
click33 a53874025e Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2025-10-21 17:49:05 +08:00
click33 7a63be81d4 fix(docs): 修复不合适的文章标题 2025-10-21 17:47:52 +08:00
刘潇 4a16556a56 !352 update sa-token-doc/sso/sso-check-domain.md.
Merge pull request !352 from 吴金川/N/A
2025-10-10 05:52:14 +00:00
吴金川 5051f430c0 update sa-token-doc/sso/sso-check-domain.md.
Signed-off-by: 吴金川 <wujinchuan@126.com>
2025-10-10 01:38:49 +00:00
click33 fa76826ee0 docs: 修复文档不正确描述 2025-10-04 18:39:16 +08:00
小墨 4bbce74570 fix: 修正一处代码注释错误:SaTokenDao 注释中 数据有效期 应为 小于等于-2 (掉了等于) 2025-09-29 05:24:44 +00:00
click33 622cf3f3b5 docs(sso): 修复不正确的API说明 2025-09-20 13:33:48 +08:00
click33 0bebcd4b02 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2025-09-20 12:57:23 +08:00
click33 e1f25ccae8 docs: 更新赞助者名单 2025-09-20 12:54:38 +08:00
刘潇 ac6f06fb37 !348 refactor: sa-token-solon-plugin 优化 Gateway 接口的处理(避免使用路由接口)
Merge pull request !348 from 西东/dev
2025-09-04 06:26:37 +00:00
kfyty725 03613bf7a3 opt:loveqq-framework版本更新 2025-08-15 15:28:47 +08:00
AdRainty e4da3d8e7b Update SaOAuth2Strategy.java
removeGrantTypeHandler的引用有误
2025-08-06 15:59:53 +08:00
石泽旭 79f772ccc8 --添加重复登录处理策略,当不允许多客户端同时登录时支持选择是踢人下线还是拦截本次登录 2025-08-05 13:48:35 +08:00
石泽旭 312ca71c47 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java
2025-08-05 13:47:22 +08:00
石泽旭 ac2e1d0abd --添加重复登录处理策略,当不允许多客户端同时登录时支持选择是踢人下线还是拦截本次登录 2025-08-05 13:46:40 +08:00
石泽旭 808c3cdc0f --添加重复登录处理策略,当不允许多客户端同时登录时支持选择是踢人下线还是拦截本次登录 2025-07-31 15:07:20 +08:00
noear 4036788426 refactor: sa-token-solon-plugin 优化 Gateway 接口的处理(避免使用路由接口) 2025-07-29 11:19:25 +08:00
font-C f57303f7ec fix: Bearer 全局统一大小写。 2025-07-16 02:46:01 +00:00
Tang 2919188470 文档内代码示例有点小问题
文档示例代码中注释与方法有冲突,差点给带进去了

Signed-off-by: Tang <tang_0416@126.com>
2025-07-15 02:06:11 +00:00
ziy 5c33c83391 修改ClientIdSecretModel的读取构建逻辑 2025-07-13 14:39:06 +08:00
COYG⚡️ 39a986071d Merge branch 'dev' into patch-1 2025-07-10 18:18:00 +08:00
sudongdong01 072ae7c787 文档:更正一些专业技术术语及清晰表述问题 2025-06-19 17:04:29 +08:00
刘潇 eefb5e5f8e !342 fix: 兼容父类SaAnnotationHandlerInterface#checkMethod
Merge pull request !342 from 最后/N/A
2025-06-08 10:32:09 +00:00
最后 f75b6595dd fix: 兼容父类SaAnnotationHandlerInterface#checkMethod
fix:  兼容父类SaAnnotationHandlerInterface#checkMethod

Signed-off-by: 最后 <244387066@qq.com>
2025-06-08 01:34:55 +00:00
click33 b0d42821d7 docs: 完善文档 2025-06-07 11:59:57 +08:00
duolaameng 2f6390a211 Update LICENSE
完善版权日期
2025-05-22 12:22:14 +08:00
356 changed files with 10178 additions and 2995 deletions
@@ -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,你给老子爬
+344
View File
@@ -0,0 +1,344 @@
<!-- sa-token-bom -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-bom</artifactId>
<version>1.45.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- sa-token-core -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-spring-boot-starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-spring-boot3-starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-spring-boot4-starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot4-starter</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-reactor-spring-boot-starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot-starter</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-reactor-spring-boot3-starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-reactor-spring-boot4-starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot4-starter</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-jboot-plugin -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jboot-plugin</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-jfinal-plugin -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jfinal-plugin</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-loveqq-boot-starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-loveqq-boot-starter</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-servlet -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-servlet</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-jakarta-servlet -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jakarta-servlet</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-plugin -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-plugin</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-alone-redis -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-alone-redis</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-redis-template -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-redis-template-jdk-serializer -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template-jdk-serializer</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-redis-jackson -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-redisson -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redisson</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-redisson-spring-boot-starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redisson-spring-boot-starter</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-redisx -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redisx</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-hutool-timed-cache -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-hutool-timed-cache</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-caffeine -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-caffeine</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-jackson -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jackson</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-jackson3 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jackson3</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-fastjson -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-fastjson</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-fastjson2 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-fastjson2</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-snack3 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-snack3</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-snack4 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-snack4</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-serializer-features -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-serializer-features</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-thymeleaf -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-thymeleaf</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-freemarker -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-freemarker</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-dubbo -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dubbo</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-dubbo3 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dubbo3</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-grpc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-grpc</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-forest -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-forest</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-okhttps -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-okhttps</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-jwt -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-temp-jwt -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-temp-jwt</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-oauth2 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-oauth2</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-apikey -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-apikey</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-sign -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sign</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-quick-login -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-quick-login</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-sso -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-spring-aop -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-aop</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-spring-el -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-el</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-spring-boot-webmvc-reactor-v2v3v4-common -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-webmvc-reactor-v2v3v4-common</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-spring-boot-reactor-v2v3v4-common -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-reactor-v2v3v4-common</artifactId>
<version>1.45.0</version>
</dependency>
<!-- sa-token-spring-boot-webmvc-v3v4-common -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-webmvc-v3v4-common</artifactId>
<version>1.45.0</version>
</dependency>
+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 里生效吗?
不生效
+11
View File
@@ -12,7 +12,18 @@ unpackage/
/.factorypath
.idea/
.vscode/
sa-token-three-plugin/
sa-token-doc/big-file/
.flattened-pom.xml
.qoder
.soloncode/
.cursor
.agents
.github
AGENTS.md
CLAUDE.md
+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.
+146 -49
View File
@@ -1,29 +1,39 @@
<p align="center">
<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>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.45.0</h1>
<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 介绍
### 📝 前言:
回望 2020 年初,我为 Sa-Token 提交第一行代码之际,彼时市面上 Java 缺少的不仅是一个简洁好用的鉴权框架,更是一整套清晰、自洽的权限架构设计思想。
因此,这几年间我将大量时间倾注在 Sa-Token 的文档编写,几乎每一章节、每一句话、每一个字都经过反复修改、精细打磨,以求做到最清晰、干练、易懂的表述。用心阅读文档,你学习到的将不止是 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)
**你还在手搓以下功能?Stop ⚠️ 让 Sa-Token 来!**
![sa-token-jss](https://sa-token.cc/big-file/index/intro/sa-token-jss--tran.png)
要在 SpringBoot 项目中使用 Sa-Token,你只需要在 pom.xml 中引入依赖:
@@ -32,11 +42,11 @@ Sa-Token 是一个轻量级 Java 权限认证框架,目前拥有五大核心
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.44.0</version>
<version>1.45.0</version>
</dependency>
```
除了 SpringBoot2、Sa-Token 还为 SpringBoot3、Solon、JFinal 等常见 Web 框架提供集成包,做到真正的开箱即用。
除了支持 SpringBoot2、Sa-Token 还为 SpringBoot3/4、Solon、JFinal 等常见 Web 框架提供集成包,做到真正的开箱即用。
<details>
@@ -144,59 +154,133 @@ 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)。
我们将定期同步赞助者名单到在线文档展示。(您需要注意的一点是:该赞助仅为友情赞助,不提供任何商业交换)
**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>
### 🚀 优秀开源集成案例
- [[ 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)
### 友情链接
### 🌍 其它语言版本
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 请求
- [[ Bean Searcher ]](https://github.com/ejlchina/bean-searcher):专注高级查询的只读 ORM,使一行代码实现复杂列表检索!
@@ -209,31 +293,44 @@ 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)
### 交流群
### 📚 示例大全
**我们为框架几乎所有技术点均单独制作了对应的集成示例,此压缩包共计 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)-->
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="https://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 ..
+141 -86
View File
@@ -12,13 +12,14 @@
<!-- 项目介绍 -->
<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>
<!-- 所有模块 -->
<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>
@@ -37,10 +38,14 @@
<!-- 一些属性 -->
<properties>
<revision>1.44.0</revision>
<revision>1.45.0</revision>
<jdk.version>1.8</jdk.version>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
<!-- Maven GPG Plugin & Maven Central Portal -->
<maven-gpg-plugin.version>3.2.8</maven-gpg-plugin.version>
<central.publishing.maven.version>0.10.0</central.publishing.maven.version>
</properties>
<!-- 仓库信息 -->
@@ -54,20 +59,11 @@
<!-- 作者信息 -->
<developers>
<developer>
<name>shengzhang</name>
<name>click33</name>
<email>2393584716@qq.com</email>
</developer>
</developers>
<!-- 父仓库 -->
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
<relativePath/>
</parent>
<!-- 仓库依赖 -->
<dependencies>
@@ -75,7 +71,11 @@
<dependencyManagement>
<dependencies>
<!--
导入 sa-token-dependencies 所有版本定义,并传导到每个子项目。
需要注意的是:该 import 只会导入 <dependencyManagement> 部分,而不会导入 <dependencies> 部分和 <properties> 部分。
-->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dependencies</artifactId>
@@ -89,79 +89,133 @@
<!-- 项目构建 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.1</version>
<configuration>
<!-- 统一生成聚合文档,解决 mvn package 时控制台发出 javadoc 警告的问题 -->
<aggregate>true</aggregate>
<!-- 忽略部分 error 和 warning -->
<failOnError>false</failOnError>
<failOnWarnings>false</failOnWarnings>
<additionalOptions>-Xdoclint:none</additionalOptions>
<detectLinks>false</detectLinks>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>aggregate</goal>
</goals>
</execution>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<additionalparam>-Xdoclint:none</additionalparam>
</configuration>
</execution>
</executions>
</plugin>
<plugins>
<!-- 统一版本号管理 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.2.7</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<!-- Source -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 源码编译 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.15.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- API 文档 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.12.0</version>
<configuration>
<!-- 统一生成聚合文档,解决 mvn package 时控制台发出 javadoc 警告的问题 -->
<!-- <aggregate>true</aggregate> -->
<!-- 忽略部分 error 和 warning -->
<failOnError>false</failOnError>
<failOnWarnings>false</failOnWarnings>
<additionalOptions>-Xdoclint:none</additionalOptions>
<detectLinks>false</detectLinks>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>aggregate</goal>
</goals>
</execution>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<doclint>none</doclint>
</configuration>
</execution>
</executions>
</plugin>
<!-- flatten 统一版本号管理 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.7.3</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- gpg 签名 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>${maven-gpg-plugin.version}</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 新版 Central Portal 中央仓库上传 -->
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>${central.publishing.maven.version}</version>
<extensions>true</extensions>
<configuration>
<!-- 必须与 settings.xml 中 server 的 id 一致 -->
<publishingServerId>central</publishingServerId>
<!-- 是否自动发布。设为 true 后,上传完成无需手动点击发布 -->
<!-- <autoPublish>true</autoPublish> -->
<!-- 等待直到发布完成,让构建过程等待最终结果 -->
<!-- <waitUntil>published</waitUntil> -->
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.eclipse.m2e</groupId>
@@ -188,7 +242,8 @@
</configuration>
</plugin>
</plugins>
</pluginManagement>
</pluginManagement>
</build>
</project>
+100 -13
View File
@@ -13,9 +13,39 @@
<url>https://github.com/dromara/sa-token</url>
<properties>
<revision>1.44.0</revision>
<revision>1.45.0</revision>
<!-- Maven GPG Plugin & Maven Central Portal -->
<maven-gpg-plugin.version>3.2.8</maven-gpg-plugin.version>
<central.publishing.maven.version>0.10.0</central.publishing.maven.version>
</properties>
<!-- 开源协议 apache 2.0 -->
<licenses>
<license>
<name>Apache 2</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
<comments>A business-friendly OSS license</comments>
</license>
</licenses>
<!-- 仓库信息 -->
<scm>
<tag>master</tag>
<url>https://github.com/dromara/sa-token.git</url>
<connection>scm:git:https://github.com/dromara/sa-token.git</connection>
<developerConnection>scm:git:https://github.com/dromara/sa-token.git</developerConnection>
</scm>
<!-- 作者信息 -->
<developers>
<developer>
<name>click33</name>
<email>2393584716@qq.com</email>
</developer>
</developers>
<dependencyManagement>
<dependencies>
<!-- sa-token 核心 -->
@@ -51,6 +81,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>
@@ -68,7 +103,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>
@@ -81,6 +126,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 -->
@@ -94,6 +144,11 @@
<artifactId>sa-token-alone-redis</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-alone-redis-by-spring-boot4</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dubbo</artifactId>
@@ -119,6 +174,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>
@@ -239,21 +299,15 @@
</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>
@@ -261,11 +315,11 @@
</configuration>
</plugin>
<!-- 统一版本号管理 -->
<!-- flatten 统一版本号管理 -->
<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>
@@ -288,7 +342,40 @@
</executions>
</plugin>
<!-- gpg 签名 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>${maven-gpg-plugin.version}</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 新版 Central Portal 中央仓库上传 -->
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>${central.publishing.maven.version}</version>
<extensions>true</extensions>
<configuration>
<!-- 必须与 settings.xml 中 server 的 id 一致 -->
<publishingServerId>central</publishingServerId>
<!-- 是否自动发布。设为 true 后,上传完成无需手动点击发布 -->
<!-- <autoPublish>true</autoPublish> -->
<!-- 等待直到发布完成,让构建过程等待最终结果 -->
<!-- <waitUntil>published</waitUntil> -->
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
+2 -2
View File
@@ -13,10 +13,10 @@
<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 -->
<!-- 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
}
@@ -36,7 +36,7 @@ public class SaTokenConsts {
/**
* Sa-Token 当前版本号
*/
public static final String VERSION_NO = "v1.44.0";
public static final String VERSION_NO = "v1.45.0";
/**
* Sa-Token 开源地址 Gitee
@@ -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();
}
}
}
+3
View File
@@ -11,6 +11,7 @@
<modules>
<module>sa-token-demo-alone-redis</module>
<module>sa-token-demo-alone-redis-cluster</module>
<module>sa-token-demo-alone-redis-sb4</module>
<module>sa-token-demo-apikey</module>
<module>sa-token-demo-async</module>
<module>sa-token-demo-beetl</module>
@@ -35,6 +36,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 +57,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>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -0,0 +1,78 @@
<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-alone-redis-sb4</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.45.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>
<!-- Sa-Token整合 Redis -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template-jdk-serializer</artifactId>
<version>${sa-token.version}</version>
<scope>test</scope>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Sa-Token插件:权限缓存与业务缓存分离(Spring Boot 4 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-alone-redis-by-spring-boot4</artifactId>
<version>${sa-token.version}</version>
</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 示例,整合 alone-redis 插件(权限缓存与业务缓存分离)
*
* @author click33
*/
@SpringBootApplication
public class SaTokenAloneRedisSb4Application {
public static void main(String[] args) {
SpringApplication.run(SaTokenAloneRedisSb4Application.class, args);
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}
@@ -0,0 +1,40 @@
package com.pj.test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 测试专用Controller,演示 alone-redis 权限缓存与业务缓存分离
*
* @author click33
*/
@RestController
@RequestMapping("/test/")
public class TestController {
@Autowired
StringRedisTemplate stringRedisTemplate;
// 测试Sa-Token缓存,浏览器访问: http://localhost:8083/test/login
@RequestMapping("login")
public SaResult login(@RequestParam(defaultValue = "10001") String id) {
System.out.println("--------------- 测试Sa-Token缓存");
StpUtil.login(id);
return SaResult.ok();
}
// 测试业务缓存,浏览器访问: http://localhost:8083/test/test
@RequestMapping("test")
public SaResult test() {
System.out.println("--------------- 测试业务缓存");
stringRedisTemplate.opsForValue().set("hello", "Hello World");
return SaResult.ok();
}
}
@@ -0,0 +1,59 @@
# 端口
server:
port: 8083
# Sa-Token配置
sa-token:
# Token名称 (同时也是cookie名称)
token-name: satoken
# Token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# Token风格
token-style: uuid
# 配置Sa-Token单独使用的Redis连接
alone-redis:
# Redis数据库索引(默认为0
database: 2
# 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
# 配置业务使用的Redis连接
spring:
data:
redis:
# Redis数据库索引(默认为0
database: 0
# 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
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
+1 -1
View File
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
+1 -1
View File
@@ -18,7 +18,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
<java.run.main.class>com.pj.SaTokenAsyncApplication</java.run.main.class>
</properties>
+1 -1
View File
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -73,7 +73,7 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-bom</artifactId>
<version>1.44.0</version>
<version>1.45.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
+1 -1
View File
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
+1 -1
View File
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -18,7 +18,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
<java.run.main.class>com.pj.SaTokenDeviceLockApplication</java.run.main.class>
</properties>
@@ -17,7 +17,7 @@
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
<dubbo.version>2.7.21</dubbo.version>
<nacos.version>1.4.2</nacos.version>
</properties>
@@ -17,7 +17,7 @@
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
<dubbo.version>2.7.21</dubbo.version>
<nacos.version>1.4.2</nacos.version>
</properties>
@@ -18,7 +18,7 @@
<properties>
<java.version>17</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
<dubbo.version>3.2.2</dubbo.version>
<nacos.version>2.2.2</nacos.version>
</properties>
@@ -18,7 +18,7 @@
<properties>
<java.version>17</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
<dubbo.version>3.2.2</dubbo.version>
<nacos.version>2.2.2</nacos.version>
</properties>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
+1 -1
View File
@@ -27,7 +27,7 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<lombok.version>1.18.10</lombok.version>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
+1 -1
View File
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -18,7 +18,7 @@
<java.version>17</java.version>
<maven.source.version>17</maven.source.version>
<maven.compile.version>17</maven.compile.version>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -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>
<!-- 配置缓存读取 -->
@@ -17,7 +17,7 @@
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<!-- 定义 Sa-Token 版本号 -->
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -17,7 +17,7 @@
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<!-- 定义 Sa-Token 版本号 -->
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -17,7 +17,7 @@
</parent>
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
+1 -1
View File
@@ -19,7 +19,7 @@
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.traget>17</maven.compiler.traget>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
@@ -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 method) {
// 获取前端请求提交的参数
String name = SaHolder.getRequest().getParamNotNull("name");
String pwd = SaHolder.getRequest().getParamNotNull("pwd");
@@ -19,7 +19,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
<java.run.main.class>com.pj.SaTokenApplication</java.run.main.class>
<java.version>1.8</java.version>
</properties>
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.1</version>
<version>3.5.11</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -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.45.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,15 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FaviconController {
@RequestMapping("/favicon.ico")
public String favicon() {
return "";
}
}
@@ -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
+1 -1
View File
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
<java.run.main.class>com.pj.SaTokenSseApplication</java.run.main.class>
</properties>
+1 -1
View File
@@ -27,7 +27,7 @@
<!--<spring.version>4.2.5.RELEASE</spring.version>-->
<spring.version>5.3.7</spring.version>
<jackson.version>2.16.1</jackson.version>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -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,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -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
*/
@@ -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,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -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();
}
}
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>
+1 -1
View File
@@ -18,7 +18,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
<java.run.main.class>com.pj.SaTokenApplication</java.run.main.class>
</properties>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.44.0</sa-token.version>
<sa-token.version>1.45.0</sa-token.version>
</properties>
<dependencies>

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