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

Compare commits

..

201 Commits

Author SHA1 Message Date
click33 28513efffa Merge branch 'master' of https://gitee.com/dromara/sa-token into dev
# Conflicts:
#	README.md
2022-02-10 01:42:40 +08:00
click33 a333355b46 v1.29.0更新 2022-02-10 01:35:09 +08:00
click33 163053d6fa 完全移除 SaHistoryVersionInject 兼容类 2022-02-09 23:31:17 +08:00
click33 369d3ffb5b 完善单元测试. 2022-02-09 23:24:08 +08:00
click33 62acc8e852 sa-token springboot集成包 单元测试 2022-02-09 20:59:37 +08:00
click33 aac6ef9a3b Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2022-02-09 20:30:35 +08:00
click33 68521356ae 完善单元测试 2022-02-09 20:30:19 +08:00
省长 a2c40b7ae7 !99 【轻量级 PR】:链接地址不对
Merge pull request !99 from AppleOfGray/N/A
2022-02-09 01:42:06 +00:00
AppleOfGray 0f84c4440f 链接地址不对 2022-02-09 01:34:11 +00:00
click33 6048fe3e90 添加案例 2022-02-08 16:45:25 +08:00
click33 f9bd57c884 完善文档 2022-02-08 16:05:54 +08:00
省长 1cd2f8ab1b !98 update sa-token-doc/doc/oauth2/oauth2-server.md.
Merge pull request !98 from 拿客/N/A
2022-02-08 04:02:01 +00:00
拿客 5d62a9aeb1 update sa-token-doc/doc/oauth2/oauth2-server.md.
缺少 .setIsAutoMode(true) 会导致 {"code":500,"msg":"暂未开放的授权模式","data":null}
2022-02-08 03:33:41 +00:00
click33 0d4830eb7d 使OAuth2模块部分属性支持每个Client单独配置 2022-02-07 19:36:31 +08:00
click33 9e47461a0a 新增Token有效期timeout续期方法 2022-02-07 13:16:11 +08:00
click33 be7a043196 Merge branch 'dev' of github.com:dromara/sa-token into dev 2022-02-06 18:07:31 +08:00
click33 2c6523a143 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2022-02-06 18:07:02 +08:00
click33 116f7d0976 OAuth2模块与会话登录模块 数据互通 2022-02-06 18:06:55 +08:00
省长 1074394fb2 !97 新增使用 Sa-Token 的开源项目链接
Merge pull request !97 from 拾年之璐/N/A
2022-02-06 09:00:42 +00:00
拾年之璐 50b19b84d7 update README.md.
新增使用 Sa-Token 的开源项目链接。
2022-02-06 06:00:43 +00:00
click33 29b86891db Merge pull request #215 from uncarbon97/dev
docs: 更新 helio 脚手架友链文案
2022-02-05 15:29:54 +08:00
click33 91d90dd901 调整字体 2022-02-04 15:54:55 +08:00
click33 978c8dd71e Jackson定制版Session,避免timeout属性的序列化 2022-02-04 11:40:30 +08:00
click33 9ffc85e749 优化文档 2022-02-04 02:04:23 +08:00
Uncarbon b4590bfbc1 docs: 更新 helio 脚手架友链文案 2022-02-03 16:35:03 +08:00
click33 3d0fffbc9a 增加企业案例 2022-02-03 05:38:38 +08:00
click33 0163044a7c 添加使用案例 2022-01-24 18:08:07 +08:00
click33 ce277bfbc2 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2022-01-22 17:26:04 +08:00
省长 3ea86f1584 !96 feat: 增加BCrypt加密
Merge pull request !96 from dream/dev
2022-01-20 07:13:54 +00:00
dream 8767bd00d5 feat: 增加BCrypt加密 2022-01-20 10:26:02 +08:00
省长 e34200efb4 !95 今天有人误以为SaTempUtil登陆后StpUtil可以拿到数据, 这里加一句提示
Merge pull request !95 from AppleOfGray/N/A
2022-01-19 10:07:04 +00:00
AppleOfGray 1bc07e94c8 今天有人误以为SaTempUtil登陆后StpUtil可以拿到数据, 这里加一句提示
通过SaTempUtil可以拿到对应的临时登录id, 但StpUtil不行, 
也就是说临时token和当前的satoken登录体系是相互独立的
2022-01-19 08:01:50 +00:00
省长 d618257dd8 !94 update README.md.
Merge pull request !94 from JackuXL/N/A
2022-01-18 10:35:02 +00:00
JackuXL b84ce6144c update README.md.
添加 QForum-Core 到 “使用Sa-Token的开源项目” 部分
2022-01-18 10:33:54 +00:00
click33 7f33d6114a Merge branch 'dev' of github.com:dromara/sa-token into dev 2022-01-12 11:42:22 +08:00
click33 b3567e4cd7 Merge pull request #210 from Tai-ch0802/dev
新增 OAuth2 plugin 裡頭處理 Past-Client-Token 的設定配置
2022-01-12 11:38:19 +08:00
戴均鑑 Tai ebeafebc3d 新增 OAuth2 plugin 裡頭處理 Past-Client-Token 的設定配置
- 背景:在實務需求上,會很有機會需要讓前一個的 client token(credential mode) 並存,以處理多個併發的請求,但原有的行為則會間接允許相同的 client 簽發出兩個能同時被接受的 access token。
- 作法:參考 jwt-auth 設計的 [blacklist_grace_period](https://github.com/tymondesigns/jwt-auth/blob/develop/config/config.php#L238),允許另外的設定去配置 pastClientToken 的 ttl 。
- 影響:在預設不調整的情境下,套件使用者也可保留原本的行為,不需做任何更改。
2022-01-12 11:10:56 +08:00
省长 dd3ab971ac !93 update sa-token-doc/doc/more/link.md.
Merge pull request !93 from 大力水手/N/A
2022-01-10 09:17:11 +00:00
大力水手 62add1edc2 update sa-token-doc/doc/more/link.md.
1.加入自己使用Sa-Token开发的开源项目
2022-01-10 09:14:04 +00:00
click33 f13f128b65 添加案例 2022-01-08 11:03:37 +08:00
省长 58e21566f6 !92 增加使用Sa-Token和Sa-Token-Plugin的脚手架项目链接
Merge pull request !92 from bootx/N/A
2022-01-07 08:11:20 +00:00
bootx 62674497d3 增加使用Sa-Token和Sa-Token-Plugin的脚手架项目链接 2022-01-07 08:07:00 +00:00
省长 5cf282ca5d !91 update README.md.
Merge pull request !91 from falser/N/A
2022-01-06 09:15:03 +00:00
falser 1260b83129 update README.md. 2022-01-06 09:14:23 +00:00
省长 9eb10eae11 !90 增加一个使用Sa-Token的开源项目链接
Merge pull request !90 from falser/master
2022-01-06 07:44:40 +00:00
falser cdb1b0c540 update README.md. 2022-01-06 07:31:09 +00:00
省长 db75d5a19b update sa-token-doc/doc/more/link.md. 2021-12-31 05:58:26 +00:00
RockMan cd9f835713 update sa-token-doc/doc/use/at-check.md.
update sa-token-doc/doc/use/at-check.md.
2021-12-31 00:16:47 +00:00
click33 f4272c3ad4 文档样式优化 2021-12-28 23:35:56 +08:00
click33 2286b02881 优化文档 2021-12-25 07:35:10 +08:00
click33 9f194e73e2 重绘功能架构图 2021-12-24 12:41:59 +08:00
click33 f2d0fae8a4 优化文档样式 2021-12-24 12:35:08 +08:00
click33 1fa72d32f2 重构官网 2021-12-24 07:45:19 +08:00
click33 6ffd14973f 优化文档样式 2021-12-24 01:21:53 +08:00
click33 939568c7b1 优化文档样式 2021-12-24 00:58:01 +08:00
click33 4550ad74c8 添加企业案例 2021-12-23 19:27:59 +08:00
click33 d4d4bd129d 重构代码:jwt模式login添加扩展信息 2021-12-23 19:20:56 +08:00
click33 f9f10477eb dubbo-demo 增加测试样例 2021-12-23 05:33:09 +08:00
click33 7a53b5cb00 重构代码:优化Dubbo调用时向下传递Token的规则,可避免在项目启动时由于Context无效引发的bug 2021-12-23 04:52:36 +08:00
click33 e2463b7e03 重构代码:OAuth2 授权模式开放由全局配置和Client单独配置共同设定 2021-12-23 04:19:34 +08:00
click33 ebec86f0de Merge pull request #198 from fanchw/dev
将SaOAuth2Template类中converXxx相关方法名称改为convertXxx
2021-12-23 01:26:31 +08:00
click33 cf2f64ce78 Merge branch 'dev' into dev 2021-12-23 01:26:23 +08:00
click33 05c8a6b6f2 增加公司案例 2021-12-23 01:21:16 +08:00
click33 c50f55983a Merge branch 'dev' of github.com:dromara/sa-token into dev 2021-12-23 01:20:51 +08:00
省长 4ab6bdd66a !89 dubbo 处理上下文错误
Merge pull request !89 from 青年/dcy-dev
2021-12-22 17:04:33 +00:00
省长 b481589e85 !88 将检测授权类型交给client去校验而非全局
Merge pull request !88 from Tutty/dev
2021-12-22 16:54:27 +00:00
click33 a77042b891 Merge pull request #196 from fxbin/dev
Jwt token 添加额外扩展数据
2021-12-23 00:06:40 +08:00
dongchunyu 254be3b3e5 fix:优化传递token bug 2021-12-22 15:48:55 +08:00
dongchunyu 2c20e7baa0 fix:优化传递token bug 2021-12-22 15:44:45 +08:00
click33 85951637ec Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2021-12-18 20:17:32 +08:00
click33 cda9542cbd 更新捐赠列表 2021-12-18 20:17:08 +08:00
Lee 03fed93422 将检测授权类型交给client去校验而非全局 2021-12-16 22:14:33 +08:00
fcw 079f87ef4f 将converXxx相关方法名称改为convertXxx 2021-12-06 14:29:34 +08:00
fxbin 8cbb0a8646 jwt 扩展数据 2021-12-02 17:34:16 +08:00
省长 ad4f2fee7e !86 修复问题:回收accesstoken index 入参传错问题
Merge pull request !86 from 疯狂‖小强/dev
2021-12-01 08:38:52 +00:00
click33 5ad5a51426 新增打赏 2021-11-30 23:15:37 +08:00
hefangqiang c813f2d41f 修复问题:回收accesstoken index 入参传错问题 2021-11-30 16:19:52 +08:00
click33 1337922620 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2021-11-29 17:48:41 +08:00
click33 03c9fc14f1 timeline 新增数据 2021-11-29 17:48:30 +08:00
省长 33790c62b7 !85 sa-token-solon-plugin: 升级到 solon 1.6.1
Merge pull request !85 from 西东/dev
2021-11-25 10:29:18 +00:00
noear 54ecb04ce0 sa-token-solon-plugin: 升级到 solon 1.6.1 2021-11-25 18:27:36 +08:00
click33 25a649df95 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2021-11-22 22:51:10 +08:00
click33 4fee7774e6 timeline 添加记录 2021-11-22 22:50:54 +08:00
省长 138db72fbe !84 添加LocalTime的序列化与反序列化
Merge pull request !84 from naohu/dev
2021-11-20 16:48:27 +00:00
HlQ b862377333 [add] 添加LocalTime的序列化与反序列化 2021-11-21 00:45:46 +08:00
click33 76f58a3a6d timeline新增数据 2021-11-20 17:39:24 +08:00
省长 e54b9e6f85 !83 处理LocalDate和LocalDateTime持久化到redis时格式不为yyyy-MM-dd HH:mm:ss问题
Merge pull request !83 from 阿超/dev
2021-11-19 17:08:02 +00:00
阿超 18483373f6 实属抱歉,忘记拉取最新代码就开始改了。。。 2021-11-19 17:06:32 +00:00
VampireAchao 2e4c1da6dc 处理LocalDate和LocalDateTime持久化到redis时格式不为yyyy-MM-dd HH:mm:ss问题 2021-11-20 00:55:02 +08:00
click33 25cdfc3e55 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2021-11-18 16:53:37 +08:00
省长 42b07671ba !82 update sa-token-doc/doc/use/config.md.
Merge pull request !82 from 微笑/N/A
2021-11-18 08:48:02 +00:00
click33 022a26af10 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2021-11-18 16:46:51 +08:00
click33 698b457abe 文档优化. 2021-11-18 16:46:24 +08:00
微笑 ce53c35067 update sa-token-doc/doc/use/config.md.
[修改] 修改文档描述错误
2021-11-18 06:40:54 +00:00
省长 a256249fb5 !80 根据文档以及配置文件实际传达的思路,此处应是不同的Redis。
Merge pull request !80 from celee/dev
2021-11-16 13:51:36 +00:00
celee 4552b8a9fd 根据文档以及配置文件实际传达的思路,此处注释应是不同的Redis 2021-11-16 13:04:57 +00:00
click33 96afba67fd 部署所有历史版本文档 2021-11-16 19:32:48 +08:00
click33 b221b82610 完善文档 link 2021-11-16 19:14:38 +08:00
click33 113a4238b7 优化文档样式 2021-11-16 18:54:51 +08:00
click33 981b51faaa Merge branch 'dev' of github.com:dromara/sa-token into dev 2021-11-12 15:15:20 +08:00
click33 6351cd5dd5 优化文档 2021-11-12 15:14:39 +08:00
click33 801c3344d1 Merge pull request #185 from ejlchina/patch-2
Update README.md
2021-11-09 14:16:45 +08:00
周旭 61f9b0b158 Update README.md 2021-11-09 13:04:09 +08:00
周旭 6b85c779ba Update README.md 2021-11-09 11:16:05 +08:00
click33 31eb525f4a 优化dubbo插件文档 2021-11-08 16:22:27 +08:00
click33 7cff4cc47c Merge pull request #184 from uncarbon97/docs/1.28.0-plugin_dubbo
docs 补充Dubbo插件文档
2021-11-08 16:07:42 +08:00
Uncarbon bd0443c3cf docs 订正变量名错误 2021-11-08 10:36:34 +08:00
Uncarbon 658284bed3 docs 补充Dubbo插件文档 2021-11-08 10:34:37 +08:00
click33 b8aacfe625 更新文档badge 2021-11-08 00:50:14 +08:00
click33 6b80afe806 v1.28.0 更新. 2021-11-06 00:14:41 +08:00
click33 d3113ed6c9 v1.28.0 更新 2021-11-06 00:08:07 +08:00
click33 8ad2e3db96 新增常见问题整理 2021-11-05 18:55:14 +08:00
click33 2b18084cbc 文档新增:Sa-Token插件开发指南 2021-11-05 17:25:07 +08:00
click33 c830ed77ca 文档 名称解释 2021-11-02 18:00:04 +08:00
click33 652e6172af 新增 Dubbo 集成插件 2021-11-01 09:52:57 +08:00
click33 a1bab9e747 修复文档部分错误 2021-10-30 00:54:01 +08:00
click33 060ed055d0 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2021-10-30 00:37:23 +08:00
click33 068f838165 新增二级Context模式 2021-10-30 00:37:04 +08:00
省长 eb1ec5676c !78 修改Oauth2 凭证式缺少校验scope 问题
Merge pull request !78 from 茉莉/dev
2021-10-27 03:10:00 +00:00
moli 1bc0f51845 修改Oauth2 凭证式鉴权缺少校验scope 问题 2021-10-27 10:58:12 +08:00
省长 daff33df13 !77 update sa-token-doc/doc/use/jur-auth.md.
Merge pull request !77 from thrgo/N/A
2021-10-27 01:37:44 +00:00
潘业鑫 41231a87d1 update sa-token-doc/doc/use/jur-auth.md.
可参考代码:[码云:StpInterfaceImpl.java],链接地址错误
2021-10-26 12:44:41 +00:00
省长 9e45312924 update sa-token-doc/doc/sso/sso-h5.md. 2021-10-26 05:46:57 +00:00
省长 a8a702fe13 !76 sa-token-solon-plugin: 升级到 solon 1.5.50
Merge pull request !76 from 林西东/dev
2021-10-25 17:31:43 +00:00
noear 50624d0f47 sa-token-solon-plugin: 升级到 solon 1.5.50 2021-10-25 20:31:17 +08:00
省长 4807429bf1 !75 update sa-token-doc/doc/up/remember-me.md.
Merge pull request !75 from Admin/N/A
2021-10-25 02:20:17 +00:00
Admin 2bddb0df89 update sa-token-doc/doc/up/remember-me.md.
文档错误修改
2021-10-25 02:13:25 +00:00
click33 3929b16dfb Merge pull request #177 from ejlchina/patch-1
添加 友情链接
2021-10-22 23:06:10 +08:00
周旭 2e7c0ac590 添加 友情链接 2021-10-22 23:05:16 +08:00
click33 a38029e060 优化文档推荐公众号列表、开源大事记 2021-10-21 10:04:25 +08:00
click33 c4160e4fd3 优化文档 2021-10-21 02:43:27 +08:00
click33 4003de98c6 优化readme 2021-10-21 02:22:32 +08:00
click33 fb017d9718 优化文档 2021-10-21 02:05:10 +08:00
click33 657623f910 项目目录介绍 2021-10-21 01:29:03 +08:00
click33 7369977241 jwt插件文档 2021-10-21 01:09:31 +08:00
click33 bb5c378f48 jwt集成新增mix模式 2021-10-20 22:49:54 +08:00
click33 f9ec6e6487 v1.27.1 beta. 细节优化 2021-10-19 20:43:13 +08:00
click33 82591e397f Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2021-10-19 20:37:36 +08:00
省长 73d4fe5071 !73 处理session持久化到redis时,LocalDateTime等java8的新时间类序列化失败的问题
Merge pull request !73 from 阿超/dev
2021-10-19 12:35:51 +00:00
省长 a3677c4afc !74 update sa-token-doc/doc/up/many-account.md.
Merge pull request !74 from AppleOfGray/N/A
2021-10-19 12:23:23 +00:00
AppleOfGray 9bffe60112 update sa-token-doc/doc/up/many-account.md.
给出不同体系不同临时过期时间的解决方案
2021-10-19 11:37:23 +00:00
VampireAchao 99dffba4eb 一行代码处理session持久化到redis时,LocalDateTime等java8的新时间类序列化失败的问题 2021-10-19 12:20:45 +08:00
click33 634a3be1aa jwt整合模块添加单元测试 2021-10-19 05:01:13 +08:00
click33 7740ab0a3f v1.27.1 beta. 2021-10-18 22:05:56 +08:00
click33 6d26761fd5 v1.27.1 新增jwt集成插件 2021-10-18 22:05:26 +08:00
省长 4a91553a77 update sa-token-doc/doc/use/jur-auth.md.
修复 GlobalException 代码示例路径问题
2021-10-13 14:59:10 +00:00
click33 30dad24302 优化readme 2021-10-12 22:35:59 +08:00
click33 ed7b1cef69 优化文档 2021-10-12 00:06:03 +08:00
click33 17235e0d2c 优化文档 2021-10-11 23:44:34 +08:00
click33 8a72c69474 v1.27.0 2021-10-11 19:44:02 +08:00
click33 3bec16627e 完善新版本特性文档 2021-10-11 01:09:28 +08:00
click33 d491f4083f 重构SSO模块,抽离三种模式的统一认证中心 2021-10-09 05:43:31 +08:00
click33 643118177a 增加Cookie模式的secure、httpOnly、sameSite等配置 2021-10-05 23:51:04 +08:00
click33 cf6632df79 增强 SaRouter 链式匹配能力 2021-10-04 17:58:53 +08:00
省长 474a560691 !72 sa-token-solon-plugin: 升级到 solon 1.5.40
Merge pull request !72 from 刘西东/dev
2021-10-04 02:25:24 +00:00
noear 040777be7c sa-token-solon-plugin: 升级到 solon 1.5.40 2021-10-04 10:21:05 +08:00
click33 977ab3ed40 @SaCheckPermission 增加 orRole 字段,用于权限角色“双重or”匹配 2021-10-02 18:26:13 +08:00
click33 b409df78b3 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2021-10-02 02:39:45 +08:00
click33 22141193d4 添加404处理示例 2021-10-02 02:39:37 +08:00
省长 d7f7a89af2 update sa-token-doc/doc/more/common-questions.md. 2021-10-01 17:32:45 +00:00
click33 e5f751d004 Merge branch 'dev' of github.com:dromara/sa-token into dev 2021-10-01 22:55:19 +08:00
click33 d107b6b341 新增 Thymeleaf 标签方言插件 2021-10-01 22:54:56 +08:00
click33 dc1e01509d Merge pull request #168 from Pancratius/patch-1
错别字
2021-10-01 15:54:20 +08:00
click33 f260f6028a 权限认证增加API:hasPermissionAnd、hasPermissionOr
角色认证增加API:hasRoleAnd、hasRoleOr
2021-10-01 04:20:40 +08:00
click33 4ba55ff687 重构注销相关API 2021-10-01 04:01:35 +08:00
Pancras 3e0ae863e5 错别字 2021-09-30 14:03:30 +08:00
click33 826760f160 v1.26.2 beta. SaStrategy增加使用样例 2021-09-30 02:52:11 +08:00
click33 dc6b0ba061 v1.26.2 beta . 2021-09-30 02:40:13 +08:00
click33 ba5784abec 临时认证模块新增 deleteToken 方法用于回收 Token 2021-09-30 02:38:15 +08:00
click33 b7dba13cab 优化注释和文档 2021-09-29 23:43:01 +08:00
click33 713e11482c 废弃 SaTokenAction 接口,新增 SaStrategy 策略类 2021-09-28 23:57:56 +08:00
click33 356e65f749 Merge pull request #165 from anaer/patch-1
Update SaResponseForReactor.java
2021-09-27 21:05:29 +08:00
anaer e254167398 Update SaResponseForReactor.java
typo
2021-09-27 21:01:39 +08:00
click33 7b2d402acb 添加案例 RuoYi-Vue-Plus 2021-09-27 02:42:10 +08:00
click33 0e16f76700 修改QQ群链接 2021-09-27 02:37:28 +08:00
click33 3e09e68b6e Merge branch 'dev' of github.com:dromara/sa-token into dev 2021-09-27 01:35:47 +08:00
click33 022e286904 Merge pull request #164 from ooknight/dev
增加Cookie安全性,支持设置Cookie的HttpOnly和Secure属性
2021-09-27 01:21:59 +08:00
ooknight 7acf5e9790 增加Cookie安全性,支持设置Cookie的HttpOnly和Secure属性 2021-09-27 01:09:30 +08:00
click33 4fb1ea96fb Merge pull request #161 from JavaLionLi/patch-2
增加 1.2K star的开源项目
2021-09-24 16:29:23 +08:00
CrazyLionLi 32bb677e26 增加 1.2K star的开源项目 2021-09-24 11:37:38 +08:00
click33 9aa6e5db42 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2021-09-22 10:17:30 +08:00
click33 854774a1f1 修复路由鉴权文档示例错误之处 2021-09-22 10:17:14 +08:00
省长 73e2b2d593 !71 update sa-token-doc/doc/use/config.md.
Merge pull request !71 from 晨鹤/N/A
2021-09-20 15:20:15 +00:00
晨鹤 fd8d0f6d92 update sa-token-doc/doc/use/config.md.
文档修复 `tokenPrefix` 配置描述错误
2021-09-20 14:59:05 +00:00
click33 a0715b8aea 文档新增自定义 SaTokenContext 指南 2021-09-15 22:15:50 +08:00
click33 9a58702eb3 新增SSO与OAuth2技术对比文档 2021-09-15 02:19:06 +08:00
click33 cff04c331c 增加常见报错排查方案 2021-09-15 01:15:45 +08:00
click33 23a85700af 文档增加示例 2021-09-14 22:18:42 +08:00
click33 97bc0211d7 . 2021-09-13 16:51:48 +08:00
click33 6ccb8ea38b 优化文档 2021-09-13 03:36:54 +08:00
click33 765c4ae9f2 更换微信群二维码 2021-09-13 01:52:43 +08:00
省长 23da73f032 update sa-token-doc/doc/fun/curr-domain.md. 2021-09-12 17:11:39 +00:00
click33 99a45be22f 更改issues模板 2021-09-11 19:09:04 +08:00
click33 d029a28488 完善注解鉴权demo 2021-09-11 00:22:07 +08:00
click33 5dc1aeb774 v1.26.1 beta. 2021-09-09 02:41:59 +08:00
click33 9bd367b877 v1.26.1 beta 2021-09-09 02:37:40 +08:00
省长 3a4c1ef8f0 update sa-token-doc/doc/use/jur-auth.md. 2021-09-06 11:51:01 +00:00
省长 2e5b166ab9 !70 sa-token-solon-plugin: 升级到 solon 1.5.27
Merge pull request !70 from 刘西东/dev
2021-09-02 14:53:09 +00:00
noear 37d38ba2b4 sa-token-solon-plugin: 升级到 solon 1.5.27 2021-09-02 16:37:43 +08:00
省长 d6e11ce5d9 !69 1.2.有按顺序加入的意思, 修改为方式A和方式B, 避免歧义
Merge pull request !69 from AppleOfGray/N/A
2021-09-02 03:02:58 +00:00
AppleOfGray 96c279f06c 1.2.有按顺序加入的意思, 修改为方式A和方式B, 避免歧义 2021-09-02 03:01:25 +00:00
332 changed files with 13121 additions and 4128 deletions
+18
View File
@@ -0,0 +1,18 @@
### 使用版本:
请提供一下版本号
### 报错信息:
请提供报错的详细信息
### 希望结果:
相比于已发生的报错,您希望看到什么样的运行结果
### 复现步骤:
如果复现步骤比较复杂,请将 demo 上传到 git 并留下地址
备注:您提供的信息越充足,我们将越能快速的定位错误
+18
View File
@@ -0,0 +1,18 @@
### 使用版本:
请提供一下版本号
### 报错信息:
请提供报错的详细信息
### 希望结果:
相比于已发生的报错,您希望看到什么样的运行结果
### 复现步骤:
如果复现步骤比较复杂,请将 demo 上传到 git 并留下地址
备注:您提供的信息越充足,我们将越能快速的定位错误
+59 -45
View File
@@ -1,11 +1,11 @@
<p align="center">
<img alt="logo" src="https://gitee.com/dromara/sa-token/raw/master/sa-token-doc/doc/logo.png" width="150" height="150">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.26.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.29.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"></a>
<a href="https://gitee.com/dromara/sa-token/members"><img src="https://gitee.com/dromara/sa-token/badge/fork.svg"></a>
<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://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>
@@ -18,7 +18,10 @@
## 前言:
- [在线文档:http://sa-token.dev33.cn/](http://sa-token.dev33.cn/)
- 我们将会尽力讲解每个功能的设计原因、应用场景,用心阅读文档,你学习到的将不止是 `Sa-Token` 框架本身,更是绝大多数场景下权限设计的最佳实践
- 注:学习测试请拉取 master 分支,dev 为正在开发的分支,有很多特性并不稳定
- 开源不易,点个 star 鼓励一下吧!
## Sa-Token 介绍
@@ -56,33 +59,32 @@ public String insert(SysUser user) {
将某个账号踢下线(待到对方再次访问系统时会抛出`NotLoginException`异常)
``` java
// 使账号id为 10001 的会话强制注销登录
StpUtil.logoutByLoginId(10001);
// 账号id为 10001 的会话踢下线
StpUtil.kickout(10001);
```
在 Sa-Token 中,绝大多数功能都可以 **一行代码** 完成:
``` java
StpUtil.login(10001); // 标记当前会话登录的账号id
StpUtil.getLoginId(); // 获取当前会话登录的账号id
StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false
StpUtil.logout(); // 当前会话注销登录
StpUtil.logoutByLoginId(10001); // 账号为10001的会话注销登录(踢人下线
StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识, 返回true或false
StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false
StpUtil.getSession(); // 获取当前账号id的Session
StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session
StpUtil.login(10001); // 标记当前会话登录的账号id
StpUtil.getLoginId(); // 获取当前会话登录的账号id
StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false
StpUtil.logout(); // 当前会话注销登录
StpUtil.kickout(10001); // 账号为10001的会话下线
StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识, 返回true或false
StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false
StpUtil.getSession(); // 获取当前账号id的Session
StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session
StpUtil.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值
StpUtil.login(10001, "PC"); // 指定设备标识登录,常用于“同端互斥登录”
StpUtil.logoutByLoginId(10001, "PC"); // 指定设备标识进行强制注销 (不同端不受影响)
StpUtil.openSafe(120); // 在当前会话开启二级认证,有效期为120秒
StpUtil.checkSafe(); // 校验当前会话是否处于二级认证有效期内,校验失败会抛出异常
StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
StpUtil.login(10001, "PC"); // 指定设备标识登录,常用于“同端互斥登录”
StpUtil.kickout(10001, "PC"); // 指定账号指定设备标识踢下线 (不同端不受影响)
StpUtil.openSafe(120); // 在当前会话开启二级认证,有效期为120秒
StpUtil.checkSafe(); // 校验当前会话是否处于二级认证有效期内,校验失败会抛出异常
StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
```
即使不运行测试,相信您也能意会到绝大多数 API 的用法。
## Sa-Token 功能一览
- **登录认证** —— 单端登录、多端登录、同端互斥登录、七天内免登录
@@ -117,44 +119,37 @@ StpUtil.switchTo(10044); // 将当前会话身份临时切换
## Sa-Token-SSO 单点登录
对于单点登录,网上教程大多以CAS模式为主,其实对于不同的系统架构,实现单点登录的步骤也大为不同,Sa-Token由简入难将其划分为三种模式:
网上的单点登录教程大多以CAS流程为主,其实对于不同的系统架构,实现单点登录的步骤也大为不同,Sa-Token由简入难将其划分为三种模式:
| 系统架构 | 采用模式 | 简介 | 文档链接 |
| :-------- | :-------- | :-------- | :-------- |
| 前端同域 + 后端同 Redis | 模式一 | 共享Cookie同步会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso1) |
| 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso2-server) |
| 前端不同域 + 后端 不同Redis | 模式三 | Http请求获取会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso3-server) |
| 前端同域 + 后端同 Redis | 模式一 | 共享Cookie同步会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso1-client) |
| 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso2-client) |
| 前端不同域 + 后端 不同Redis | 模式三 | Http请求获取会话 | [文档](http://sa-token.dev33.cn/doc/index.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,其它的缓存数据中心亦可。PS:这里并不需要把所有项目的数据都放在同一个Redis中,Sa-Token提供了 **`[权限缓存与业务缓存分离]`** 的解决方案,详情戳:[Alone独立Redis插件](http://sa-token.dev33.cn/doc/index.html#/plugin/alone-redis)
2. 后端同Redis:就是指多个系统可以连接同一个Redis。PS:这里并不需要把所有项目的数据都放在同一个Redis中,Sa-Token提供了 **`[权限缓存与业务缓存分离]`** 的解决方案,详情戳:[Alone独立Redis插件](http://sa-token.dev33.cn/doc/index.html#/plugin/alone-redis)
3. 如果既无法做到前端同域,也无法做到后端同Redis,那么只能走模式三,Http请求获取会话(Sa-Token对SSO提供了完整的封装,你只需要按照示例从文档上复制几段代码便可以轻松集成)
4. 技术选型一定要根据系统架构对症下药,切不可胡乱选择
## Sa-Token-SSO 特性
1. API简单易用,文档介绍详细,且提供直接可用的集成示例
2. 支持三种模式,不论是否跨域、是否共享Redis、是否前后端分离,都可以完美解决
3. 安全性高:内置域名校验、Ticket校验、秘钥校验等,杜绝`Ticket劫持`、`Token窃取`等常见攻击手段(文档讲述攻击原理和防御手段)
4. 不丢参数:笔者曾试验多个单点登录框架,均有参数丢失的情况,比如重定向之前是:`http://a.com?id=1&name=2`,登录成功之后就变成了:`http://a.com?id=1`Sa-Token-SSO内有专门的算法保证了参数不丢失,登录成功之后原路返回页面
5. 无缝集成:由于Sa-Token本身就是一个权限认证框架,因此你可以只用一个框架同时解决`权限认证` + `单点登录`问题,让你不再到处搜索:xxx单点登录与xxx权限认证如何整合……
6. 高可定制:Sa-Token-SSO模块对代码架构侵入性极低,结合Sa-Token本身的路由拦截特性,你可以非常轻松的定制化开发
## Sa-Token-OAuth2.0 授权登录
Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749) 编写,通过Sa-OAuth2你可以非常轻松的实现系统的OAuth2.0授权认证
1. 授权码(Authorization Code):OAuth2.0标准授权步骤,Server端向Client端下放Code码,Client端再用Code码换取授权Token
2. 隐藏式(Implicit):无法使用授权码模式时的备用选择,Server端使用URL重定向方式直接将Token下放到Client端页面
3. 密码式(Password):Client直接拿着用户的账号密码换取授权Token
4. 客户端凭证(Client Credentials):Server端针对Client级别的Token,代表应用自身的资源授权
| 授权模式 | 简介 |
| :-------- | :-------- |
| 授权码(Authorization Code | OAuth2.0 标准授权步骤,Server 端向 Client 端下放 Code 码,Client 端再用 Code 码换取授权 Token |
| 隐藏式(Implicit) | 无法使用授权码模式时的备用选择,Server 端使用 URL 重定向方式直接将 Token 下放到 Client 端页面 |
| 密码式(Password | Client直接拿着用户的账号密码换取授权 Token |
| 客户端凭证(Client Credentials| Server 端针对 Client 级别的 Token,代表应用自身的资源授权 |
详细参考文档:[http://sa-token.dev33.cn/doc/index.html#/oauth2/readme](http://sa-token.dev33.cn/doc/index.html#/oauth2/readme)
## Sa-Token 功能结构图
![sa-token-js](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/x/sa-token-js3.png 's-w')
![sa-token-js](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/x/sa-token-js4.png 's-w')
![sa-token-rz](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/x/sa-token-rz2.png 's-w')
@@ -172,17 +167,29 @@ Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749)
- **[ dcy-fast ]**[ 一个基于 SpringBoot + Sa-Token + Mybatis-Plus 的后台管理系统,前端vue-element-admin,并且内置代码生成器](https://gitee.com/dcy421/dcy-fast)
- **[ helio-starters ]**[ 基于JDK15 + Spring Boot 2.4 + Sa-Token + Mybatis-Plus的单体Boot版脚手架微服务Cloud版脚手架,带有配套后台管理前端模板及代码生成器](https://gitee.com/uncarbon97/helio-starters)
- **[ helio-starters ]**[ 单体 Boot 版脚手架 + 微服务 Cloud 版脚手架,带有配套后台管理前端模板及代码生成器](https://gitee.com/uncarbon97/helio-starters)
- **[ sa-token-plugin ]**[Sa-Token第三方插件实现,基于Sa-Token-Core,提供一些与官方不同实现机制的的插件集合,作为Sa-Token开源生态的补充](https://gitee.com/bootx/sa-token-plugin)
- **[ easy-admin ]**[一个基于SpringBoot2 + Sa-Token + Mybatis-Plus + Snakerflow + Layui 的后台管理系统,灵活多变可前后端分离,也可单体,内置代码生成器、权限管理、工作流引擎等](https://gitee.com/lakernote/easy-admin)
- **[ RuoYi-Vue-Plus ]**[基于 RuoYi-Vue 集成 SaToken + Lombok + Mybatis-Plus + Undertow + knife4j + Hutool + Feign 重写所有原生业务 定期与 RuoYi-Vue 同步](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/satoken/)
- **[ falser-cloud ]**: [基于 SpringCloud Alibaba + SpringCloud gateway + SpringBoot + Sa-Token + vue-admin-template + Nacos + Rabbit MQ + Redis 的一个后台管理系统,前后端分离,权限管理,菜单管理,数据字典,停车场系统管理等功能](https://gitee.com/falser/falser-cloud)
- **[ bootx-platform ]**[集成sa-token和sa-token-plugin并深度定制认证模块,包含多级别数据范围权限、数据自动加解密、数据脱敏、超级查询器、以及支付收单、消息通知等准商用功能的开源免费开发脚手架项目](https://gitee.com/bootx/bootx-platform)
- **[ QForum-Core ]**[QForum 论坛系统官方核心,可拓展性强、轻量级、高性能、前后端分离,基于 SpringBoot2 + Sa-Token + Mybatis-Plus](https://github.com/Project-QForum/QForum-Core/)
- **[ ExciteCMS-Layui ]**[ExciteCMS 快速开发脚手架:一款后端基于 SpringBoot2 + Sa-Token + Mybatis-Plus,前端基于 Layuimini 的内容管理系统,具备RBAC、日志管理、代码生成等功能,并集成常用的支付、OSS等第三方服务,拥有详细的开发文档](https://gitee.com/ExciteTeam/ExciteCMS-SpringBoot-Layui)
如果您的项目使用了Sa-Token,欢迎提交pr
## 友情链接
- **[ OkHttps ]**[ 一个轻量级http通信框架,API设计无比优雅,支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps)
- **[ Bean Searcher ]**[ 比 MyBatis 效率快 100 倍的条件检索引擎,天生支持联表,使一行代码实现复杂列表检索成为可能!](https://github.com/ejlchina/bean-searcher)
- **[ 小诺快速开发平台 ]**[ 基于SpringBoot2 + AntDesignVue全新快速开发平台,同时拥有三个版本](https://xiaonuo.vip/index#pricing)
- **[ Jpom ]**[ 简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件](https://gitee.com/dromara/Jpom)
@@ -190,16 +197,23 @@ Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749)
- **[ TLog ]**[ 一个轻量级的分布式日志标记追踪神器](https://gitee.com/dromara/TLog)
## 贡献者名单
感谢每一个为 Sa-Token 贡献代码的小伙伴
[![Giteye chart](https://chart.giteye.net/gitee/dromara/sa-token/CGZ7GT8E.png)](https://giteye.net/chart/CGZ7GT8E)
## 交流群
QQ交流群:1002350610 [点击加入](https://jq.qq.com/?_wv=1027&k=45H977HM)
QQ交流群:1群:1002350610 (已满) 、
2群:614714762 [点击加入](https://jq.qq.com/?_wv=1027&k=b759RZrL)
微信交流群:
![微信群](https://dev33-test.oss-cn-beijing.aliyuncs.com/sa-token/doc/km/sa-token-hm1.jpg ':size=230')
<!--
<!-- ![微信群](https://dev33-test.oss-cn-beijing.aliyuncs.com/sa-token/doc/km/sa-token-hm1.jpg ':size=230') -->
![微信群](https://dev33-test.oss-cn-beijing.aliyuncs.com/sa-token/i-wx-qr.png ':size=230')
(扫码添加微信,备注:sa-token,邀您加入群聊)
-->
<br>
+24 -10
View File
@@ -37,11 +37,11 @@ cd sa-token-demo-alone-redis
call mvn clean
cd ..
cd sa-token-demo-sso1
cd sa-token-demo-thymeleaf
call mvn clean
cd ..
cd sa-token-demo-sso1-server
cd sa-token-demo-sso-server
call mvn clean
cd ..
@@ -49,29 +49,43 @@ cd sa-token-demo-sso1-client
call mvn clean
cd ..
cd sa-token-demo-sso2-server
call mvn clean
cd ..
cd sa-token-demo-sso2-client
call mvn clean
cd ..
cd sa-token-demo-sso3-server
call mvn clean
cd ..
cd sa-token-demo-sso3-client
call mvn clean
cd ..
cd sa-token-demo-jwt
call mvn clean
cd ..
cd sa-token-demo-jwt
call mvn clean
cd ..
cd sa-token-demo-dubbo-provider
call mvn clean
cd ..
cd sa-token-demo-dubbo-consumer
call mvn clean
cd ..
cd ..
:: test clean
cd sa-token-test
call mvn clean
cd ..
:: 最后打印
echo;
echo;
+3 -2
View File
@@ -8,7 +8,7 @@
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<packaging>pom</packaging>
<version>1.26.0</version>
<version>1.29.0</version>
<!-- 项目介绍 -->
<name>sa-token</name>
@@ -21,6 +21,7 @@
<module>sa-token-core</module>
<module>sa-token-starter</module>
<module>sa-token-plugin</module>
<!-- <module>sa-token-test</module> -->
<!-- <module>sa-token-demo/sa-token-demo-solon</module> -->
</modules>
@@ -36,7 +37,7 @@
<!-- 一些属性 -->
<properties>
<sa-token-version>1.26.0</sa-token-version>
<sa-token-version>1.29.0</sa-token-version>
<jdk.version>1.8</jdk.version>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.26.0</version>
<version>1.29.0</version>
</parent>
<packaging>jar</packaging>
@@ -9,6 +9,7 @@ import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.config.SaTokenConfigFactory;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.context.SaTokenContextDefaultImpl;
import cn.dev33.satoken.context.second.SaTokenSecondContext;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.SaTokenDaoDefaultImpl;
import cn.dev33.satoken.exception.SaTokenException;
@@ -18,21 +19,22 @@ import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.temp.SaTempInterface;
import cn.dev33.satoken.temp.SaTempDefaultImpl;
import cn.dev33.satoken.temp.SaTempInterface;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* 管理 Sa-Token 所有接口对象
* 管理 Sa-Token 所有全局组件
* @author kong
*
*/
@SuppressWarnings("deprecation")
public class SaManager {
/**
* 配置文件 Bean
*/
public static SaTokenConfig config;
public volatile static SaTokenConfig config;
public static void setConfig(SaTokenConfig config) {
SaManager.config = config;
if(config.getIsPrint()) {
@@ -55,7 +57,7 @@ public class SaManager {
/**
* 持久化 Bean
*/
private static SaTokenDao saTokenDao;
private volatile static SaTokenDao saTokenDao;
public static void setSaTokenDao(SaTokenDao saTokenDao) {
if((SaManager.saTokenDao instanceof SaTokenDaoDefaultImpl)) {
((SaTokenDaoDefaultImpl)SaManager.saTokenDao).endRefreshThread();
@@ -76,7 +78,7 @@ public class SaManager {
/**
* 权限认证 Bean
*/
private static StpInterface stpInterface;
private volatile static StpInterface stpInterface;
public static void setStpInterface(StpInterface stpInterface) {
SaManager.stpInterface = stpInterface;
}
@@ -94,7 +96,7 @@ public class SaManager {
/**
* 框架行为 Bean
*/
private static SaTokenAction saTokenAction;
private volatile static SaTokenAction saTokenAction;
public static void setSaTokenAction(SaTokenAction saTokenAction) {
SaManager.saTokenAction = saTokenAction;
}
@@ -110,27 +112,55 @@ public class SaManager {
}
/**
* 容器操作 Bean
* 上下文Context Bean
*/
private static SaTokenContext saTokenContext;
private volatile static SaTokenContext saTokenContext;
public static void setSaTokenContext(SaTokenContext saTokenContext) {
SaManager.saTokenContext = saTokenContext;
}
public static SaTokenContext getSaTokenContext() {
if (saTokenContext == null) {
synchronized (SaManager.class) {
if (saTokenContext == null) {
setSaTokenContext(new SaTokenContextDefaultImpl());
}
return saTokenContext;
}
/**
* 二级Context
*/
private volatile static SaTokenSecondContext saTokenSecondContext;
public static SaTokenSecondContext getSaTokenSecondContext() {
return saTokenSecondContext;
}
public static void setSaTokenSecondContext(SaTokenSecondContext saTokenSecondContext) {
SaManager.saTokenSecondContext = saTokenSecondContext;
}
/**
* 获取一个可用的SaTokenContext
* @return /
*/
public static SaTokenContext getSaTokenContextOrSecond() {
// s1. 一级Context可用时返回一级Context
if(saTokenContext != null) {
if(saTokenSecondContext == null || saTokenContext.isValid()) {
// 因为 isValid 是一个耗时操作,所以此处假定:二级Context为null的情况下无需验证一级Context有效性
// 这样可以提升6倍左右的上下文获取速度
return saTokenContext;
}
}
return saTokenContext;
// s2. 一级Context不可用时判断二级Context是否可用
if(saTokenSecondContext != null && saTokenSecondContext.isValid()) {
return saTokenSecondContext;
}
// s3. 都不行,就返回默认的 Context
return SaTokenContextDefaultImpl.defaultContext;
}
/**
* 侦听器 Bean
*/
private static SaTokenListener saTokenListener;
private volatile static SaTokenListener saTokenListener;
public static void setSaTokenListener(SaTokenListener saTokenListener) {
SaManager.saTokenListener = saTokenListener;
}
@@ -148,7 +178,7 @@ public class SaManager {
/**
* 临时令牌验证模块 Bean
*/
private static SaTempInterface saTemp;
private volatile static SaTempInterface saTemp;
public static void setSaTemp(SaTempInterface saTemp) {
SaManager.saTemp = saTemp;
}
@@ -1,16 +1,19 @@
package cn.dev33.satoken.action;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
import cn.dev33.satoken.session.SaSession;
/**
* Sa-Token 逻辑代理接口
* <h1> v1.27+ 此接口已废弃,目前版本暂时向下兼容,请及时更换为 SaStrategy </h1>
* <p>Sa-Token 逻辑代理接口 </p>
* <p>此接口将会代理框架内部的一些关键性逻辑,方便开发者进行按需重写</p>
* @author kong
*
*/
@Deprecated
public interface SaTokenAction {
/**
@@ -42,4 +45,10 @@ public interface SaTokenAction {
*/
public void checkMethodAnnotation(Method method);
/**
* 从指定元素校验注解
* @param target /
*/
public void validateAnnotation(AnnotatedElement target);
}
@@ -13,14 +13,17 @@ import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.basic.SaBasicUtil;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaTokenConsts;
/**
* Sa-Token 逻辑代理接口 [默认实现类]
* <h1> v1.27+ 此接口已废弃,目前版本暂时向下兼容,请及时更换为 SaStrategy </h1>
* <p> Sa-Token 逻辑代理接口 [默认实现类] </p>
* @author kong
*
*/
@Deprecated
public class SaTokenActionDefaultImpl implements SaTokenAction {
/**
@@ -110,37 +113,38 @@ public class SaTokenActionDefaultImpl implements SaTokenAction {
* 从指定元素校验注解
* @param target see note
*/
protected void validateAnnotation(AnnotatedElement target) {
public void validateAnnotation(AnnotatedElement target) {
// 校验 @SaCheckLogin 注解
if(target.isAnnotationPresent(SaCheckLogin.class)) {
SaCheckLogin at = target.getAnnotation(SaCheckLogin.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.me.getAnnotation.apply(target, SaCheckLogin.class);
if(checkLogin != null) {
SaManager.getStpLogic(checkLogin.type()).checkByAnnotation(checkLogin);
}
// 校验 @SaCheckRole 注解
if(target.isAnnotationPresent(SaCheckRole.class)) {
SaCheckRole at = target.getAnnotation(SaCheckRole.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
SaCheckRole checkRole = (SaCheckRole) SaStrategy.me.getAnnotation.apply(target, SaCheckRole.class);
if(checkRole != null) {
SaManager.getStpLogic(checkRole.type()).checkByAnnotation(checkRole);
}
// 校验 @SaCheckPermission 注解
if(target.isAnnotationPresent(SaCheckPermission.class)) {
SaCheckPermission at = target.getAnnotation(SaCheckPermission.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
SaCheckPermission checkPermission = (SaCheckPermission) SaStrategy.me.getAnnotation.apply(target, SaCheckPermission.class);
if(checkPermission != null) {
SaManager.getStpLogic(checkPermission.type()).checkByAnnotation(checkPermission);
}
// 校验 @SaCheckSafe 注解
if(target.isAnnotationPresent(SaCheckSafe.class)) {
SaCheckSafe at = target.getAnnotation(SaCheckSafe.class);
SaManager.getStpLogic(null).checkByAnnotation(at);
SaCheckSafe checkSafe = (SaCheckSafe) SaStrategy.me.getAnnotation.apply(target, SaCheckSafe.class);
if(checkSafe != null) {
SaManager.getStpLogic(checkSafe.type()).checkByAnnotation(checkSafe);
}
// 校验 @SaCheckBasic 注解
if(target.isAnnotationPresent(SaCheckBasic.class)) {
SaCheckBasic at = target.getAnnotation(SaCheckBasic.class);
SaBasicUtil.check(at.realm(), at.account());
SaCheckBasic checkBasic = (SaCheckBasic) SaStrategy.me.getAnnotation.apply(target, SaCheckBasic.class);
if(checkBasic != null) {
SaBasicUtil.check(checkBasic.realm(), checkBasic.account());
}
}
}
@@ -24,7 +24,7 @@ public @interface SaCheckBasic {
String realm() default SaBasicTemplate.DEFAULT_REALM;
/**
* 需要校验的账号密码
* 需要校验的账号密码,格式形如 sa:123456
* @return see note
*/
String account() default "";
@@ -33,4 +33,21 @@ public @interface SaCheckPermission {
*/
String type() default "";
/**
* 在权限认证不通过时的次要选择,两者只要其一认证成功即可通过校验
*
* <p>
* 例1@SaCheckPermission(value="user-add", orRole="admin")
* 代表本次请求只要具有 user-add权限 或 admin角色 其一即可通过校验
* </p>
*
* <p>
* 例2 orRole = {"admin", "manager", "staff"},具有三个角色其一即可 <br>
* 例3 orRole = {"admin, manager, staff"},必须三个角色同时具备
* </p>
*
* @return /
*/
String[] orRole() default {};
}
@@ -15,4 +15,10 @@ import java.lang.annotation.Target;
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface SaCheckSafe {
/**
* 多账号体系下所属的账号体系标识
* @return see note
*/
String type() default "";
}
@@ -0,0 +1,122 @@
package cn.dev33.satoken.config;
/**
* Sa-Token Cookie写入 相关配置
* @author kong
*
*/
public class SaCookieConfig {
/**
* 域(写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景)
*/
private String domain;
/**
* 路径
*/
private String path;
/**
* 是否只在 https 协议下有效
*/
private Boolean secure = false;
/**
* 是否禁止 js 操作 Cookie
*/
private Boolean httpOnly = false;
/**
* 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
*/
private String sameSite;
/**
* @return 域 (写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景)
*/
public String getDomain() {
return domain;
}
/**
* @param domain 域 (写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景)
* @return 对象自身
*/
public SaCookieConfig setDomain(String domain) {
this.domain = domain;
return this;
}
/**
* @return 路径
*/
public String getPath() {
return path;
}
/**
* @param path 路径
* @return 对象自身
*/
public SaCookieConfig setPath(String path) {
this.path = path;
return this;
}
/**
* @return 是否只在 https 协议下有效
*/
public Boolean getSecure() {
return secure;
}
/**
* @param secure 是否只在 https 协议下有效
* @return 对象自身
*/
public SaCookieConfig setSecure(Boolean secure) {
this.secure = secure;
return this;
}
/**
* @return 是否禁止 js 操作 Cookie
*/
public Boolean getHttpOnly() {
return httpOnly;
}
/**
* @param httpOnly 是否禁止 js 操作 Cookie
* @return 对象自身
*/
public SaCookieConfig setHttpOnly(Boolean httpOnly) {
this.httpOnly = httpOnly;
return this;
}
/**
* @return 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
*/
public String getSameSite() {
return sameSite;
}
/**
* @param sameSite 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
* @return 对象自身
*/
public SaCookieConfig setSameSite(String sameSite) {
this.sameSite = sameSite;
return this;
}
// toString
@Override
public String toString() {
return "SaCookieConfig [domain=" + domain + ", path=" + path + ", secure=" + secure + ", httpOnly=" + httpOnly
+ ", sameSite=" + sameSite + "]";
}
}
@@ -10,7 +10,7 @@ import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token-SSO 单点登录模块 配置Model
* Sa-Token SSO 单点登录模块 配置Model
* @author kong
*
*/
@@ -18,8 +18,11 @@ public class SaSsoConfig implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
// ----------------- Server端相关配置
/**
* Ticket有效期 (单位: 秒)
* Ticket有效期 (单位: 秒)
*/
public long ticketTimeout = 60 * 5;
@@ -27,48 +30,77 @@ public class SaSsoConfig implements Serializable {
* 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
*/
public String allowUrl = "*";
/**
* 是否打开单点注销功能
*/
public Boolean isSlo = true;
/**
* 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
* 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
*/
public Boolean isHttp = false;
/**
* 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
*/
public String secretkey;
// ----------------- Client端相关配置
/**
* SSO-Server端 单点登录地址
* 配置 Server 端单点登录授权地址
*/
public String authUrl;
/**
* SSO-Server端 Ticket校验地址
* 是否打开单点注销功能
*/
// public Boolean isSlo = true; // 同上
/**
* 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
*/
// public Boolean isHttp = false; // 同上
/**
* 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
*/
// public String secretkey; // 同上
/**
* 配置 Server 端的 ticket 校验地址
*/
public String checkTicketUrl;
/**
* SSO-Server端 单点注销地址
* 配置 Server 端查询 userinfo 地址
*/
public String userinfoUrl;
/**
* 配置 Server 端单点注销地址
*/
public String sloUrl;
/**
* SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取)
* 配置当前 Client 端的单点注销回调URL (为空时自动获取)
*/
public String ssoLogoutCall;
/**
* SSO-Server端 账号资料查询地址
*/
public String userinfoUrl;
/**
* @return Ticket有效期 (单位: 秒)
* @return Ticket有效期 (单位: 秒)
*/
public long getTicketTimeout() {
return ticketTimeout;
}
/**
* @param ticketTimeout Ticket有效期 (单位: 秒)
* @param ticketTimeout Ticket有效期 (单位: 秒)
* @return 对象自身
*/
public SaSsoConfig setTicketTimeout(long ticketTimeout) {
@@ -93,14 +125,46 @@ public class SaSsoConfig implements Serializable {
}
/**
* @return 调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
* @return 是否打开单点注销功能
*/
public Boolean getIsSlo() {
return isSlo;
}
/**
* @param isSlo 是否打开单点注销功能
* @return 对象自身
*/
public SaSsoConfig setIsSlo(Boolean isSlo) {
this.isSlo = isSlo;
return this;
}
/**
* @return isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
*/
public Boolean getIsHttp() {
return isHttp;
}
/**
* @param isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
* @return 对象自身
*/
public SaSsoConfig setIsHttp(Boolean isHttp) {
this.isHttp = isHttp;
return this;
}
/**
* @return 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
*/
public String getSecretkey() {
return secretkey;
}
/**
* @param secretkey 调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
* @param secretkey 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
* @return 对象自身
*/
public SaSsoConfig setSecretkey(String secretkey) {
@@ -109,14 +173,14 @@ public class SaSsoConfig implements Serializable {
}
/**
* @return SSO-Server端 单点登录地址
* @return 配置的 Server 端单点登录授权地址
*/
public String getAuthUrl() {
return authUrl;
}
/**
* @param authUrl SSO-Server端 单点登录地址
* @param authUrl 配置 Server 端单点登录授权地址
* @return 对象自身
*/
public SaSsoConfig setAuthUrl(String authUrl) {
@@ -125,14 +189,14 @@ public class SaSsoConfig implements Serializable {
}
/**
* @return SSO-Server端Ticket校验地址
* @return 配置的 Server 端的 ticket 校验地址
*/
public String getCheckTicketUrl() {
return checkTicketUrl;
}
/**
* @param checkTicketUrl SSO-Server端Ticket校验地址
* @param checkTicketUrl 配置 Server 端的 ticket 校验地址
* @return 对象自身
*/
public SaSsoConfig setCheckTicketUrl(String checkTicketUrl) {
@@ -141,14 +205,30 @@ public class SaSsoConfig implements Serializable {
}
/**
* @return SSO-Server端单点注销地址
* @return 配置的 Server 端查询 userinfo 地址
*/
public String getUserinfoUrl() {
return userinfoUrl;
}
/**
* @param userinfoUrl 配置 Server 端查询 userinfo 地址
* @return 对象自身
*/
public SaSsoConfig setUserinfoUrl(String userinfoUrl) {
this.userinfoUrl = userinfoUrl;
return this;
}
/**
* @return 配置 Server 端单点注销地址
*/
public String getSloUrl() {
return sloUrl;
}
/**
* @param sloUrl SSO-Server端单点注销地址
* @param sloUrl 配置 Server 端单点注销地址
* @return 对象自身
*/
public SaSsoConfig setSloUrl(String sloUrl) {
@@ -157,14 +237,14 @@ public class SaSsoConfig implements Serializable {
}
/**
* @return SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取)
* @return 配置当前 Client 端的单点注销回调URL (为空时自动获取)
*/
public String getSsoLogoutCall() {
return ssoLogoutCall;
}
/**
* @param ssoLogoutCall SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取)
* @param ssoLogoutCall 配置当前 Client 端的单点注销回调URL (为空时自动获取)
* @return 对象自身
*/
public SaSsoConfig setSsoLogoutCall(String ssoLogoutCall) {
@@ -172,30 +252,14 @@ public class SaSsoConfig implements Serializable {
return this;
}
/**
* @return SSO-Server端 账号资料查询地址
*/
public String getUserinfoUrl() {
return userinfoUrl;
}
/**
* @param userinfoUrl SSO-Server端 账号资料查询地址
* @return 对象自身
*/
public SaSsoConfig setUserinfoUrl(String userinfoUrl) {
this.userinfoUrl = userinfoUrl;
return this;
}
@Override
public String toString() {
return "SaSsoConfig [ticketTimeout=" + ticketTimeout + ", allowUrl=" + allowUrl + ", secretkey=" + secretkey
+ ", authUrl=" + authUrl + ", checkTicketUrl=" + checkTicketUrl + ", sloUrl=" + sloUrl
+ ", ssoLogoutCall=" + ssoLogoutCall + ", userinfoUrl=" + userinfoUrl + ", isHttp=" + isHttp + ", isSlo=" + isSlo + "]";
return "SaSsoConfig [ticketTimeout=" + ticketTimeout + ", allowUrl=" + allowUrl + ", isSlo=" + isSlo
+ ", isHttp=" + isHttp + ", secretkey=" + secretkey + ", authUrl=" + authUrl + ", checkTicketUrl="
+ checkTicketUrl + ", userinfoUrl=" + userinfoUrl + ", sloUrl=" + sloUrl + ", ssoLogoutCall="
+ ssoLogoutCall + "]";
}
/**
* 以数组形式写入允许的授权回调地址
* @param url 所有集合
@@ -206,50 +270,6 @@ public class SaSsoConfig implements Serializable {
return this;
}
// -------------------- SaSsoHandle 相关配置 --------------------
/**
* 是否使用http请求校验ticket值
*/
public Boolean isHttp = false;
/**
* 是否打开单点注销
*/
public Boolean isSlo = false;
/**
* @return isHttp 是否使用http请求校验ticket值
*/
public Boolean getIsHttp() {
return isHttp;
}
/**
* @param isHttp 是否使用http请求校验ticket值
* @return 对象自身
*/
public SaSsoConfig setIsHttp(Boolean isHttp) {
this.isHttp = isHttp;
return this;
}
/**
* @return 是否打开单点注销
*/
public Boolean getIsSlo() {
return isSlo;
}
/**
* @param isSlo 是否打开单点注销
* @return 对象自身
*/
public SaSsoConfig setIsSlo(Boolean isSlo) {
this.isSlo = isSlo;
return this;
}
// -------------------- SaSsoHandle 所有回调函数 --------------------
@@ -312,4 +332,7 @@ public class SaSsoConfig implements Serializable {
return this;
}
}
@@ -53,9 +53,6 @@ public class SaTokenConfig implements Serializable {
/** 是否打开自动续签 (如果此值为true, 框架会在每次直接或间接调用getLoginId()时进行一次过期检查与续签操作) */
private Boolean autoRenew = true;
/** 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景 */
private String cookieDomain;
/** token前缀, 格式样例(satoken: Bearer xxxx-xxxx-xxxx-xxxx) */
private String tokenPrefix;
@@ -66,7 +63,7 @@ public class SaTokenConfig implements Serializable {
private Boolean isLog = false;
/**
* jwt秘钥 (只有集成 sa-token-temp-jwt 模块时此参数才会生效)
* jwt秘钥 (只有集成 jwt 模块时此参数才会生效)
*/
private String jwtSecretKey;
@@ -83,7 +80,14 @@ public class SaTokenConfig implements Serializable {
/** 配置当前项目的网络访问地址 */
private String currDomain;
/** 是否校验Id-Token(部分rpc插件有效) */
private Boolean checkIdToken = false;
/**
* Cookie配置对象
*/
public SaCookieConfig cookie = new SaCookieConfig();
/**
* SSO单点登录配置对象
*/
@@ -286,22 +290,6 @@ public class SaTokenConfig implements Serializable {
return this;
}
/**
* @return 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景
*/
public String getCookieDomain() {
return cookieDomain;
}
/**
* @param cookieDomain 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景
* @return 对象自身
*/
public SaTokenConfig setCookieDomain(String cookieDomain) {
this.cookieDomain = cookieDomain;
return this;
}
/**
* @return token前缀, 格式样例(satoken: Bearer xxxx-xxxx-xxxx-xxxx)
*/
@@ -351,14 +339,14 @@ public class SaTokenConfig implements Serializable {
}
/**
* @return jwt秘钥 (只有集成 sa-token-temp-jwt 模块时此参数才会生效)
* @return jwt秘钥 (只有集成 jwt 模块时此参数才会生效)
*/
public String getJwtSecretKey() {
return jwtSecretKey;
}
/**
* @param jwtSecretKey jwt秘钥 (只有集成 sa-token-temp-jwt 模块时此参数才会生效)
* @param jwtSecretKey jwt秘钥 (只有集成 jwt 模块时此参数才会生效)
* @return 对象自身
*/
public SaTokenConfig setJwtSecretKey(String jwtSecretKey) {
@@ -413,6 +401,22 @@ public class SaTokenConfig implements Serializable {
this.currDomain = currDomain;
return this;
}
/**
* @return 是否校验Id-Token(部分rpc插件有效)
*/
public Boolean getCheckIdToken() {
return checkIdToken;
}
/**
* @param checkIdToken 是否校验Id-Token(部分rpc插件有效)
* @return 对象自身
*/
public SaTokenConfig setCheckIdToken(Boolean checkIdToken) {
this.checkIdToken = checkIdToken;
return this;
}
/**
* @return SSO单点登录配置对象
@@ -423,23 +427,58 @@ public class SaTokenConfig implements Serializable {
/**
* @param sso SSO单点登录配置对象
* @return 对象自身
*/
public void setSso(SaSsoConfig sso) {
public SaTokenConfig setSso(SaSsoConfig sso) {
this.sso = sso;
return this;
}
/**
* @return Cookie 全局配置对象
*/
public SaCookieConfig getCookie() {
return cookie;
}
/**
* @param cookie Cookie 全局配置对象
* @return 对象自身
*/
public SaTokenConfig setCookie(SaCookieConfig cookie) {
this.cookie = cookie;
return this;
}
@Override
public String toString() {
return "SaTokenConfig [tokenName=" + tokenName + ", timeout=" + timeout + ", activityTimeout=" + activityTimeout
+ ", isConcurrent=" + isConcurrent + ", isShare=" + isShare + ", isReadBody=" + isReadBody
+ ", isReadHead=" + isReadHead + ", isReadCookie=" + isReadCookie + ", tokenStyle=" + tokenStyle
+ ", dataRefreshPeriod=" + dataRefreshPeriod + ", tokenSessionCheckLogin=" + tokenSessionCheckLogin
+ ", autoRenew=" + autoRenew + ", cookieDomain=" + cookieDomain + ", tokenPrefix=" + tokenPrefix
+ ", isPrint=" + isPrint + ", isLog=" + isLog + ", jwtSecretKey=" + jwtSecretKey + ", idTokenTimeout="
+ idTokenTimeout + ", basic=" + basic + ", currDomain=" + currDomain + ", sso=" + sso + "]";
return "SaTokenConfig ["
+ "tokenName=" + tokenName
+ ", timeout=" + timeout
+ ", activityTimeout=" + activityTimeout
+ ", isConcurrent=" + isConcurrent
+ ", isShare=" + isShare
+ ", isReadBody=" + isReadBody
+ ", isReadHead=" + isReadHead
+ ", isReadCookie=" + isReadCookie
+ ", tokenStyle=" + tokenStyle
+ ", dataRefreshPeriod=" + dataRefreshPeriod
+ ", tokenSessionCheckLogin=" + tokenSessionCheckLogin
+ ", autoRenew=" + autoRenew
+ ", tokenPrefix=" + tokenPrefix
+ ", isPrint=" + isPrint
+ ", isLog=" + isLog
+ ", jwtSecretKey=" + jwtSecretKey
+ ", idTokenTimeout=" + idTokenTimeout
+ ", basic=" + basic
+ ", currDomain=" + currDomain
+ ", checkIdToken=" + checkIdToken
+ ", sso=" + sso
+ ", cookie=" + cookie
+ "]";
}
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 setIsConcurrent() ,使用方式保持不变 </h1>
* @param allowConcurrentLogin see note
@@ -456,10 +495,31 @@ public class SaTokenConfig implements Serializable {
* @param isV see note
* @return see note
*/
@Deprecated
public SaTokenConfig setIsV(Boolean isV) {
this.isPrint = isV;
return this;
}
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 getCookie().getDomain() ,使用方式保持不变 </h1>
* @return 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景
*/
@Deprecated
public String getCookieDomain() {
return getCookie().getDomain();
}
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 getCookie().setDomain() ,使用方式保持不变 </h1>
* @param cookieDomain 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景
* @return 对象自身
*/
@Deprecated
public SaTokenConfig setCookieDomain(String cookieDomain) {
this.getCookie().setDomain(cookieDomain);
return this;
}
}
@@ -10,7 +10,7 @@ import java.util.Properties;
/**
* Sa-Token配置文件的构建工厂类
* <p>
* 只有在非IOC环境下才会用到此类
* 用于手动读取配置文件初始化 SaTokenConfig 对象,只有在非IOC环境下才会用到此类
*
* @author kong
*
@@ -11,6 +11,15 @@ import cn.dev33.satoken.context.model.SaStorage;
*
*/
public class SaHolder {
/**
* 获取当前请求的 SaTokenContext
*
* @return see note
*/
public static SaTokenContext getContext() {
return SaManager.getSaTokenContextOrSecond();
}
/**
* 获取当前请求的 [Request] 对象
@@ -18,7 +27,7 @@ public class SaHolder {
* @return see note
*/
public static SaRequest getRequest() {
return SaManager.getSaTokenContext().getRequest();
return SaManager.getSaTokenContextOrSecond().getRequest();
}
/**
@@ -27,7 +36,7 @@ public class SaHolder {
* @return see note
*/
public static SaResponse getResponse() {
return SaManager.getSaTokenContext().getResponse();
return SaManager.getSaTokenContextOrSecond().getResponse();
}
/**
@@ -36,7 +45,7 @@ public class SaHolder {
* @return see note
*/
public static SaStorage getStorage() {
return SaManager.getSaTokenContext().getStorage();
return SaManager.getSaTokenContextOrSecond().getStorage();
}
}
@@ -41,4 +41,12 @@ public interface SaTokenContext {
*/
public boolean matchPath(String pattern, String path);
/**
* 此上下文是否有效
* @return /
*/
public default boolean isValid() {
return false;
}
}
@@ -6,11 +6,22 @@ import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.exception.SaTokenException;
/**
* Sa-Token 上下文处理器 [默认实现类]
* Sa-Token 上下文处理器 [默认实现类]
*
* <p>
* 一般情况下框架会为你自动注入合适的上下文处理器,如果代码断点走到了此默认实现类,
* 说明你引入的依赖有问题或者错误的调用了Sa-Token的API, 请在[在线开发文档 → 附录 → 常见问题排查] 中按照提示进行排查
* </p>
*
* @author kong
*
*/
public class SaTokenContextDefaultImpl implements SaTokenContext {
/**
* 默认的上下文处理器对象
*/
public static SaTokenContext defaultContext = new SaTokenContextDefaultImpl();
/**
* 默认的错误提示语
@@ -50,5 +61,4 @@ public class SaTokenContextDefaultImpl implements SaTokenContext {
}
}
@@ -6,6 +6,12 @@ import cn.dev33.satoken.context.model.SaStorage;
/**
* Sa-Token 上下文处理器 [ThreadLocal版本]
*
* <p>
* 使用 [ThreadLocal版本] 上下文处理器需要在全局过滤器或者拦截器内率先调用
* SaTokenContextForThreadLocalStorage.setBox(req,res, sto) 初始化上下文
* </p>
*
* @author kong
*
*/
@@ -31,4 +37,9 @@ public class SaTokenContextForThreadLocal implements SaTokenContext {
return false;
}
@Override
public boolean isValid() {
return SaTokenContextForThreadLocalStorage.getBox() != null;
}
}
@@ -7,6 +7,7 @@ import cn.dev33.satoken.exception.SaTokenException;
/**
* Sa-Token 上下文处理器 [ThreadLocal版本] ---- 对象存储器
*
* @author kong
*
*/
@@ -0,0 +1,276 @@
package cn.dev33.satoken.context.model;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* Cookie Model
* @author kong
*
*/
public class SaCookie {
/**
* 写入响应头时使用的key
*/
public static final String HEADER_NAME = "Set-Cookie";
/**
* 名称
*/
private String name;
/**
* 值
*/
private String value;
/**
* 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
*/
private int maxAge = -1;
/**
* 域
*/
private String domain;
/**
* 路径
*/
private String path;
/**
* 是否只在 https 协议下有效
*/
private Boolean secure = false;
/**
* 是否禁止 js 操作 Cookie
*/
private Boolean httpOnly = false;
/**
* 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
*/
private String sameSite;
/**
* 构造一个
*/
public SaCookie() {
}
/**
* 构造一个
* @param name 名字
* @param value 值
*/
public SaCookie(String name, String value) {
this.name = name;
this.value = value;
}
/**
* @return 名称
*/
public String getName() {
return name;
}
/**
* @param name 名称
* @return 对象自身
*/
public SaCookie setName(String name) {
this.name = name;
return this;
}
/**
* @return 值
*/
public String getValue() {
return value;
}
/**
* @param value 值
* @return 对象自身
*/
public SaCookie setValue(String value) {
this.value = value;
return this;
}
/**
* @return 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
*/
public int getMaxAge() {
return maxAge;
}
/**
* @param maxAge 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
* @return 对象自身
*/
public SaCookie setMaxAge(int maxAge) {
this.maxAge = maxAge;
return this;
}
/**
* @return 域
*/
public String getDomain() {
return domain;
}
/**
* @param domain 域
* @return 对象自身
*/
public SaCookie setDomain(String domain) {
this.domain = domain;
return this;
}
/**
* @return 路径
*/
public String getPath() {
return path;
}
/**
* @param path 路径
* @return 对象自身
*/
public SaCookie setPath(String path) {
this.path = path;
return this;
}
/**
* @return 是否只在 https 协议下有效
*/
public Boolean getSecure() {
return secure;
}
/**
* @param secure 是否只在 https 协议下有效
* @return 对象自身
*/
public SaCookie setSecure(Boolean secure) {
this.secure = secure;
return this;
}
/**
* @return 是否禁止 js 操作 Cookie
*/
public Boolean getHttpOnly() {
return httpOnly;
}
/**
* @param httpOnly 是否禁止 js 操作 Cookie
* @return 对象自身
*/
public SaCookie setHttpOnly(Boolean httpOnly) {
this.httpOnly = httpOnly;
return this;
}
/**
* @return 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
*/
public String getSameSite() {
return sameSite;
}
/**
* @param sameSite 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
* @return 对象自身
*/
public SaCookie setSameSite(String sameSite) {
this.sameSite = sameSite;
return this;
}
// toString
@Override
public String toString() {
return "SaCookie [name=" + name + ", value=" + value + ", maxAge=" + maxAge + ", domain=" + domain + ", path=" + path
+ ", secure=" + secure + ", httpOnly=" + httpOnly + ", sameSite="
+ sameSite + "]";
}
/**
* 构建一下
*/
public void builde() {
if(path == null) {
path = "/";
}
}
/**
* 转换为响应头 Set-Cookie 参数需要的值
* @return /
*/
public String toHeaderValue() {
this.builde();
if(SaFoxUtil.isEmpty(name)) {
throw new SaTokenException("name不能为空");
}
if(value != null && value.indexOf(";") > -1) {
throw new SaTokenException("无效Value" + value);
}
// Set-Cookie: name=value; Max-Age=100000; Expires=Tue, 05-Oct-2021 20:28:17 GMT; Domain=localhost; Path=/; Secure; HttpOnly; SameSite=Lax
StringBuffer sb = new StringBuffer();
sb.append(name + "=" + value);
if(maxAge >= 0) {
sb.append("; Max-Age=" + maxAge);
String expires;
if(maxAge == 0) {
expires = Instant.EPOCH.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME);
} else {
expires = OffsetDateTime.now().plusSeconds(maxAge).format(DateTimeFormatter.RFC_1123_DATE_TIME);
}
sb.append("; Expires=" + expires);
}
if(SaFoxUtil.isEmpty(domain) == false) {
sb.append("; Domain=" + domain);
}
if(SaFoxUtil.isEmpty(path) == false) {
sb.append("; Path=" + path);
}
if(secure) {
sb.append("; Secure");
}
if(httpOnly) {
sb.append("; HttpOnly");
}
if(SaFoxUtil.isEmpty(sameSite) == false) {
sb.append("; sameSite=" + sameSite);
}
return sb.toString();
}
}
@@ -17,22 +17,34 @@ public interface SaResponse {
* 删除指定Cookie
* @param name Cookie名称
*/
public void deleteCookie(String name);
public default void deleteCookie(String name) {
addCookie(name, null, null, null, 0);
}
/**
* 写入指定Cookie
* 写入指定Cookie
* @param name Cookie名称
* @param value Cookie值
* @param path Cookie路径
* @param domain Cookie的作用域
* @param timeout 过期时间 (秒)
*/
public void addCookie(String name, String value, String path, String domain, int timeout);
public default void addCookie(String name, String value, String path, String domain, int timeout) {
this.addCookie(new SaCookie(name, value).setPath(path).setDomain(domain).setMaxAge(timeout));
}
/**
* 设置响应状态码
* 写入指定Cookie
* @param cookie Cookie-Model
*/
public default void addCookie(SaCookie cookie) {
this.addHeader(SaCookie.HEADER_NAME, cookie.toHeaderValue());
}
/**
* 设置响应状态码
* @param sc 响应状态码
* @return 对象自身
* @return 对象自身
*/
public SaResponse setStatus(int sc);
@@ -43,6 +55,14 @@ public interface SaResponse {
* @return 对象自身
*/
public SaResponse setHeader(String name, String value);
/**
* 在响应头里添加一个值
* @param name 名字
* @param value 值
* @return 对象自身
*/
public SaResponse addHeader(String name, String value);
/**
* 在响应头写入 [Server] 服务器名称
@@ -0,0 +1,15 @@
package cn.dev33.satoken.context.second;
import cn.dev33.satoken.context.SaTokenContext;
/**
* Sa-Token 二级Context - 基础接口
*
* <p> (利用继承机制实现区别 [一级Context] 与 [二级Context] 的目的)
*
* @author kong
*
*/
public interface SaTokenSecondContext extends SaTokenContext {
}
@@ -0,0 +1,18 @@
package cn.dev33.satoken.context.second;
/**
* Sa-Token 二级Context - 创建器
*
* @author kong
*
*/
@FunctionalInterface
public interface SaTokenSecondContextCreator {
/**
* 创建一个二级 Context
* @return /
*/
public SaTokenSecondContext create();
}
@@ -99,7 +99,7 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
if(getKeyTimeout(key) == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
// 无动作
dataMap.put(key, object);
}
@Override
@@ -176,7 +176,7 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
/**
* 是否继续执行数据清理的线程标记
*/
public boolean refreshFlag;
public volatile boolean refreshFlag;
/**
@@ -224,7 +224,7 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
}
}
});
refreshThread.start();
this.refreshThread.start();
}
/**
@@ -0,0 +1,31 @@
package cn.dev33.satoken.exception;
/**
* 一个异常:代表 API 已被禁用
* @author kong
*/
public class ApiDisabledException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130133L;
/** 异常提示语 */
public static final String BE_MESSAGE = "This API is disabled";
/**
* 一个异常:代表 API 已被禁用
*/
public ApiDisabledException() {
super(BE_MESSAGE);
}
/**
* 一个异常:代表 API 已被禁用
* @param message 异常描述
*/
public ApiDisabledException(String message) {
super(message);
}
}
@@ -3,6 +3,8 @@ package cn.dev33.satoken.exception;
import java.util.Arrays;
import java.util.List;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* 一个异常:代表会话未能通过登录认证
* @author kong
@@ -24,23 +26,23 @@ public class NotLoginException extends SaTokenException {
/** 表示未提供token */
public static final String NOT_TOKEN = "-1";
public static final String NOT_TOKEN_MESSAGE = "未提供token";
public static final String NOT_TOKEN_MESSAGE = "未提供Token";
/** 表示token无效 */
public static final String INVALID_TOKEN = "-2";
public static final String INVALID_TOKEN_MESSAGE = "token无效";
public static final String INVALID_TOKEN_MESSAGE = "Token无效";
/** 表示token已过期 */
public static final String TOKEN_TIMEOUT = "-3";
public static final String TOKEN_TIMEOUT_MESSAGE = "token已过期";
public static final String TOKEN_TIMEOUT_MESSAGE = "Token已过期";
/** 表示token已被顶下线 */
public static final String BE_REPLACED = "-4";
public static final String BE_REPLACED_MESSAGE = "token已被顶下线";
public static final String BE_REPLACED_MESSAGE = "Token已被顶下线";
/** 表示token已被踢下线 */
public static final String KICK_OUT = "-5";
public static final String KICK_OUT_MESSAGE = "token已被踢下线";
public static final String KICK_OUT_MESSAGE = "Token已被踢下线";
/** 默认的提示语 */
public static final String DEFAULT_MESSAGE = "当前会话未登录";
@@ -99,6 +101,17 @@ public class NotLoginException extends SaTokenException {
* @return 构建完毕的异常对象
*/
public static NotLoginException newInstance(String loginType, String type) {
return newInstance(loginType, type, null);
}
/**
* 静态方法构建一个NotLoginException
* @param loginType 账号类型
* @param type 账号类型
* @param token 引起异常的Token值
* @return 构建完毕的异常对象
*/
public static NotLoginException newInstance(String loginType, String type, String token) {
String message = null;
if(NOT_TOKEN.equals(type)) {
message = NOT_TOKEN_MESSAGE;
@@ -118,6 +131,9 @@ public class NotLoginException extends SaTokenException {
else {
message = DEFAULT_MESSAGE;
}
if(SaFoxUtil.isEmpty(token) == false) {
message = message + "" + token;
}
return new NotLoginException(message, loginType, type);
}
@@ -1,5 +1,7 @@
package cn.dev33.satoken.exception;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* Sa-Token框架内部逻辑发生错误抛出的异常
* (自定义此异常方便开发者在做全局异常处理时分辨异常类型)
@@ -42,4 +44,26 @@ public class SaTokenException extends RuntimeException {
super(message, cause);
}
/**
* 如果flag==true,则抛出message异常
* @param flag 标记
* @param message 异常信息
*/
public static void throwBy(boolean flag, String message) {
if(flag) {
throw new SaTokenException(message);
}
}
/**
* 如果value==null或者isEmpty,则抛出message异常
* @param value 值
* @param message 异常信息
*/
public static void throwByNull(Object value, String message) {
if(SaFoxUtil.isEmpty(value)) {
throw new SaTokenException(message);
}
}
}
@@ -1,7 +1,7 @@
package cn.dev33.satoken.exception;
/**
* 一个异常:代表停止路由匹配
* 一个异常:代表停止路由匹配,进入Controller
*
* @author kong
*/
@@ -0,0 +1,17 @@
package cn.dev33.satoken.fun;
/**
* 设定一个函数,并传入一个参数,方便在Lambda表达式下的函数式编程
* @author kong
*
*/
@FunctionalInterface
public interface SaParamFunction<T> {
/**
* 执行的方法
* @param r 传入的参数
*/
public void run(T r);
}
@@ -0,0 +1,18 @@
package cn.dev33.satoken.fun;
/**
* 设定一个函数,传入一个参数,并返回一个值,方便在Lambda表达式下的函数式编程
* @author kong
*
*/
@FunctionalInterface
public interface SaParamRetFunction<T, R> {
/**
* 执行的方法
* @param param 传入的参数
* @return 返回值
*/
public R run(T param);
}
@@ -147,14 +147,14 @@ public class SaIdTemplate {
* @return Token
*/
public String createToken() {
return SaFoxUtil.getRandomString(60);
return SaFoxUtil.getRandomString(64);
}
// -------------------- 拼接key
/**
* 拼接keyId-Token的存储key
* 拼接keyId-Token 的存储 key
* @return key
*/
public String splicingTokenSaveKey() {
@@ -162,7 +162,7 @@ public class SaIdTemplate {
}
/**
* 拼接keyId-Token的存储key
* 拼接key次级 Id-Token 的存储 key
* @return key
*/
public String splicingPastTokenSaveKey() {
@@ -27,22 +27,20 @@ public interface SaTokenListener {
public void doLogout(String loginType, Object loginId, String tokenValue);
/**
* 每次被踢下线时触发
* @param loginType 账号类别
* @param loginId 账号id
* @param tokenValue token值
* @param device 设备标识
* 每次被踢下线时触发
* @param loginType 账号类别
* @param loginId 账号id
* @param tokenValue token值
*/
public void doLogoutByLoginId(String loginType, Object loginId, String tokenValue, String device);
public void doKickout(String loginType, Object loginId, String tokenValue);
/**
* 每次被顶下线时触发
* @param loginType 账号类别
* @param loginId 账号id
* @param tokenValue token值
* @param device 设备标识
*/
public void doReplaced(String loginType, Object loginId, String tokenValue, String device);
public void doReplaced(String loginType, Object loginId, String tokenValue);
/**
* 每次被封禁时触发
@@ -26,23 +26,23 @@ public class SaTokenListenerDefaultImpl implements SaTokenListener {
*/
@Override
public void doLogout(String loginType, Object loginId, String tokenValue) {
println("账号[" + loginId + "]注销成功");
println("账号[" + loginId + "]注销成功 (Token=" + tokenValue + ")");
}
/**
* 每次被踢下线时触发
*/
@Override
public void doLogoutByLoginId(String loginType, Object loginId, String tokenValue, String device) {
println("账号[" + loginId + "]被踢下线 (终端: " + device + ")");
public void doKickout(String loginType, Object loginId, String tokenValue) {
println("账号[" + loginId + "]被踢下线 (Token=" + tokenValue + ")");
}
/**
* 每次被顶下线时触发
*/
@Override
public void doReplaced(String loginType, Object loginId, String tokenValue, String device) {
println("账号[" + loginId + "]被顶下线 (终端: " + device + ")");
public void doReplaced(String loginType, Object loginId, String tokenValue) {
println("账号[" + loginId + "]被顶下线 (Token=" + tokenValue + ")");
}
/**
@@ -0,0 +1,62 @@
package cn.dev33.satoken.router;
import java.util.HashMap;
import java.util.Map;
import cn.dev33.satoken.exception.SaTokenException;
/**
* Http 请求各种请求类型的枚举表示
*
* <p> 参考:Spring - HttpMethod
*
* @author kong
*
*/
public enum SaHttpMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE, CONNECT,
/**
* 代表全部请求方式
*/
ALL;
private static final Map<String, SaHttpMethod> map = new HashMap<>();
static {
for (SaHttpMethod reqMethod : values()) {
map.put(reqMethod.name(), reqMethod);
}
}
/**
* String 转 enum
* @param method 请求类型
* @return ReqMethod 对象
*/
public static SaHttpMethod toEnum(String method) {
if(method == null) {
throw new SaTokenException("无效Method" + method);
}
SaHttpMethod reqMethod = map.get(method.toUpperCase());
if(reqMethod == null) {
throw new SaTokenException("无效Method" + method);
}
return reqMethod;
}
/**
* String[] 转 enum[]
* @param methods 请求类型数组
* @return ReqMethod 对象
*/
public static SaHttpMethod[] toEnumArray(String... methods) {
SaHttpMethod [] arr = new SaHttpMethod[methods.length];
for (int i = 0; i < methods.length; i++) {
arr[i] = SaHttpMethod.toEnum(methods[i]);
}
return arr;
}
}
@@ -1,14 +1,14 @@
package cn.dev33.satoken.router;
import java.util.Arrays;
import java.util.List;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.BackResultException;
import cn.dev33.satoken.exception.StopMatchException;
import cn.dev33.satoken.fun.IsRunFunction;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.fun.SaParamFunction;
import cn.dev33.satoken.fun.SaParamRetFunction;
/**
* 路由匹配操作工具类
@@ -26,7 +26,7 @@ public class SaRouter {
* @return 是否匹配成功
*/
public static boolean isMatch(String pattern, String path) {
return SaManager.getSaTokenContext().matchPath(pattern, path);
return SaManager.getSaTokenContextOrSecond().matchPath(pattern, path);
}
/**
@@ -36,6 +36,9 @@ public class SaRouter {
* @return 是否匹配成功
*/
public static boolean isMatch(List<String> patterns, String path) {
if(patterns == null) {
return false;
}
for (String pattern : patterns) {
if(isMatch(pattern, path)) {
return true;
@@ -44,6 +47,44 @@ public class SaRouter {
return false;
}
/**
* 路由匹配
* @param patterns 路由匹配符数组
* @param path 被匹配的路由
* @return 是否匹配成功
*/
public static boolean isMatch(String[] patterns, String path) {
if(patterns == null) {
return false;
}
for (String pattern : patterns) {
if(isMatch(pattern, path)) {
return true;
}
}
return false;
}
/**
* Http请求方法匹配
* @param methods Http请求方法断言数组
* @param methodString Http请求方法
* @return 是否匹配成功
*/
public static boolean isMatch(SaHttpMethod[] methods, String methodString) {
if(methods == null) {
return false;
}
for (SaHttpMethod method : methods) {
if(method == SaHttpMethod.ALL || (method != null && method.toString().equalsIgnoreCase(methodString))) {
return true;
}
}
return false;
}
// ------ 使用当前URI匹配
/**
* 路由匹配 (使用当前URI)
* @param pattern 路由匹配符
@@ -61,40 +102,233 @@ public class SaRouter {
public static boolean isMatchCurrURI(List<String> patterns) {
return isMatch(patterns, SaHolder.getRequest().getRequestPath());
}
/**
* 路由匹配 (使用当前URI)
* @param patterns 路由匹配符数组
* @return 是否匹配成功
*/
public static boolean isMatchCurrURI(String[] patterns) {
return isMatch(patterns, SaHolder.getRequest().getRequestPath());
}
/**
* Http请求方法匹配 (使用当前请求方式)
* @param methods Http请求方法断言数组
* @return 是否匹配成功
*/
public static boolean isMatchCurrMethod(SaHttpMethod[] methods) {
return isMatch(methods, SaHolder.getRequest().getMethod());
}
// -------------------- 执行相关 --------------------
// -------------------- 开始匹配 --------------------
/**
* 初始化一个SaRouterStaff,开始匹配
* @return SaRouterStaff
*/
public static SaRouterStaff newMatch() {
return new SaRouterStaff();
}
// ----------------- path匹配
/**
* 路由匹配
* @param patterns 路由匹配符集合
* @return SaRouterStaff
*/
public static SaRouterStaff match(String... patterns) {
return new SaRouterStaff().match(patterns);
}
/**
* 路由匹配排除
* @param patterns 路由匹配符排除数组
* @return SaRouterStaff
*/
public static SaRouterStaff notMatch(String... patterns) {
return new SaRouterStaff().notMatch(patterns);
}
/**
* 路由匹配
* @param patterns 路由匹配符集合
* @return 对象自身
*/
public static SaRouterStaff match(List<String> patterns) {
return new SaRouterStaff().match(patterns);
}
/**
* 路由匹配排除
* @param patterns 路由匹配符排除集合
* @return 对象自身
*/
public static SaRouterStaff notMatch(List<String> patterns) {
return new SaRouterStaff().notMatch(patterns);
}
// ----------------- Method匹配
/**
* Http请求方式匹配 (Enum)
* @param methods Http请求方法断言数组
* @return SaRouterStaff
*/
public static SaRouterStaff match(SaHttpMethod... methods) {
return new SaRouterStaff().match(methods);
}
/**
* Http请求方法匹配排除 (Enum)
* @param methods Http请求方法断言排除数组
* @return SaRouterStaff
*/
public static SaRouterStaff notMatch(SaHttpMethod... methods) {
return new SaRouterStaff().notMatch(methods);
}
/**
* Http请求方法匹配 (String)
* @param methods Http请求方法断言数组
* @return SaRouterStaff
*/
public static SaRouterStaff matchMethod(String... methods) {
return new SaRouterStaff().matchMethod(methods);
}
/**
* Http请求方法匹配排除 (String)
* @param methods Http请求方法断言排除数组
* @return SaRouterStaff
*/
public static SaRouterStaff notMatchMethod(String... methods) {
return new SaRouterStaff().notMatchMethod(methods);
}
// ----------------- 条件匹配
/**
* 根据 boolean 值进行匹配
* @param flag boolean值
* @return SaRouterStaff
*/
public static SaRouterStaff match(boolean flag) {
return new SaRouterStaff().match(flag);
}
/**
* 根据 boolean 值进行匹配排除
* @param flag boolean值
* @return SaRouterStaff
*/
public static SaRouterStaff notMatch(boolean flag) {
return new SaRouterStaff().notMatch(flag);
}
/**
* 根据自定义方法进行匹配 (lazy)
* @param fun 自定义方法
* @return SaRouterStaff
*/
public static SaRouterStaff match(SaParamRetFunction<Object, Boolean> fun) {
return new SaRouterStaff().match(fun);
}
/**
* 根据自定义方法进行匹配排除 (lazy)
* @param fun 自定义排除方法
* @return SaRouterStaff
*/
public static SaRouterStaff notMatch(SaParamRetFunction<Object, Boolean> fun) {
return new SaRouterStaff().notMatch(fun);
}
// -------------------- 直接指定check函数 --------------------
/**
* 路由匹配,如果匹配成功则执行认证函数
* @param pattern 路由匹配符
* @param function 要执行的方法
* @param fun 要执行的校验方法
* @return /
*/
public static void match(String pattern, SaFunction function) {
if(isMatchCurrURI(pattern)) {
function.run();
}
public static SaRouterStaff match(String pattern, SaFunction fun) {
return new SaRouterStaff().match(pattern, fun);
}
/**
* 路由匹配,如果匹配成功则执行认证函数
* @param pattern 路由匹配符
* @param fun 要执行的校验方法
* @return /
*/
public static SaRouterStaff match(String pattern, SaParamFunction<SaRouterStaff> fun) {
return new SaRouterStaff().match(pattern, fun);
}
/**
* 路由匹配 (并指定排除匹配符),如果匹配成功则执行认证函数
* @param pattern 路由匹配符
* @param excludePattern 要排除的路由匹配符
* @param function 要执行的方法
* @param fun 要执行的方法
* @return /
*/
public static void match(String pattern, String excludePattern, SaFunction function) {
if(isMatchCurrURI(pattern)) {
if(isMatchCurrURI(excludePattern) == false) {
function.run();
}
}
public static SaRouterStaff match(String pattern, String excludePattern, SaFunction fun) {
return new SaRouterStaff().match(pattern, excludePattern, fun);
}
/**
* 路由匹配 (并指定排除匹配符),如果匹配成功则执行认证函数
* @param pattern 路由匹配符
* @param excludePattern 要排除的路由匹配符
* @param fun 要执行的方法
* @return /
*/
public static SaRouterStaff match(String pattern, String excludePattern, SaParamFunction<SaRouterStaff> fun) {
return new SaRouterStaff().match(pattern, excludePattern, fun);
}
// -------------------- 提前退出 --------------------
/**
* 停止匹配,跳出函数 (在多个匹配链中一次性跳出Auth函数)
* @return SaRouterStaff
*/
public static SaRouterStaff stop() {
throw new StopMatchException();
}
/**
* 停止匹配,结束执行,向前端返回结果
* @return SaRouterStaff
*/
public static SaRouterStaff back() {
throw new BackResultException("");
}
/**
* 停止匹配,结束执行,向前端返回结果
* @param result 要输出的结果
* @return SaRouterStaff
*/
public static SaRouterStaff back(Object result) {
throw new BackResultException(result);
}
// -------------------- 历史API兼容 --------------------
/**
* <h1>本函数设计已过时,请更换为:SaRouter.match(path...).ckeck(fun) </h1>
* 路由匹配,如果匹配成功则执行认证函数
* @param patterns 路由匹配符集合
* @param function 要执行的方法
*/
@Deprecated
public static void match(List<String> patterns, SaFunction function) {
if(isMatchCurrURI(patterns)) {
function.run();
@@ -102,11 +336,13 @@ public class SaRouter {
}
/**
* <h1>本函数设计已过时,请更换为:SaRouter.match(path...).notMatch(path...).ckeck(fun) </h1>
* 路由匹配 (并指定排除匹配符),如果匹配成功则执行认证函数
* @param patterns 路由匹配符集合
* @param excludePatterns 要排除的路由匹配符集合
* @param function 要执行的方法
*/
@Deprecated
public static void match(List<String> patterns, List<String> excludePatterns, SaFunction function) {
if(isMatchCurrURI(patterns)) {
if(isMatchCurrURI(excludePatterns) == false) {
@@ -114,41 +350,5 @@ public class SaRouter {
}
}
}
/**
* 路由匹配,如果匹配成功则执行认证函数
* @param patterns 路由匹配符集合
* @return 匹配结果包装对象
*/
public static IsRunFunction match(String... patterns) {
boolean matchResult = isMatch(Arrays.asList(patterns), SaHolder.getRequest().getRequestPath());
return new IsRunFunction(matchResult);
}
// -------------------- 其它操作 --------------------
/**
* 停止匹配,跳出函数 (在多个匹配链中一次性跳出Auth函数)
*/
public static void stop() {
throw new StopMatchException();
}
/**
* 停止匹配,结束执行,向前端返回结果
* @param result 要输出的结果
*/
public static void back(Object result) {
throw new BackResultException(result);
}
/**
* 停止匹配,结束执行,向前端返回结果
*/
public static void back() {
throw new BackResultException("");
}
}
@@ -0,0 +1,329 @@
package cn.dev33.satoken.router;
import java.util.List;
import cn.dev33.satoken.exception.BackResultException;
import cn.dev33.satoken.exception.StopMatchException;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.fun.SaParamFunction;
import cn.dev33.satoken.fun.SaParamRetFunction;
/**
* 路由匹配操作对象
*
* @author kong
*
*/
public class SaRouterStaff {
/**
* 是否命中的标记变量
*/
public boolean isHit = true;
/**
* @return 是否命中
*/
public boolean isHit() {
return isHit;
}
/**
* @param isHit 命中标记
* @return 对象自身
*/
public SaRouterStaff setHit(boolean isHit) {
this.isHit = isHit;
return this;
}
/**
* 重置命中标记为 true
* @return 对象自身
*/
public SaRouterStaff reset() {
this.isHit = true;
return this;
}
// ----------------- path匹配
/**
* 路由匹配
* @param patterns 路由匹配符数组
* @return 对象自身
*/
public SaRouterStaff match(String... patterns) {
if(isHit) {
isHit = SaRouter.isMatchCurrURI(patterns);
}
return this;
}
/**
* 路由匹配排除
* @param patterns 路由匹配符排除数组
* @return 对象自身
*/
public SaRouterStaff notMatch(String... patterns) {
if(isHit) {
isHit = !SaRouter.isMatchCurrURI(patterns);
}
return this;
}
/**
* 路由匹配
* @param patterns 路由匹配符集合
* @return 对象自身
*/
public SaRouterStaff match(List<String> patterns) {
if(isHit) {
isHit = SaRouter.isMatchCurrURI(patterns);
}
return this;
}
/**
* 路由匹配排除
* @param patterns 路由匹配符排除集合
* @return 对象自身
*/
public SaRouterStaff notMatch(List<String> patterns) {
if(isHit) {
isHit = !SaRouter.isMatchCurrURI(patterns);
}
return this;
}
// ----------------- Method匹配
/**
* Http请求方法匹配 (Enum)
* @param methods Http请求方法断言数组
* @return 对象自身
*/
public SaRouterStaff match(SaHttpMethod... methods) {
if(isHit) {
isHit = SaRouter.isMatchCurrMethod(methods);
}
return this;
}
/**
* Http请求方法匹配排除 (Enum)
* @param methods Http请求方法断言排除数组
* @return 对象自身
*/
public SaRouterStaff notMatch(SaHttpMethod... methods) {
if(isHit) {
isHit = !SaRouter.isMatchCurrMethod(methods);
}
return this;
}
/**
* Http请求方法匹配 (String)
* @param methods Http请求方法断言数组
* @return 对象自身
*/
public SaRouterStaff matchMethod(String... methods) {
if(isHit) {
SaHttpMethod [] arr = SaHttpMethod.toEnumArray(methods);
isHit = SaRouter.isMatchCurrMethod(arr);
}
return this;
}
/**
* Http请求方法匹配排除 (String)
* @param methods Http请求方法断言排除数组
* @return 对象自身
*/
public SaRouterStaff notMatchMethod(String... methods) {
if(isHit) {
SaHttpMethod [] arr = SaHttpMethod.toEnumArray(methods);
isHit = !SaRouter.isMatchCurrMethod(arr);
}
return this;
}
// ----------------- 条件匹配
/**
* 根据 boolean 值进行匹配
* @param flag boolean值
* @return 对象自身
*/
public SaRouterStaff match(boolean flag) {
if(isHit) {
isHit = flag;
}
return this;
}
/**
* 根据 boolean 值进行匹配排除
* @param flag boolean值
* @return 对象自身
*/
public SaRouterStaff notMatch(boolean flag) {
if(isHit) {
isHit = !flag;
}
return this;
}
/**
* 根据自定义方法进行匹配 (lazy)
* @param fun 自定义方法
* @return 对象自身
*/
public SaRouterStaff match(SaParamRetFunction<Object, Boolean> fun) {
if(isHit) {
isHit = fun.run(this);
}
return this;
}
/**
* 根据自定义方法进行匹配排除 (lazy)
* @param fun 自定义排除方法
* @return 对象自身
*/
public SaRouterStaff notMatch(SaParamRetFunction<Object, Boolean> fun) {
if(isHit) {
isHit = !fun.run(this);
}
return this;
}
// ----------------- 函数校验执行
/**
* 执行校验函数 (无参)
* @param fun 要执行的函数
* @return 对象自身
*/
public SaRouterStaff check(SaFunction fun) {
if(isHit) {
fun.run();
}
return this;
}
/**
* 执行校验函数 (带参)
* @param fun 要执行的函数
* @return 对象自身
*/
public SaRouterStaff check(SaParamFunction<SaRouterStaff> fun) {
if(isHit) {
fun.run(this);
}
return this;
}
/**
* 自由匹配 ( 在free作用域里执行stop()不会跳出Auth函数,而是仅仅跳出free代码块 )
* @param fun 要执行的函数
* @return 对象自身
*/
public SaRouterStaff free(SaParamFunction<SaRouterStaff> fun) {
if(isHit) {
try {
fun.run(this);
} catch (StopMatchException e) {
// 跳出 free自由匹配代码块
}
}
return this;
}
// ----------------- 直接指定check函数
/**
* 路由匹配,如果匹配成功则执行认证函数
* @param pattern 路由匹配符
* @param fun 要执行的校验方法
* @return /
*/
public SaRouterStaff match(String pattern, SaFunction fun) {
return this.match(pattern).check(fun);
}
/**
* 路由匹配,如果匹配成功则执行认证函数
* @param pattern 路由匹配符
* @param fun 要执行的校验方法
* @return /
*/
public SaRouterStaff match(String pattern, SaParamFunction<SaRouterStaff> fun) {
return this.match(pattern).check(fun);
}
/**
* 路由匹配 (并指定排除匹配符),如果匹配成功则执行认证函数
* @param pattern 路由匹配符
* @param excludePattern 要排除的路由匹配符
* @param fun 要执行的方法
* @return /
*/
public SaRouterStaff match(String pattern, String excludePattern, SaFunction fun) {
return this.match(pattern).notMatch(excludePattern).check(fun);
}
/**
* 路由匹配 (并指定排除匹配符),如果匹配成功则执行认证函数
* @param pattern 路由匹配符
* @param excludePattern 要排除的路由匹配符
* @param fun 要执行的方法
* @return /
*/
public SaRouterStaff match(String pattern, String excludePattern, SaParamFunction<SaRouterStaff> fun) {
return this.match(pattern).notMatch(excludePattern).check(fun);
}
// ----------------- 提前退出
/**
* 停止匹配,跳出函数 (在多个匹配链中一次性跳出Auth函数)
* @return 对象自身
*/
public SaRouterStaff stop() {
if(isHit) {
throw new StopMatchException();
}
return this;
}
/**
* 停止匹配,结束执行,向前端返回结果
* @return 对象自身
*/
public SaRouterStaff back() {
if(isHit) {
throw new BackResultException("");
}
return this;
}
/**
* 停止匹配,结束执行,向前端返回结果
* @return 对象自身
* @param result 要输出的结果
*/
public SaRouterStaff back(Object result) {
if(isHit) {
throw new BackResultException(result);
}
return this;
}
}
@@ -27,7 +27,7 @@ public class SaRouterUtil {
* @return 是否匹配成功
*/
public static boolean isMatch(String pattern, String path) {
return SaManager.getSaTokenContext().matchPath(pattern, path);
return SaManager.getSaTokenContextOrSecond().matchPath(pattern, path);
}
/**
@@ -0,0 +1,533 @@
package cn.dev33.satoken.secure;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
/**
* BCrypt加密算法实现。由它加密的文件可在所有支持的操作系统和处理器上进行转移。它的口令必须是8至56个字符,并将在内部被转化为448位的密钥。
* <p>
* 此类来自于https://github.com/jeremyh/jBCrypt/
* <p>
* 使用方法如下:
* <p>
* {@code
* String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt());
* }
* <p>
* 使用checkpw方法检查被加密的字符串是否与原始字符串匹配:
* <p>
* {@code
* BCrypt.checkpw(candidate_password, stored_hash);
* }
* <p>
* gensalt方法提供了可选参数 (log_rounds) 来定义加盐多少,也决定了加密的复杂度:
* <p>
* {@code
* String strong_salt = BCrypt.gensalt(10);
* String stronger_salt = BCrypt.gensalt(12);
* }
*
* @author Damien Miller
* @since 4.1.1
*/
public class BCrypt {
// BCrypt parameters
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
private static final int BCRYPT_SALT_LEN = 16;
// Blowfish parameters
private static final int BLOWFISH_NUM_ROUNDS = 16;
// Initial contents of key schedule
private static final int[] P_orig = {0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7,
0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b};
private static final int[] S_orig = {0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8,
0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, 0xb4cc5c34,
0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af,
0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, 0x2e0b4482,
0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72,
0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4,
0x5e5c9ec2, 0x196a2463, 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857,
0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, 0xe1ffa35d,
0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01,
0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, 0x4fad5ea0,
0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb,
0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a,
0x1b510052, 0x9a532915, 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d,
0xa99f8fa1, 0x08ba4799, 0x6e85076a, 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, 0x4cdd2086, 0x8470eb26, 0x6382e9c6,
0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d,
0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, 0xa9446146,
0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366,
0xc464c3d2, 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b,
0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6,
0x5266c825, 0x2e4cc978, 0x9c10b36a, 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, 0x1521b628, 0x29076170, 0xecdd4775,
0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca,
0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, 0x0e358829,
0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a,
0x3d816250, 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea,
0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa,
0xc50c06c2, 0x5a04abfc, 0x800bcadc, 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7,
0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, 0x96eb27b3, 0x55fd3941,
0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c,
0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c,
0x9029317c, 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1,
0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e,
0xe6ba0d99, 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b,
0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, 0x6a124237, 0xb79251e7,
0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057,
0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591,
0xc902de4c, 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df,
0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28,
0xc0f586e0, 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce,
0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315,
0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, 0x2939bbdb,
0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9,
0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc,
0xf8d56629, 0x79132e28, 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b,
0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, 0x586cdecf,
0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e,
0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, 0x6f3f3b82,
0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71,
0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df,
0xd3a0342b, 0x8971f21e, 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166,
0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, 0x38abbd60,
0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0,
0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6};
// bcrypt IV: "OrpheanBeholderScryDoubt". The C implementation calls
// this "ciphertext", but it is really plaintext or an IV. We keep
// the name to make code comparison easier.
static private final int[] bf_crypt_ciphertext = {0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274};
// Table for Base64 encoding
static private final char[] base64_code = {'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
// Table for Base64 decoding
static private final byte[] index_64 = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, 0, 1, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, -1, -1, -1, -1, -1, -1, -1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, -1, -1, -1, -1, -1, -1, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, -1, -1, -1, -1, -1};
// Expanded Blowfish key
private int[] P;
private int[] S;
/**
* Encode a byte array using bcrypt's slightly-modified base64 encoding scheme. Note that this is *not* compatible with the standard MIME-base64 encoding.
*
* @param d the byte array to encode
* @param len the number of bytes to encode
* @return base64-encoded string
* @throws IllegalArgumentException if the length is invalid
*/
private static String encode_base64(byte[] d, int len) throws IllegalArgumentException {
int off = 0;
StringBuilder rs = new StringBuilder();
int c1, c2;
if (len <= 0 || len > d.length)
throw new IllegalArgumentException("Invalid len");
while (off < len) {
c1 = d[off++] & 0xff;
rs.append(base64_code[(c1 >> 2) & 0x3f]);
c1 = (c1 & 0x03) << 4;
if (off >= len) {
rs.append(base64_code[c1 & 0x3f]);
break;
}
c2 = d[off++] & 0xff;
c1 |= (c2 >> 4) & 0x0f;
rs.append(base64_code[c1 & 0x3f]);
c1 = (c2 & 0x0f) << 2;
if (off >= len) {
rs.append(base64_code[c1 & 0x3f]);
break;
}
c2 = d[off++] & 0xff;
c1 |= (c2 >> 6) & 0x03;
rs.append(base64_code[c1 & 0x3f]);
rs.append(base64_code[c2 & 0x3f]);
}
return rs.toString();
}
/**
* Look up the 3 bits base64-encoded by the specified character, range-checking againt conversion table
*
* @param x the base64-encoded value
* @return the decoded value of x
*/
private static byte char64(char x) {
if ((int) x > index_64.length)
return -1;
return index_64[x];
}
/**
* Decode a string encoded using bcrypt's base64 scheme to a byte array.<br>
* Note that this is *not* compatible with the standard MIME-base64 encoding.
*
* @param s the string to decode
* @param maxolen the maximum number of bytes to decode
* @return an array containing the decoded bytes
* @throws IllegalArgumentException if maxolen is invalid
*/
private static byte[] decodeBase64(String s, int maxolen) throws IllegalArgumentException {
final StringBuilder rs = new StringBuilder();
int off = 0, slen = s.length(), olen = 0;
byte[] ret;
byte c1, c2, c3, c4, o;
if (maxolen <= 0)
throw new IllegalArgumentException("Invalid maxolen");
while (off < slen - 1 && olen < maxolen) {
c1 = char64(s.charAt(off++));
c2 = char64(s.charAt(off++));
if (c1 == -1 || c2 == -1)
break;
o = (byte) (c1 << 2);
o |= (c2 & 0x30) >> 4;
rs.append((char) o);
if (++olen >= maxolen || off >= slen)
break;
c3 = char64(s.charAt(off++));
if (c3 == -1)
break;
o = (byte) ((c2 & 0x0f) << 4);
o |= (c3 & 0x3c) >> 2;
rs.append((char) o);
if (++olen >= maxolen || off >= slen)
break;
c4 = char64(s.charAt(off++));
o = (byte) ((c3 & 0x03) << 6);
o |= c4;
rs.append((char) o);
++olen;
}
ret = new byte[olen];
for (off = 0; off < olen; off++)
ret[off] = (byte) rs.charAt(off);
return ret;
}
/**
* Blowfish encipher a single 64-bit block encoded as two 32-bit halves
*
* @param lr an array containing the two 32-bit half blocks
* @param off the position in the array of the blocks
*/
private void encipher(int[] lr, int off) {
int i, n, l = lr[off], r = lr[off + 1];
l ^= P[0];
for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2; ) {
// Feistel substitution on left word
n = S[(l >> 24) & 0xff];
n += S[0x100 | ((l >> 16) & 0xff)];
n ^= S[0x200 | ((l >> 8) & 0xff)];
n += S[0x300 | (l & 0xff)];
r ^= n ^ P[++i];
// Feistel substitution on right word
n = S[(r >> 24) & 0xff];
n += S[0x100 | ((r >> 16) & 0xff)];
n ^= S[0x200 | ((r >> 8) & 0xff)];
n += S[0x300 | (r & 0xff)];
l ^= n ^ P[++i];
}
lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1];
lr[off + 1] = l;
}
/**
* Cycically extract a word of key material
*
* @param data the string to extract the data from
* @param offp a "pointer" (as a one-entry array) to the current offset into data
* @return the next word of material from data
*/
private static int streamToWord(byte[] data, int[] offp) {
int i;
int word = 0;
int off = offp[0];
for (i = 0; i < 4; i++) {
word = (word << 8) | (data[off] & 0xff);
off = (off + 1) % data.length;
}
offp[0] = off;
return word;
}
/**
* Initialise the Blowfish key schedule
*/
private void init_key() {
P = P_orig.clone();
S = S_orig.clone();
}
/**
* Key the Blowfish cipher
*
* @param key an array containing the key
*/
private void key(byte[] key) {
int i;
int[] koffp = {0};
int[] lr = {0, 0};
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++)
P[i] = P[i] ^ streamToWord(key, koffp);
for (i = 0; i < plen; i += 2) {
encipher(lr, 0);
P[i] = lr[0];
P[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2) {
encipher(lr, 0);
S[i] = lr[0];
S[i + 1] = lr[1];
}
}
/**
* Perform the "enhanced key schedule" step described by Provos and Mazieres in "A Future-Adaptable Password Scheme" http://www.openbsd.org/papers/bcrypt-paper.ps
*
* @param data salt information
* @param key password information
*/
private void ekskey(byte[] data, byte[] key) {
int i;
int[] koffp = {0};
int[] doffp = {0};
int[] lr = {0, 0};
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++)
P[i] = P[i] ^ streamToWord(key, koffp);
for (i = 0; i < plen; i += 2) {
lr[0] ^= streamToWord(data, doffp);
lr[1] ^= streamToWord(data, doffp);
encipher(lr, 0);
P[i] = lr[0];
P[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2) {
lr[0] ^= streamToWord(data, doffp);
lr[1] ^= streamToWord(data, doffp);
encipher(lr, 0);
S[i] = lr[0];
S[i + 1] = lr[1];
}
}
/**
* 加密密文
*
* @param password 明文密码
* @param salt 加盐
* @param log_rounds hash中叠加的对数
* @param cdata 加密数据
* @return 加密后的密文
*/
public byte[] crypt(byte[] password, byte[] salt, int log_rounds, int[] cdata) {
int rounds, i, j;
int clen = cdata.length;
byte[] ret;
if (log_rounds < 4 || log_rounds > 30)
throw new IllegalArgumentException("Bad number of rounds");
rounds = 1 << log_rounds;
if (salt.length != BCRYPT_SALT_LEN)
throw new IllegalArgumentException("Bad salt length");
init_key();
ekskey(salt, password);
for (i = 0; i != rounds; i++) {
key(password);
key(salt);
}
for (i = 0; i < 64; i++) {
for (j = 0; j < (clen >> 1); j++)
encipher(cdata, j << 1);
}
ret = new byte[clen * 4];
for (i = 0, j = 0; i < clen; i++) {
ret[j++] = (byte) ((cdata[i] >> 24) & 0xff);
ret[j++] = (byte) ((cdata[i] >> 16) & 0xff);
ret[j++] = (byte) ((cdata[i] >> 8) & 0xff);
ret[j++] = (byte) (cdata[i] & 0xff);
}
return ret;
}
/**
* 生成密文,使用长度为10的加盐方式
*
* @param password 需要加密的明文
* @return 密文
*/
public static String hashpw(String password) {
return hashpw(password, gensalt());
}
/**
* 生成密文
*
* @param password 需要加密的明文
* @param salt 盐,使用{@link #gensalt()} 生成
* @return 密文
*/
public static String hashpw(String password, String salt) {
BCrypt bcrypt;
String real_salt;
byte[] saltb;
byte[] hashed;
char minor = (char) 0;
int rounds, off;
StringBuilder rs = new StringBuilder();
if (salt.charAt(0) != '$' || salt.charAt(1) != '2')
throw new IllegalArgumentException("Invalid salt version");
if (salt.charAt(2) == '$')
off = 3;
else {
minor = salt.charAt(2);
// pr#1560@Github
// 修正一个在Blowfish实现上的安全风险
if ((minor != 'a' && minor != 'x' && minor != 'y' && minor != 'b') || salt.charAt(3) != '$')
throw new IllegalArgumentException("Invalid salt revision");
off = 4;
}
// Extract number of rounds
if (salt.charAt(off + 2) > '$')
throw new IllegalArgumentException("Missing salt rounds");
rounds = Integer.parseInt(salt.substring(off, off + 2));
real_salt = salt.substring(off + 3, off + 25);
byte[] passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes(StandardCharsets.UTF_8);
saltb = decodeBase64(real_salt, BCRYPT_SALT_LEN);
bcrypt = new BCrypt();
hashed = bcrypt.crypt(passwordb, saltb, rounds, bf_crypt_ciphertext.clone());
rs.append("$2");
if (minor >= 'a')
rs.append(minor);
rs.append("$");
if (rounds < 10)
rs.append("0");
if (rounds > 30) {
throw new IllegalArgumentException("rounds exceeds maximum (30)");
}
rs.append(rounds);
rs.append("$");
rs.append(encode_base64(saltb, saltb.length));
rs.append(encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1));
return rs.toString();
}
/**
* 生成盐
*
* @param log_rounds hash中叠加的2的对数 - the work factor therefore increases as 2**log_rounds.
* @param random {@link SecureRandom}
* @return an encoded salt value
*/
public static String gensalt(int log_rounds, SecureRandom random) {
final StringBuilder rs = new StringBuilder();
byte[] rnd = new byte[BCRYPT_SALT_LEN];
random.nextBytes(rnd);
rs.append("$2a$");
if (log_rounds < 10)
rs.append("0");
if (log_rounds > 30) {
throw new IllegalArgumentException("log_rounds exceeds maximum (30)");
}
rs.append(log_rounds);
rs.append("$");
rs.append(encode_base64(rnd, rnd.length));
return rs.toString();
}
/**
* 生成盐
*
* @param log_rounds the log2 of the number of rounds of hashing to apply - the work factor therefore increases as 2**log_rounds.
* @return 盐
*/
public static String gensalt(int log_rounds) {
return gensalt(log_rounds, new SecureRandom());
}
/**
* 生成盐
*
* @return 盐
*/
public static String gensalt() {
return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
}
/**
* 检查明文密码文本是否匹配加密后的文本
*
* @param plaintext 需要验证的明文密码
* @param hashed 密文
* @return 是否匹配
*/
public static boolean checkpw(String plaintext, String hashed) {
byte[] hashed_bytes;
byte[] try_bytes;
String try_pw;
try {
try_pw = hashpw(plaintext, hashed);
} catch (Exception ignore) {
// 生成密文时错误直接返回false issue#1377@Github
return false;
}
hashed_bytes = hashed.getBytes(StandardCharsets.UTF_8);
try_bytes = try_pw.getBytes(StandardCharsets.UTF_8);
if (hashed_bytes.length != try_bytes.length) {
return false;
}
byte ret = 0;
for (int i = 0; i < try_bytes.length; i++)
ret |= hashed_bytes[i] ^ try_bytes[i];
return ret == 0;
}
}
@@ -8,6 +8,7 @@ import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.fun.SaRetFunction;
import cn.dev33.satoken.util.SaFoxUtil;
@@ -20,7 +21,17 @@ import cn.dev33.satoken.util.SaFoxUtil;
public class SaSession implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 在 Session 上存储角色时建议使用的key
*/
public static final String ROLE_LIST = "ROLE_LIST";
/**
* 在 Session 上存储权限时建议使用的key
*/
public static final String PERMISSION_LIST = "PERMISSION_LIST";
/** 此Session的id */
private String id;
@@ -139,6 +150,16 @@ public class SaSession implements Serializable {
update();
}
/**
* 添加一个token签名
*
* @param tokenValue token值
* @param device 设备标识
*/
public void addTokenSign(String tokenValue, String device) {
addTokenSign(new TokenSign(tokenValue, device));
}
/**
* 移除一个token签名
*
@@ -196,8 +217,10 @@ public class SaSession implements Serializable {
* @param minTimeout 过期时间 (单位: 秒)
*/
public void updateMinTimeout(long minTimeout) {
if(getTimeout() < minTimeout) {
SaManager.getSaTokenDao().updateSessionTimeout(this.id, minTimeout);
long min = trans(minTimeout);
long curr = trans(getTimeout());
if(curr < min) {
updateTimeout(minTimeout);
}
}
@@ -206,12 +229,21 @@ public class SaSession implements Serializable {
* @param maxTimeout 过期时间 (单位: 秒)
*/
public void updateMaxTimeout(long maxTimeout) {
if(getTimeout() > maxTimeout) {
SaManager.getSaTokenDao().updateSessionTimeout(this.id, maxTimeout);
long max = trans(maxTimeout);
long curr = trans(getTimeout());
if(curr > max) {
updateTimeout(maxTimeout);
}
}
/**
* value为 -1 时返回 Long.MAX_VALUE,否则原样返回
* @param value /
* @return /
*/
protected long trans(long value) {
return value == SaTokenDao.NEVER_EXPIRE ? Long.MAX_VALUE : value;
}
// ----------------------- 存取值 (类型转换)
@@ -1,9 +1,22 @@
package cn.dev33.satoken.session;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.strategy.SaStrategy;
/**
* 自定义Session工具类
* 自定义 Session 工具类
*
* <p>样例:
* <pre>
* // 在一处代码写入数据
* SaSession session = SaSessionCustomUtil.getSessionById("role-" + 1001);
* session.set("count", 1);
*
* // 在另一处代码获取数据
* SaSession session = SaSessionCustomUtil.getSessionById("role-" + 1001);
* int count = session.getInt("count");
* System.out.println("count=" + count);
* </pre>
*
* @author kong
*
@@ -11,12 +24,12 @@ import cn.dev33.satoken.SaManager;
public class SaSessionCustomUtil {
/**
* 添加上指定前缀,防止恶意伪造Session
* 添加上指定前缀,防止恶意伪造Session
*/
public static String sessionKey = "custom";
/**
* 拼接Key: 自定义Session的Id
* 拼接Key: 自定义Session的Id
*
* @param sessionId 会话id
* @return sessionId
@@ -45,7 +58,7 @@ public class SaSessionCustomUtil {
public static SaSession getSessionById(String sessionId, boolean isCreate) {
SaSession session = SaManager.getSaTokenDao().getSession(splicingSessionKey(sessionId));
if (session == null && isCreate) {
session = SaManager.getSaTokenAction().createSession(splicingSessionKey(sessionId));
session = SaStrategy.me.createSession.apply(splicingSessionKey(sessionId));
SaManager.getSaTokenDao().setSession(session, SaManager.getConfig().getTimeout());
}
return session;
@@ -3,7 +3,7 @@ package cn.dev33.satoken.session;
import java.io.Serializable;
/**
* Token签名 Model
* Token 签名 Model
*
* 挂在到SaSession上的token签名
*
@@ -43,7 +43,7 @@ public class TokenSign implements Serializable {
}
/**
* @return token value
* @return token
*/
public String getValue() {
return value;
@@ -48,6 +48,9 @@ public class SaSsoConsts {
/** back参数名称 */
public static String back = "back";
/** mode参数名称 */
public static String mode = "mode";
/** loginId参数名称 */
public static String loginId = "loginId";
@@ -70,6 +73,12 @@ public class SaSsoConsts {
/** 表示自己 */
public static final String SELF = "self";
/** 表示简单模式(SSO模式一) */
public static final String MODE_SIMPLE = "simple";
/** 表示ticket模式(SSO模式二和模式三) */
public static final String MODE_TICKET = "ticket";
/** 表示请求没有得到任何有效处理 {msg: "not handle"} */
public static final String NOT_HANDLE = "{\"msg\": \"not handle\"}";
@@ -20,7 +20,7 @@ import cn.dev33.satoken.util.SaResult;
public class SaSsoHandle {
/**
* 处理所有Server端请求
* 处理Server端所有请求
* @return 处理结果
*/
public static Object serverRequest() {
@@ -46,8 +46,13 @@ public class SaSsoHandle {
return ssoCheckTicket();
}
// SSO-Server端:单点注销
if(req.isPath(Api.ssoLogout) && cfg.isSlo) {
// SSO-Server端:单点注销 [模式一] (不带loginId参数)
if(req.isPath(Api.ssoLogout) && cfg.isSlo && req.hasParam(ParamName.loginId) == false) {
return ssoServerLogoutType1();
}
// SSO-Server端:单点注销 [模式三] (带loginId参数)
if(req.isPath(Api.ssoLogout) && cfg.isHttp && cfg.isSlo && req.hasParam(ParamName.loginId)) {
return ssoServerLogout();
}
@@ -66,14 +71,24 @@ public class SaSsoHandle {
SaSsoConfig cfg = SaManager.getConfig().getSso();
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
// ---------- 此处两种情况分开处理:
// 情况1:在SSO认证中心尚未登录,先去登
// ---------- 此处两种情况分开处理:
// ---- 情况1:在SSO认证中心尚未登录,需要先去登录
if(stpLogic.isLogin() == false) {
return cfg.notLoginView.get();
}
// 情况2:在SSO认证中心已经登录,开始构建授权重定向地址,下放ticket
String redirectUrl = SaSsoUtil.buildRedirectUrl(stpLogic.getLoginId(), req.getParam(ParamName.redirect));
return res.redirect(redirectUrl);
// ---- 情况2:在SSO认证中心已经登录,需要重定向回 Client 端,而这又分为两种方式:
String mode = req.getParam(ParamName.mode, "");
// 方式1:直接重定向回Client端 (mode=simple)
if(mode.equals(SaSsoConsts.MODE_SIMPLE)) {
String redirect = req.getParam(ParamName.redirect);
SaSsoUtil.checkRedirectUrl(redirect);
return res.redirect(redirect);
} else {
// 方式2:带着ticket参数重定向回Client端 (mode=ticket)
String redirectUrl = SaSsoUtil.buildRedirectUrl(stpLogic.getLoginId(), req.getParam(ParamName.redirect));
return res.redirect(redirectUrl);
}
}
/**
@@ -112,7 +127,24 @@ public class SaSsoHandle {
}
/**
* SSO-Server端:单点注销
* SSO-Server端:单点注销 [模式一]
* @return 处理结果
*/
public static Object ssoServerLogoutType1() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
// 开始处理
stpLogic.logout();
// 返回
return ssoLogoutBack(req, res);
}
/**
* SSO-Server端:单点注销 [模式三]
* @return 处理结果
*/
public static Object ssoServerLogout() {
@@ -126,7 +158,6 @@ public class SaSsoHandle {
String secretkey = req.getParam(ParamName.secretkey);
// 遍历通知Client端注销会话
// SaSsoUtil.singleLogout(secretkey, loginId, url -> cfg.sendHttp.apply(url));
// step.1 校验秘钥
SaSsoUtil.checkSecretkey(secretkey);
@@ -134,7 +165,7 @@ public class SaSsoHandle {
SaSsoUtil.forEachSloUrl(loginId, url -> cfg.sendHttp.apply(url));
// step.3 Server端注销
stpLogic.logoutByTokenValue(stpLogic.getTokenValueByLoginId(loginId));
stpLogic.logout(loginId);
// 完成
return SaSsoConsts.OK;
@@ -142,7 +173,7 @@ public class SaSsoHandle {
/**
* 处理所有Client端请求
* 处理Client端所有请求
* @return 处理结果
*/
public static Object clientRequest() {
@@ -206,20 +237,8 @@ public class SaSsoHandle {
return res.redirect(serverAuthUrl);
} else {
// ------- 1、校验ticket,获取账号id
Object loginId = null;
if(cfg.isHttp) {
// 方式1:使用http请求校验ticket
String ssoLogoutCall = null;
if(cfg.isSlo) {
ssoLogoutCall = SaHolder.getRequest().getUrl().replace(Api.ssoLogin, Api.ssoLogoutCall);
}
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
Object body = cfg.sendHttp.apply(checkUrl);
loginId = (SaFoxUtil.isEmpty(body) ? null : body);
} else {
// 方式2:直连Redis校验ticket
loginId = SaSsoUtil.checkTicket(ticket);
}
Object loginId = checkTicket(ticket, Api.ssoLogin);
// Be: 如果开发者自定义了处理逻辑
if(cfg.ticketResultHandle != null) {
return cfg.ticketResultHandle.apply(loginId, back);
@@ -307,7 +326,7 @@ public class SaSsoHandle {
/*
* 三种情况:
* 1. 有back参数,值为SELF -> 回退一级并刷新
* 2. 有back参数,值为url -> 跳转back地址
* 2. 有back参数,值为url -> 跳转到此url地址
* 3. 无back参数 -> 返回json数据
*/
String back = req.getParam(ParamName.back);
@@ -321,4 +340,28 @@ public class SaSsoHandle {
}
}
/**
* 封装:校验ticket,取出loginId
* @param ticket ticket码
* @param currUri 当前路由的uri,用于计算单点注销回调地址
* @return loginId
*/
public static Object checkTicket(String ticket, String currUri) {
SaSsoConfig cfg = SaManager.getConfig().getSso();
// --------- 两种模式
if(cfg.isHttp) {
// 模式三:使用http请求校验ticket
String ssoLogoutCall = null;
if(cfg.isSlo) {
ssoLogoutCall = SaHolder.getRequest().getUrl().replace(currUri, Api.ssoLogoutCall);
}
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
Object body = cfg.sendHttp.apply(checkUrl);
return (SaFoxUtil.isEmpty(body) ? null : body);
} else {
// 模式二:直连Redis校验ticket
return SaSsoUtil.checkTicket(ticket);
}
}
}
@@ -11,6 +11,7 @@ import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.sso.SaSsoConsts.ParamName;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
/**
@@ -213,7 +214,7 @@ public class SaSsoTemplate {
// 3、是否在[允许地址列表]之中
List<String> authUrlList = Arrays.asList(getAllowUrl().replaceAll(" ", "").split(","));
if(SaManager.getSaTokenAction().hasElement(authUrlList, url) == false) {
if(SaStrategy.me.hasElement.apply(authUrlList, url) == false) {
throw new SaTokenException("非法redirect" + url);
}
@@ -327,14 +328,17 @@ public class SaSsoTemplate {
* @param fun 调用方法
*/
public void forEachSloUrl(Object loginId, CallSloUrlFunction fun) {
SaSession session = stpLogic.getSessionByLoginId(loginId, false);
if(session == null) {
return;
}
String secretkey = SaManager.getConfig().getSso().getSecretkey();
Set<String> urlSet = stpLogic.getSessionByLoginId(loginId).get(SaSsoConsts.SLO_CALLBACK_SET_KEY,
() -> new HashSet<String>());
Set<String> urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, () -> new HashSet<String>());
for (String url : urlSet) {
// 拼接:login参数、秘钥参数
url = SaFoxUtil.joinParam(url, ParamName.loginId, loginId);
url = SaFoxUtil.joinParam(url, ParamName.secretkey, secretkey);
url = SaFoxUtil.joinParam(url, ParamName.secretkey, secretkey);
// 调用
fun.run(url);
}
@@ -408,7 +412,7 @@ public class SaSsoTemplate {
* @author kong
*/
@FunctionalInterface
static interface CallSloUrlFunction{
public static interface CallSloUrlFunction{
/**
* 调用function
* @param url 注销回调URL
@@ -0,0 +1,72 @@
package cn.dev33.satoken.stp;
import java.util.Map;
/**
*
* 快速构建 调用 `StpUtil.login()` 时的 [配置参数 Model ]
*
* @author kong
*
*/
public class SaLoginConfig {
/**
* @param device 此次登录的客户端设备标识
* @return SaLoginModel配置对象
*/
public static SaLoginModel setDevice(String device) {
return create().setDevice(device);
}
/**
* @param isLastingCookie 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
* @return 对象自身
*/
public static SaLoginModel setIsLastingCookie(Boolean isLastingCookie) {
return create().setIsLastingCookie(isLastingCookie);
}
/**
* @param timeout 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值)
* @return 对象自身
*/
public static SaLoginModel setTimeout(Long timeout) {
return create().setTimeout(timeout);
}
/**
* @param extraData 扩展信息(只在jwt模式下生效)
* @return 对象自身
*/
public static SaLoginModel setExtraData(Map<String, Object> extraData) {
return create().setExtraData(extraData);
}
/**
* @param token 预定Token(预定本次登录生成的Token值)
* @return 对象自身
*/
public static SaLoginModel setToken(String token) {
return create().setToken(token);
}
/**
* 写入扩展数据(只在jwt模式下生效)
* @param key 键
* @param value 值
* @return 对象自身
*/
public static SaLoginModel setExtra(String key, Object value) {
return create().setExtra(key, value);
}
/**
* 静态方法获取一个 SaLoginModel 对象
* @return SaLoginModel 对象
*/
public static SaLoginModel create() {
return new SaLoginModel();
}
}
@@ -1,5 +1,8 @@
package cn.dev33.satoken.stp;
import java.util.LinkedHashMap;
import java.util.Map;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.dao.SaTokenDao;
@@ -12,7 +15,6 @@ import cn.dev33.satoken.util.SaTokenConsts;
*/
public class SaLoginModel {
/**
* 此次登录的客户端设备标识
*/
@@ -28,16 +30,26 @@ public class SaLoginModel {
*/
public Long timeout;
/**
* 扩展信息(只在jwt模式下生效)
*/
public Map<String, Object> extraData;
/**
* 预定Token(预定本次登录生成的Token值)
*/
public String token;
/**
* @return 参考 {@link #device}
* @return 此次登录的客户端设备标识
*/
public String getDevice() {
return device;
}
/**
* @param device 参考 {@link #device}
* @param device 此次登录的客户端设备标识
* @return 对象自身
*/
public SaLoginModel setDevice(String device) {
@@ -46,14 +58,14 @@ public class SaLoginModel {
}
/**
* @return 参考 {@link #isLastingCookie}
* @return 参考 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
*/
public Boolean getIsLastingCookie() {
return isLastingCookie;
}
/**
* @param isLastingCookie 参考 {@link #isLastingCookie}
* @param isLastingCookie 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
* @return 对象自身
*/
public SaLoginModel setIsLastingCookie(Boolean isLastingCookie) {
@@ -62,14 +74,14 @@ public class SaLoginModel {
}
/**
* @return 参考 {@link #timeout}
* @return 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值)
*/
public Long getTimeout() {
return timeout;
}
/**
* @param timeout 参考 {@link #timeout}
* @param timeout 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值)
* @return 对象自身
*/
public SaLoginModel setTimeout(long timeout) {
@@ -77,6 +89,75 @@ public class SaLoginModel {
return this;
}
/**
* @return 扩展信息(只在jwt模式下生效)
*/
public Map<String, Object> getExtraData() {
return extraData;
}
/**
* @param extraData 扩展信息(只在jwt模式下生效)
* @return 对象自身
*/
public SaLoginModel setExtraData(Map<String, Object> extraData) {
this.extraData = extraData;
return this;
}
/**
* @return 预定Token(预定本次登录生成的Token值)
*/
public String getToken() {
return token;
}
/**
* @param token 预定Token(预定本次登录生成的Token值)
* @return 对象自身
*/
public SaLoginModel setToken(String token) {
this.token = token;
return this;
}
/*
* toString
*/
@Override
public String toString() {
return "SaLoginModel [device=" + device + ", isLastingCookie=" + isLastingCookie + ", timeout=" + timeout
+ ", extraData=" + extraData + ", token=" + token + "]";
}
// ------ 附加方法
/**
* 写入扩展数据(只在jwt模式下生效)
* @param key 键
* @param value 值
* @return 对象自身
*/
public SaLoginModel setExtra(String key, Object value) {
if(this.extraData == null) {
this.extraData = new LinkedHashMap<>();
}
this.extraData.put(key, value);
return this;
}
/**
* 获取扩展数据(只在jwt模式下生效)
* @param key 键
* @return 扩展数据的值
*/
public Object getExtra(String key) {
if(this.extraData == null) {
return null;
}
return this.extraData.get(key);
}
/**
* @return Cookie时长
@@ -91,6 +172,16 @@ public class SaLoginModel {
return (int)(long)timeout;
}
/**
* @return 获取device参数,如果为null,则返回默认值
*/
public String getDeviceOrDefault() {
if(device == null) {
return SaTokenConsts.DEFAULT_LOGIN_DEVICE;
}
return device;
}
/**
* 构建对象,初始化默认值
* @return 对象自身
@@ -105,9 +196,9 @@ public class SaLoginModel {
* @return 对象自身
*/
public SaLoginModel build(SaTokenConfig config) {
if(device == null) {
device = SaTokenConsts.DEFAULT_LOGIN_DEVICE;
}
// if(device == null) {
// device = SaTokenConsts.DEFAULT_LOGIN_DEVICE;
// }
if(isLastingCookie == null) {
isLastingCookie = true;
}
@@ -125,12 +216,13 @@ public class SaLoginModel {
return new SaLoginModel();
}
/**
* toString
* 更换为 getDeviceOrDefault()
* @return /
*/
@Override
public String toString() {
return "SaLoginModel [device=" + device + ", isLastingCookie=" + isLastingCookie + ", timeout=" + timeout + "]";
@Deprecated
public String getDeviceOrDefalut() {
return getDeviceOrDefault();
}
}
File diff suppressed because it is too large Load Diff
@@ -2,11 +2,12 @@ package cn.dev33.satoken.stp;
import java.util.List;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.session.SaSession;
/**
* Sa-Token 权限证工具类
* Sa-Token 权限证工具类
* @author kong
*/
public class StpUtil {
@@ -29,6 +30,16 @@ public class StpUtil {
return stpLogic.getLoginType();
}
/**
* 重置 StpLogic 对象
* @param stpLogic /
*/
public static void setStpLogic(StpLogic stpLogic) {
StpUtil.stpLogic = stpLogic;
// 防止自定义 stpLogic 被覆盖
SaManager.putStpLogic(stpLogic);
}
// =================== 获取token 相关 ===================
@@ -40,6 +51,14 @@ public class StpUtil {
return stpLogic.getTokenName();
}
/**
* 在当前会话写入当前TokenValue
* @param tokenValue token值
*/
public static void setTokenValue(String tokenValue){
stpLogic.setTokenValue(tokenValue);
}
/**
* 在当前会话写入当前TokenValue
* @param tokenValue token值
@@ -57,6 +76,14 @@ public class StpUtil {
return stpLogic.getTokenValue();
}
/**
* 获取当前TokenValue (不裁剪前缀)
* @return /
*/
public static String getTokenValueNotCut(){
return stpLogic.getTokenValueNotCut();
}
/**
* 获取当前会话的Token信息
* @return token信息
@@ -68,6 +95,8 @@ public class StpUtil {
// =================== 登录相关操作 ===================
// --- 登录
/**
* 会话登录
* @param id 账号id,建议的类型:(long | int | String
@@ -102,16 +131,56 @@ public class StpUtil {
public static void login(Object id, SaLoginModel loginModel) {
stpLogic.login(id, loginModel);
}
/**
* 创建指定账号id的登录会话
* @param id 登录id,建议的类型:(long | int | String
* @return 返回会话令牌
*/
public static String createLoginSession(Object id) {
return stpLogic.createLoginSession(id);
}
/**
* 创建指定账号id的登录会话
* @param id 登录id,建议的类型:(long | int | String
* @param loginModel 此次登录的参数Model
* @return 返回会话令牌
*/
public static String createLoginSession(Object id, SaLoginModel loginModel) {
return stpLogic.createLoginSession(id, loginModel);
}
// --- 注销
/**
* 会话注销
* 会话注销
*/
public static void logout() {
stpLogic.logout();
}
/**
* 会话注销,根据指定Token
* 会话注销,根据账号id
* @param loginId 账号id
*/
public static void logout(Object loginId) {
stpLogic.logout(loginId);
}
/**
* 会话注销,根据账号id 和 设备标识
*
* @param loginId 账号id
* @param device 设备标识 (填null代表所有注销设备)
*/
public static void logout(Object loginId, String device) {
stpLogic.logout(loginId, device);
}
/**
* 会话注销,根据指定 Token
*
* @param tokenValue 指定token
*/
public static void logoutByTokenValue(String tokenValue) {
@@ -119,24 +188,48 @@ public class StpUtil {
}
/**
* 会话注销,根据账号id (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 </p>
* 踢人下线,根据账号id
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
*
* @param loginId 账号id
*/
public static void logoutByLoginId(Object loginId) {
stpLogic.logoutByLoginId(loginId);
public static void kickout(Object loginId) {
stpLogic.kickout(loginId);
}
/**
* 踢人下线,根据账号id 和 设备标识
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
*
* @param loginId 账号id
* @param device 设备标识 (填null代表踢出所有设备)
*/
public static void kickout(Object loginId, String device) {
stpLogic.kickout(loginId, device);
}
/**
* 会话注销,根据账号id and 设备标识 (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
* @param loginId 账号id
* @param device 设备标识
* 踢人下线,根据指定 Token
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
*
* @param tokenValue 指定token
*/
public static void logoutByLoginId(Object loginId, String device) {
stpLogic.logoutByLoginId(loginId, device);
public static void kickoutByTokenValue(String tokenValue) {
stpLogic.kickoutByTokenValue(tokenValue);
}
/**
* 顶人下线,根据账号id 和 设备标识
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-4 </p>
*
* @param loginId 账号id
* @param device 设备标识 (填null代表顶替所有设备)
*/
public static void replaced(Object loginId, String device) {
stpLogic.replaced(loginId, device);
}
// 查询相关
/**
@@ -212,9 +305,18 @@ public class StpUtil {
public static Object getLoginIdByToken(String tokenValue) {
return stpLogic.getLoginIdByToken(tokenValue);
}
/**
* 获取Token扩展信息(只在jwt模式下有效)
* @param key 键值
* @return 对应的扩展数据
*/
public static Object getExtra(String key) {
return stpLogic.getExtra(key);
}
// =================== session相关 ===================
// =================== User-Session 相关 ===================
/**
* 获取指定账号id的Session, 如果Session尚未创建,isCreate=是否新建并返回
@@ -262,7 +364,7 @@ public class StpUtil {
}
// =================== token专属session ===================
// =================== Token-Session 相关 ===================
/**
* 获取指定Token-Session,如果Session尚未创建,则新建并返回
@@ -282,7 +384,7 @@ public class StpUtil {
}
// =================== [临时期] 验证相关 ===================
// =================== [临时有效期] 验证相关 ===================
/**
* 检查当前token 是否已经[临时过期],如果已经过期则抛出异常
@@ -304,7 +406,7 @@ public class StpUtil {
// =================== 过期时间相关 ===================
/**
* 获取当前登录者的token剩余有效时间 (单位: 秒)
* 获取当前登录者的 token 剩余有效时间 (单位: 秒)
* @return token剩余有效时间
*/
public static long getTokenTimeout() {
@@ -312,7 +414,7 @@ public class StpUtil {
}
/**
* 获取当前登录者的Session剩余有效时间 (单位: 秒)
* 获取当前登录者的 User-Session 剩余有效时间 (单位: 秒)
* @return token剩余有效时间
*/
public static long getSessionTimeout() {
@@ -320,7 +422,7 @@ public class StpUtil {
}
/**
* 获取当前token的专属Session剩余有效时间 (单位: 秒)
* 获取当前 Token-Session 剩余有效时间 (单位: 秒)
* @return token剩余有效时间
*/
public static long getTokenSessionTimeout() {
@@ -328,19 +430,60 @@ public class StpUtil {
}
/**
* 获取当前token[临时过期]剩余有效时间 (单位: 秒)
* @return token[临时过期]剩余有效时间
* 获取当前 token [临时过期] 剩余有效时间 (单位: 秒)
* @return token [临时过期] 剩余有效时间
*/
public static long getTokenActivityTimeout() {
return stpLogic.getTokenActivityTimeout();
}
/**
* 对当前 Token 的 timeout 值进行续期
* @param timeout 要修改成为的有效时间 (单位: 秒)
*/
public static void renewTimeout(long timeout) {
stpLogic.renewTimeout(timeout);
}
/**
* 对指定 Token 的 timeout 值进行续期
* @param tokenValue 指定token
* @param timeout 要修改成为的有效时间 (单位: 秒)
*/
public static void renewTimeout(String tokenValue, long timeout) {
stpLogic.renewTimeout(tokenValue, timeout);
}
// =================== 角色验证操作 ===================
/**
* 获取:当前账号的角色集合
* @return /
*/
public static List<String> getRoleList() {
return stpLogic.getRoleList();
}
/**
* 获取:指定账号的角色集合
* @param loginId 指定账号id
* @return /
*/
public static List<String> getRoleList(Object loginId) {
return stpLogic.getRoleList(loginId);
}
/**
* 指定账号id是否含有角色标识, 返回true或false
* 判断:当前账号是否拥有指定角色, 返回true或false
* @param role 角色标识
* @return 是否含有指定角色标识
*/
public static boolean hasRole(String role) {
return stpLogic.hasRole(role);
}
/**
* 判断:指定账号是否含有指定角色标识, 返回true或false
* @param loginId 账号id
* @param role 角色标识
* @return 是否含有指定角色标识
@@ -350,16 +493,25 @@ public class StpUtil {
}
/**
* 当前账号是否含有指定角色标识, 返回true或false
* @param role 角色标识
* @return 是否含有指定角色标识
* 判断:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
* @param roleArray 角色标识数组
* @return true或false
*/
public static boolean hasRole(String role) {
return stpLogic.hasRole(role);
public static boolean hasRoleAnd(String... roleArray){
return stpLogic.hasRoleAnd(roleArray);
}
/**
* 当前账号是否含有指定角色标识, 如果验证通过,则抛出异常: NotRoleException
* 判断:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
* @param roleArray 角色标识数组
* @return true或false
*/
public static boolean hasRoleOr(String... roleArray){
return stpLogic.hasRoleOr(roleArray);
}
/**
* 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
* @param role 角色标识
*/
public static void checkRole(String role) {
@@ -367,7 +519,7 @@ public class StpUtil {
}
/**
* 当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
* 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
* @param roleArray 角色标识数组
*/
public static void checkRoleAnd(String... roleArray){
@@ -375,18 +527,44 @@ public class StpUtil {
}
/**
* 当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
* 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
* @param roleArray 角色标识数组
*/
public static void checkRoleOr(String... roleArray){
stpLogic.checkRoleOr(roleArray);
}
// =================== 权限验证操作 ===================
/**
* 获取:当前账号的权限码集合
* @return /
*/
public static List<String> getPermissionList() {
return stpLogic.getPermissionList();
}
/**
* 获取:指定账号的权限码集合
* @param loginId 指定账号id
* @return /
*/
public static List<String> getPermissionList(Object loginId) {
return stpLogic.getPermissionList(loginId);
}
/**
* 指定账号id是否含有指定权限, 返回true或false
* 判断:当前账号是否含有指定权限, 返回true或false
* @param permission 权限码
* @return 是否含有指定权限
*/
public static boolean hasPermission(String permission) {
return stpLogic.hasPermission(permission);
}
/**
* 判断:指定账号id是否含有指定权限, 返回true或false
* @param loginId 账号id
* @param permission 权限码
* @return 是否含有指定权限
@@ -396,16 +574,25 @@ public class StpUtil {
}
/**
* 当前账号是否含有指定权限, 返回true或false
* @param permission 权限码
* @return 是否含有指定权限
* 判断:当前账号是否含有指定权限, [指定多个,必须全部具有]
* @param permissionArray 权限码数组
* @return true 或 false
*/
public static boolean hasPermission(String permission) {
return stpLogic.hasPermission(permission);
}
public static boolean hasPermissionAnd(String... permissionArray){
return stpLogic.hasPermissionAnd(permissionArray);
}
/**
* 当前账号是否含有指定权限, 如果验证通过,则抛出异常: NotPermissionException
* 判断:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
* @param permissionArray 权限码数组
* @return true 或 false
*/
public static boolean hasPermissionOr(String... permissionArray){
return stpLogic.hasPermissionOr(permissionArray);
}
/**
* 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
* @param permission 权限码
*/
public static void checkPermission(String permission) {
@@ -413,7 +600,7 @@ public class StpUtil {
}
/**
* 当前账号是否含有指定权限 [指定多个,必须全部验证通过]
* 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
* @param permissionArray 权限码数组
*/
public static void checkPermissionAnd(String... permissionArray) {
@@ -421,7 +608,7 @@ public class StpUtil {
}
/**
* 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
* 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
* @param permissionArray 权限码数组
*/
public static void checkPermissionOr(String... permissionArray) {
@@ -637,6 +824,7 @@ public class StpUtil {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.getLoginType() ,使用方式保持不变 </h1>
*
* 获取当前StpLogin的loginKey
* @return 当前StpLogin的loginKey
*/
@@ -647,6 +835,7 @@ public class StpUtil {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
*
* 在当前会话上登录id
* @param loginId 登录id,建议的类型:(long | int | String
*/
@@ -657,6 +846,7 @@ public class StpUtil {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
*
* 在当前会话上登录id, 并指定登录设备
* @param loginId 登录id,建议的类型:(long | int | String
* @param device 设备标识
@@ -668,6 +858,7 @@ public class StpUtil {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
*
* 在当前会话上登录id, 并指定登录设备
* @param loginId 登录id,建议的类型:(long | int | String
* @param isLastingCookie 是否为持久Cookie
@@ -679,6 +870,7 @@ public class StpUtil {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
*
* 在当前会话上登录id, 并指定所有登录参数Model
* @param loginId 登录id,建议的类型:(long | int | String
* @param loginModel 此次登录的参数Model
@@ -688,4 +880,29 @@ public class StpUtil {
stpLogic.login(loginId, loginModel);
}
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
*
* 会话注销,根据账号id (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
* @param loginId 账号id
*/
@Deprecated
public static void logoutByLoginId(Object loginId) {
stpLogic.kickout(loginId);
}
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
*
* 会话注销,根据账号id and 设备标识 (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 </p>
* @param loginId 账号id
* @param device 设备标识 (填null代表所有注销设备)
*/
@Deprecated
public static void logoutByLoginId(Object loginId, String device) {
stpLogic.kickout(loginId, device);
}
}
@@ -0,0 +1,172 @@
package cn.dev33.satoken.strategy;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.session.SaSession;
/**
* Sa-Token 策略对象
* <p>
* 此类统一定义框架内的一些关键性逻辑算法,方便开发者进行按需重写,例:
* </p>
* <pre>
// SaStrategy全局单例,所有方法都用以下形式重写
SaStrategy.me.setCreateToken((loginId, loginType) -》 {
// 自定义Token生成的算法
return "xxxx";
});
* </pre>
*
* @author kong
*
*/
@SuppressWarnings("deprecation")
public final class SaStrategy {
private SaStrategy() {
}
/**
* 获取 SaStrategy 对象的单例引用
*/
public static final SaStrategy me = new SaStrategy();
//
// 所有策略
//
/**
* 创建 Token 的策略
* <p> 参数 [账号id, 账号类型]
*/
public BiFunction<Object, String, String> createToken = (loginId, loginType) -> {
return SaManager.getSaTokenAction().createToken(loginId, loginType);
};
/**
* 创建 Session 的策略
* <p> 参数 [SessionId]
*/
public Function<String, SaSession> createSession = (sessionId) -> {
return SaManager.getSaTokenAction().createSession(sessionId);
};
/**
* 判断:集合中是否包含指定元素(模糊匹配)
* <p> 参数 [集合, 元素]
*/
public BiFunction<List<String>, String, Boolean> hasElement = (list, element) -> {
return SaManager.getSaTokenAction().hasElement(list, element);
};
/**
* 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
* <p> 参数 [Method句柄]
*/
public Consumer<Method> checkMethodAnnotation = (method) -> {
// 先校验 Method 所属 Class 上的注解
me.checkElementAnnotation.accept(method.getDeclaringClass());
// 再校验 Method 上的注解
me.checkElementAnnotation.accept(method);
};
/**
* 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
* <p> 参数 [element元素]
*/
public Consumer<AnnotatedElement> checkElementAnnotation = (element) -> {
// 为了兼容旧版本
SaManager.getSaTokenAction().validateAnnotation(element);
};
/**
* 从元素上获取注解(注解鉴权内部实现)
* <p> 参数 [element元素,要获取的注解类型]
*/
public BiFunction<AnnotatedElement, Class<? extends Annotation> , Annotation> getAnnotation = (element, annotationClass)->{
// 默认使用jdk的注解处理器
return element.getAnnotation(annotationClass);
};
//
// 重写策略 set连缀风格
//
/**
* 重写创建 Token 的策略
* <p> 参数 [账号id, 账号类型]
* @param createToken /
* @return 对象自身
*/
public SaStrategy setCreateToken(BiFunction<Object, String, String> createToken) {
this.createToken = createToken;
return this;
}
/**
* 重写创建 Session 的策略
* <p> 参数 [SessionId]
* @param createSession /
* @return 对象自身
*/
public SaStrategy setCreateSession(Function<String, SaSession> createSession) {
this.createSession = createSession;
return this;
}
/**
* 判断:集合中是否包含指定元素(模糊匹配)
* <p> 参数 [集合, 元素]
* @param hasElement /
* @return 对象自身
*/
public SaStrategy setHasElement(BiFunction<List<String>, String, Boolean> hasElement) {
this.hasElement = hasElement;
return this;
}
/**
* 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
* <p> 参数 [Method句柄]
* @param checkMethodAnnotation /
* @return 对象自身
*/
public SaStrategy setCheckMethodAnnotation(Consumer<Method> checkMethodAnnotation) {
this.checkMethodAnnotation = checkMethodAnnotation;
return this;
}
/**
* 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
* <p> 参数 [element元素]
* @param checkElementAnnotation /
* @return 对象自身
*/
public SaStrategy setCheckElementAnnotation(Consumer<AnnotatedElement> checkElementAnnotation) {
this.checkElementAnnotation = checkElementAnnotation;
return this;
}
/**
* 从元素上获取注解(注解鉴权内部实现)
* <p> 参数 [element元素,要获取的注解类型]
* @param getAnnotation /
* @return 对象自身
*/
public SaStrategy setGetAnnotation(BiFunction<AnnotatedElement, Class<? extends Annotation> , Annotation> getAnnotation) {
this.getAnnotation = getAnnotation;
return this;
}
}
@@ -1,6 +1,7 @@
package cn.dev33.satoken.temp;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
/**
@@ -19,7 +20,7 @@ public interface SaTempInterface {
public default String createToken(Object value, long timeout) {
// 生成 token
String token = SaManager.getSaTokenAction().createToken(null, null);
String token = SaStrategy.me.createToken.apply(null, null);
// 持久化映射关系
String key = splicingKeyTempToken(token);
@@ -60,6 +61,15 @@ public interface SaTempInterface {
String key = splicingKeyTempToken(token);
return SaManager.getSaTokenDao().getObjectTimeout(key);
}
/**
* 删除一个 token
* @param token 指定token
*/
public default void deleteToken(String token) {
String key = splicingKeyTempToken(token);
SaManager.getSaTokenDao().deleteObject(key);
}
/**
* 获取映射关系的持久化key
@@ -48,5 +48,13 @@ public class SaTempUtil {
public static long getTimeout(String token) {
return SaManager.getSaTemp().getTimeout(token);
}
/**
* 删除一个 token
* @param token 指定token
*/
public static void deleteToken(String token) {
SaManager.getSaTemp().deleteToken(token);
}
}
@@ -404,4 +404,51 @@ public class SaFoxUtil {
return str;
}
/**
* String 转 Array,按照逗号切割
* @param str 字符串
* @return 数组
*/
public static String[] convertStringToArray(String str) {
List<String> list = convertStringToList(str);
return list.toArray(new String[list.size()]);
}
/**
* Array 转 String,按照逗号切割
* @param arr 数组
* @return 字符串
*/
public static String convertArrayToString(String[] arr) {
if(arr == null || arr.length == 0) {
return "";
}
return String.join(",", arr);
}
/**
* 返回一个空集合
* @param <T> 集合类型
* @return 空集合
*/
public static <T>List<T> emptyList() {
return new ArrayList<>();
}
/**
* String数组转集合
* @param strs String数组
* @return 集合
*/
public static List<String> toList(String... strs) {
List<String> list = new ArrayList<>();
for (String str : strs) {
list.add(str);
}
return list;
}
}
@@ -10,6 +10,7 @@ import java.util.Map;
* code=状态码 <br>
* msg=描述信息 <br>
* data=携带对象 <br>
*
* @author kong
*
*/
@@ -13,7 +13,7 @@ public class SaTokenConsts {
/**
* Sa-Token 当前版本号
*/
public static final String VERSION_NO = "v1.26.0";
public static final String VERSION_NO = "v1.29.0";
/**
* Sa-Token 开源地址
@@ -30,7 +30,12 @@ public class SaTokenConsts {
/**
* 常量key标记: 如果token为本次请求新创建的,则以此字符串为key存储在当前request中
*/
public static final String JUST_CREATED_SAVE_KEY = "JUST_CREATED_SAVE_KEY_";
public static final String JUST_CREATED = "JUST_CREATED_";
/**
* 常量key标记: 如果token为本次请求新创建的,则以此字符串为key存储在当前request中(不拼接前缀,纯Token
*/
public static final String JUST_CREATED_NOT_PREFIX = "JUST_CREATED_NOT_PREFIX_";
/**
* 常量key标记: 如果本次请求已经验证过[无操作过期], 则以此值存储在当前request中
@@ -97,5 +102,14 @@ public class SaTokenConsts {
* 切面、拦截器、过滤器等各种组件的注册优先级顺序
*/
public static final int ASSEMBLY_ORDER = -100;
// =================== 废弃 ===================
/**
* 请更换为 JUST_CREATED
*/
@Deprecated
public static final String JUST_CREATED_SAVE_KEY = JUST_CREATED;
}
@@ -16,7 +16,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.26.0</sa-token-version>
<sa-token-version>1.29.0</sa-token-version>
</properties>
<dependencies>
@@ -15,11 +15,11 @@ sa-token:
# Redis数据库索引(默认为0
database: 2
# Redis服务器地址
host: 49.235.117.153
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password: Kdfsjia.d.1212dsa
# password:
# 连接超时时间(毫秒)
timeout: 10s
lettuce:
@@ -0,0 +1,13 @@
target/
.project
.classpath
.settings
/.idea/
node_modules/
bin/
.settings/
unpackage/
/.apt_generated/
/.apt_generated_tests/
@@ -0,0 +1,76 @@
<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>com.pj</groupId>
<artifactId>sa-token-demo-dubbo-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
</parent>
<!-- 指定一些属性 -->
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token-version>1.29.0</sa-token-version>
</properties>
<dependencies>
<!-- SpringBoot Web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sa-Token -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.11</version>
</dependency>
<!-- Dubbo 注册到 Nacos -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.11</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.4.1</version>
</dependency>
<!-- Sa-Token 整合 Dubbo -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-context-dubbo</artifactId>
<version>${sa-token-version}</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,22 @@
package com.pj;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Dubbo 服务消费端
*
* @author kong
*
*/
@EnableDubbo
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
System.out.println("ConsumerApplication 启动成功");
}
}
@@ -0,0 +1,16 @@
package com.pj.more;
public interface DemoService {
/**
* 登录
* @param loginId 账号id
*/
void doLogin(Object loginId);
/**
* 判断是否登录,打印状态
*/
void isLogin(String str);
}
@@ -0,0 +1,72 @@
package com.pj.more;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
@RestController
public class TestController {
@DubboReference
private DemoService demoService;
// Consumer端登录,状态传播到Provider端
@RequestMapping("test")
public String test() {
demoService.isLogin("----------- 登录前 ");
StpUtil.login(10001);
demoService.isLogin("----------- 登录后 ");
return "ok";
}
// Provider端登录,状态回传到Consumer端
@RequestMapping("test2")
public String test2() {
System.out.println("----------- 登录前 ");
System.out.println("Token值:" + StpUtil.getTokenValue());
System.out.println("是否登录:" + StpUtil.isLogin());
demoService.doLogin(10002);
System.out.println("----------- 登录后 ");
System.out.println("Token值:" + StpUtil.getTokenValue());
System.out.println("是否登录:" + StpUtil.isLogin());
return "ok";
}
// Consumer端登录,状态在Consumer端保持
@RequestMapping("test3")
public String test3() {
System.out.println("----------- 登录前 ");
System.out.println("Token值:" + StpUtil.getTokenValue());
System.out.println("是否登录:" + StpUtil.isLogin());
StpUtil.login(10003);
demoService.isLogin("----------- Provider状态");
System.out.println("----------- 登录后 ");
System.out.println("Token值:" + StpUtil.getTokenValue());
System.out.println("是否登录:" + StpUtil.isLogin());
return "ok";
}
// Provider端登录,状态在Provider端保持
@RequestMapping("test4")
public String test4() {
// 登录
demoService.doLogin(10004);
// 打印一下
demoService.isLogin("----------- 会话信息 ");
return "ok";
}
}
@@ -0,0 +1,24 @@
server:
# 端口号
port: 8081
spring:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
dubbo:
application:
# 服务名称
name: dubbo-consumer-demo
registry:
# 注册中心地址
address: nacos://127.0.0.1:8001
@@ -0,0 +1,13 @@
target/
.project
.classpath
.settings
/.idea/
node_modules/
bin/
.settings/
unpackage/
/.apt_generated/
/.apt_generated_tests/
@@ -0,0 +1,76 @@
<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>com.pj</groupId>
<artifactId>sa-token-demo-dubbo-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
</parent>
<!-- 指定一些属性 -->
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token-version>1.29.0</sa-token-version>
</properties>
<dependencies>
<!-- SpringBoot Web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sa-Token -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.11</version>
</dependency>
<!-- Dubbo 注册到 Nacos -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.11</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.4.1</version>
</dependency>
<!-- Sa-Token 整合 Dubbo -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-context-dubbo</artifactId>
<version>${sa-token-version}</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,22 @@
package com.pj;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Dubbo 服务提供端
*
* @author kong
*
*/
@EnableDubbo
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
System.out.println("ProviderApplication 启动成功");
}
}
@@ -0,0 +1,16 @@
package com.pj.more;
public interface DemoService {
/**
* 登录
* @param loginId 账号id
*/
void doLogin(Object loginId);
/**
* 判断是否登录,打印状态
*/
void isLogin(String str);
}
@@ -0,0 +1,23 @@
package com.pj.more;
import org.apache.dubbo.config.annotation.DubboService;
import cn.dev33.satoken.stp.StpUtil;
@DubboService()
public class DemoServiceImpl implements DemoService {
@Override
public void doLogin(Object loginId) {
StpUtil.login(loginId);
}
@Override
public void isLogin(String str) {
System.out.println(str);
System.out.println("Token值:" + StpUtil.getTokenValue());
System.out.println("是否登录:" + StpUtil.isLogin());
}
}
@@ -0,0 +1,38 @@
server:
# 端口号
port: 8080
spring:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
# Dubbo
dubbo:
# 服务名
application:
name: dubbo-provider-demo
# 扫描包
scan:
base-packages: com.pj
# 注册中心地址
registry:
address: nacos://127.0.0.1:8001
# 协议
protocol:
name: dubbo
port: 12345
+15 -34
View File
@@ -16,7 +16,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.26.0</sa-token-version>
<sa-token-version>1.29.0</sa-token-version>
</properties>
<dependencies>
@@ -26,52 +26,32 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- sa-token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- sa-token整合redis (使用jdk默认序列化方式) -->
<!-- <dependency>
<!-- Sa-Token 整合 jwt -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis</artifactId>
<artifactId>sa-token-jwt</artifactId>
<version>${sa-token-version}</version>
</dependency> -->
<!-- sa-token整合redis (使用jackson序列化方式) -->
</dependency>
<!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency> -->
<!-- 提供redis连接池 -->
<!-- <dependency>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency> -->
<!-- sa-token整合SpringAOP实现注解鉴权 -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-aop</artifactId>
<version>${sa-token-version}</version>
</dependency> -->
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -79,12 +59,13 @@
<optional>true</optional>
</dependency>
<!-- test -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<!-- <version>2.3.1</version> -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
@@ -10,7 +10,7 @@ public class SaTokenJwtDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenJwtDemoApplication.class, args);
System.out.println("\n启动成功:sa-token配置如下:" + SaManager.getConfig());
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}
@@ -0,0 +1,38 @@
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.interceptor.SaAnnotationInterceptor;
import cn.dev33.satoken.jwt.StpLogicJwtForStyle;
import cn.dev33.satoken.stp.StpLogic;
/**
* [Sa-Token 权限认证] 配置类
* @author kong
*
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/**
* 注册Sa-Token 的拦截器,打开注解式鉴权功能
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册注解拦截器
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
}
/**
* Sa-Token 整合 jwt
*/
@Bean
public StpLogic getStpLogicJwt() {
return new StpLogicJwtForStyle();
}
}
@@ -1,235 +0,0 @@
package com.pj.satoken.jwt;
import java.util.Date;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaTokenConsts;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
@Component
public class SaTokenJwtUtil {
/**
* 秘钥 (随便手打几个字母就好了)
*/
public static final String BASE64_SECURITY = "79e7c69681b8270162386e6daa53d1dd";
/**
* token有效期 (单位: 秒)
*/
public static final long TIMEOUT = 60 * 60 * 2;
public static final String LOGIN_ID_KEY = "loginId";
/**
* 根据userId生成token
* @param loginId 账号id
* @param base64Security 秘钥
* @return jwt-token
*/
public static String createToken(Object loginId) {
// 判断,不可使用默认秘钥
// if(BASE64_SECURITY.equals("79e7c69681b8270162386e6daa53d1dd")) {
// throw new SaTokenException("请更换秘钥");
// }
// 在这里你可以使用官方提供的claim方法构建载荷,也可以使用setPayload自定义载荷,但是两者不可一起使用
JwtBuilder builder = Jwts.builder()
.setHeaderParam("type", "JWT")
.claim(LOGIN_ID_KEY, loginId)
.setIssuedAt(new Date()) // 签发日期
.setExpiration(new Date(System.currentTimeMillis() + 1000 * TIMEOUT)) // 有效截止日期
.signWith(SignatureAlgorithm.HS256, BASE64_SECURITY.getBytes()); // 加密算法
//生成JWT
return builder.compact();
}
/**
* 从一个jwt里面解析出Claims
* @param tokenValue token值
* @param base64Security 秘钥
* @return Claims对象
*/
public static Claims getClaims(String tokenValue) {
// System.out.println(tokenValue);
Claims claims = Jwts.parser()
.setSigningKey(BASE64_SECURITY.getBytes())
.parseClaimsJws(tokenValue).getBody();
return claims;
}
/**
* 从一个jwt里面解析loginId
* @param tokenValue token值
* @param base64Security 秘钥
* @return loginId
*/
public static String getLoginId(String tokenValue) {
try {
Object loginId = getClaims(tokenValue).get(LOGIN_ID_KEY);
if(loginId == null) {
return null;
}
return String.valueOf(loginId);
} catch (ExpiredJwtException e) {
// throw NotLoginException.newInstance(StpUtil.TYPE, NotLoginException.TOKEN_TIMEOUT);
return NotLoginException.TOKEN_TIMEOUT;
} catch (MalformedJwtException e) {
throw NotLoginException.newInstance(StpUtil.stpLogic.loginType, NotLoginException.INVALID_TOKEN);
} catch (Exception e) {
throw new SaTokenException(e);
}
}
static {
// 判断秘钥
if(BASE64_SECURITY.equals("79e7c69681b8270162386e6daa53d1dd")) {
String warn = "-------------------------------------\n";
warn += "请更换JWT秘钥,不要使用示例默认秘钥\n";
warn += "-------------------------------------";
System.err.println(warn);
}
// 提前调用一下方法,促使其属性初始化
StpUtil.getLoginType();
// 修改默认实现
StpUtil.stpLogic = new StpLogic("login") {
// 重写 (随机生成一个tokenValue)
@Override
public String createTokenValue(Object loginId) {
return SaTokenJwtUtil.createToken(loginId);
}
// 重写 (在当前会话上登录id )
@Override
public void login(Object loginId, SaLoginModel loginModel) {
// ------ 1、获取相应对象
SaStorage storage = SaManager.getSaTokenContext().getStorage();
SaTokenConfig config = getConfig();
// ------ 2、生成一个token
String tokenValue = createTokenValue(loginId);
storage.set(splicingKeyJustCreatedSave(), tokenValue); // 将token保存到本次request里
if(config.getIsReadCookie() == true){ // cookie注入
SaManager.getSaTokenContext().getResponse().addCookie(getTokenName(), tokenValue, "/", config.getCookieDomain(), (int)config.getTimeout());
}
}
// 重写 (获取指定token对应的登录id)
@Override
public String getLoginIdNotHandle(String tokenValue) {
try {
return SaTokenJwtUtil.getLoginId(tokenValue);
} catch (Exception e) {
return null;
}
}
// 重写 (当前会话注销登录)
@Override
public void logout() {
// 如果连token都没有,那么无需执行任何操作
String tokenValue = getTokenValue();
if(tokenValue == null) {
return;
}
// 如果打开了cookie模式,把cookie清除掉
if(getConfig().getIsReadCookie() == true){
SaManager.getSaTokenContext().getResponse().deleteCookie(getTokenName());
}
}
// 重写 (获取指定key的session)
@Override
public SaSession getSessionBySessionId(String sessionId, boolean isCreate) {
throw new SaTokenException("jwt has not session");
}
// 重写 (获取当前登录者的token剩余有效时间 (单位: 秒))
@Override
public long getTokenTimeout() {
// 如果没有token
String tokenValue = getTokenValue();
if(tokenValue == null) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
// 开始取值
Claims claims = null;
try {
claims = SaTokenJwtUtil.getClaims(tokenValue);
} catch (Exception e) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
if(claims == null) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
Date expiration = claims.getExpiration();
if(expiration == null) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
return (expiration.getTime() - System.currentTimeMillis()) / 1000;
}
// 重写 (返回当前token的登录设备)
@Override
public String getLoginDevice() {
return SaTokenConsts.DEFAULT_LOGIN_DEVICE;
}
// 重写 (获取当前会话的token信息)
@Override
public SaTokenInfo getTokenInfo() {
SaTokenInfo info = new SaTokenInfo();
info.tokenName = getTokenName();
info.tokenValue = getTokenValue();
info.isLogin = isLogin();
info.loginId = getLoginIdDefaultNull();
info.loginType = getLoginType();
info.tokenTimeout = getTokenTimeout();
// info.sessionTimeout = getSessionTimeout();
// info.tokenSessionTimeout = getTokenSessionTimeout();
// info.tokenActivityTimeout = getTokenActivityTimeout();
info.loginDevice = getLoginDevice();
return info;
}
};
}
}
@@ -1,12 +1,9 @@
package com.pj.test;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.pj.util.AjaxJson;
@@ -21,15 +18,6 @@ import cn.dev33.satoken.exception.NotRoleException;
@RestControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin")
public class GlobalException {
// 在每个控制器之前触发的操作
@ModelAttribute
public void get(HttpServletRequest request) throws IOException {
}
// 全局异常拦截(拦截项目中的所有异常)
@ExceptionHandler
public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
@@ -55,46 +43,5 @@ public class GlobalException {
// 返回给前端
return aj;
// 输出到客户端
// response.setContentType("application/json; charset=utf-8"); // http说明,我要返回JSON对象
// response.getWriter().print(new ObjectMapper().writeValueAsString(aj));
}
// 全局异常拦截(拦截项目中的NotLoginException异常)
// @ExceptionHandler(NotLoginException.class)
// public AjaxJson handlerNotLoginException(NotLoginException nle, HttpServletRequest request, HttpServletResponse response)
// throws Exception {
//
// // 打印堆栈,以供调试
// nle.printStackTrace();
//
// // 判断场景值,定制化异常信息
// String message = "";
// if(nle.getType().equals(NotLoginException.NOT_TOKEN)) {
// message = "未提供token";
// }
// else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) {
// message = "token无效";
// }
// else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) {
// message = "token已过期";
// }
// else if(nle.getType().equals(NotLoginException.BE_REPLACED)) {
// message = "token已被顶下线";
// }
// else if(nle.getType().equals(NotLoginException.KICK_OUT)) {
// message = "token已被踢下线";
// }
// else {
// message = "当前会话未登录";
// }
//
// // 返回给前端
// return AjaxJson.getError(message);
// }
}
@@ -10,6 +10,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
@@ -22,8 +23,6 @@ import cn.dev33.satoken.stp.StpUtil;
@RequestMapping("/test/")
public class TestJwtController {
// 测试登录接口, 浏览器访问: http://localhost:8081/test/login
@RequestMapping("login")
public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
@@ -51,7 +50,7 @@ public class TestJwtController {
System.out.println(tokenInfo);
return AjaxJson.getSuccessData(tokenInfo);
}
// 测试会话session接口, 浏览器访问: http://localhost:8081/test/session
@RequestMapping("session")
@@ -70,11 +69,12 @@ public class TestJwtController {
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
@SaCheckLogin
public AjaxJson test() {
System.out.println();
System.out.println("--------------进入请求--------------");
StpUtil.login(10001);
System.out.println(StpUtil.getTokenInfo().getTokenValue());
System.out.println(StpUtil.getExtra("username"));
System.out.println(StpUtil.getExtra("nick"));
return AjaxJson.getSuccess();
}
@@ -16,6 +16,9 @@ sa-token:
is-share: true
# token风格
token-style: uuid
# jwt秘钥
jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk
spring:
# redis配置
redis:
@@ -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.26.0</sa-token-version>
<sa-token-version>1.29.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.26.0</sa-token-version>
<sa-token-version>1.29.0</sa-token-version>
</properties>
<dependencies>
@@ -21,7 +21,8 @@ public class SaOAuth2TemplateImpl extends SaOAuth2Template {
.setClientId("10001")
.setClientSecret("aaaa-bbbb-cccc-dddd-eeee")
.setAllowUrl("*")
.setContractScope("userinfo");
.setContractScope("userinfo")
.setIsAutoMode(true);
}
return null;
}
@@ -15,7 +15,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.26.0</sa-token-version>
<sa-token-version>1.29.0</sa-token-version>
</properties>
<dependencies>
+2 -2
View File
@@ -9,7 +9,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.26.0</sa-token-version>
<sa-token-version>1.29.0</sa-token-version>
</properties>
<dependencies>
@@ -18,7 +18,7 @@
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-web</artifactId>
<version>1.5.1</version>
<version>1.6.1</version>
</dependency>
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
@@ -185,7 +185,7 @@ public class TestController {
// 先登录上
StpUtil.login(10001);
// 踢下线
StpUtil.logoutByLoginId(10001);
StpUtil.kickout(10001);
// 再尝试获取
StpUtil.getLoginId();
// 返回
@@ -17,7 +17,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.26.0</sa-token-version>
<sa-token-version>1.29.0</sa-token-version>
</properties>
<dependencies>
@@ -14,7 +14,7 @@ import cn.dev33.satoken.SaManager;
public class SaTokenDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenDemoApplication.class, args);
SpringApplication.run(SaTokenDemoApplication.class, args);
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
@@ -0,0 +1,59 @@
package com.pj.current;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.exception.DisableLoginException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
/**
* 全局异常处理
*/
@ControllerAdvice
public class GlobalException {
// 全局异常拦截(拦截项目中的所有异常)
@ResponseBody
@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.getCode());
}
else if(e instanceof DisableLoginException) { // 如果是被封禁异常
DisableLoginException ee = (DisableLoginException) e;
aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封");
}
else { // 普通异常, 输出:500 + 异常信息
aj = AjaxJson.getError(e.getMessage());
}
// 返回给前端
return aj;
}
}
@@ -0,0 +1,32 @@
package com.pj.current;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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;
/**
* 处理 404
* @author kong
*/
@RestController
public class NotFoundHandle implements ErrorController {
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping("/error")
public Object error(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setStatus(200);
return SaResult.get(404, "not found", null);
}
}
@@ -1,7 +1,9 @@
package com.pj.satoken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -10,6 +12,7 @@ import com.pj.util.AjaxJson;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import cn.dev33.satoken.strategy.SaStrategy;
/**
@@ -19,18 +22,18 @@ import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/**
* 注册sa-token的拦截器,打开注解式鉴权功能
* 注册Sa-Token 的拦截器,打开注解式鉴权功能
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册注解拦截器
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**").excludePathPatterns("");
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
}
/**
* 注册 [sa-token全局过滤器]
* 注册 [Sa-Token 全局过滤器]
*/
@Bean
public SaServletFilter getSaServletFilter() {
@@ -40,10 +43,9 @@ public class SaTokenConfigure implements WebMvcConfigurer {
.addInclude("/**")// .addExclude("/favicon.ico")
// 认证函数: 每次请求执行
.setAuth(r -> {
// System.out.println("---------- sa全局认证");
// SaRouter.match("/test/test", () -> new Object());
.setAuth(obj -> {
// System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath());
})
// 异常处理函数:每次认证函数发生异常时执行此函数
@@ -69,5 +71,15 @@ public class SaTokenConfigure implements WebMvcConfigurer {
;
}
/**
* 重写 Sa-Token 框架内部算法策略
*/
@Autowired
public void rewriteSaStrategy() {
// 重写Sa-Token的注解处理器,增加注解合并功能
SaStrategy.me.getAnnotation = (element, annotationClass) -> {
return AnnotatedElementUtils.getMergedAnnotation(element, annotationClass);
};
}
}
@@ -1,52 +0,0 @@
package com.pj.satoken.at;
import java.lang.reflect.AnnotatedElement;
import org.springframework.core.annotation.AnnotatedElementUtils;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.action.SaTokenActionDefaultImpl;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaCheckSafe;
/**
* 继承Sa-Token行为Bean默认实现, 重写部分逻辑
*/
//@Component
public class MySaTokenAction extends SaTokenActionDefaultImpl {
/**
* 重写Sa-Token的注解处理器,加强注解合并功能
* @param target see note
*/
@Override
protected void validateAnnotation(AnnotatedElement target) {
// 校验 @SaCheckLogin 注解
if(AnnotatedElementUtils.isAnnotated(target, SaCheckLogin.class)) {
SaCheckLogin at = AnnotatedElementUtils.getMergedAnnotation(target, SaCheckLogin.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
}
// 校验 @SaCheckRole 注解
if(AnnotatedElementUtils.isAnnotated(target, SaCheckRole.class)) {
SaCheckRole at = AnnotatedElementUtils.getMergedAnnotation(target, SaCheckRole.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
}
// 校验 @SaCheckPermission 注解
if(AnnotatedElementUtils.isAnnotated(target, SaCheckPermission.class)) {
SaCheckPermission at = AnnotatedElementUtils.getMergedAnnotation(target, SaCheckPermission.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
}
// 校验 @SaCheckSafe 注解
if(AnnotatedElementUtils.isAnnotated(target, SaCheckSafe.class)) {
SaCheckSafe at = AnnotatedElementUtils.getMergedAnnotation(target, SaCheckSafe.class);
SaManager.getStpLogic(null).checkByAnnotation(at);
}
}
}
@@ -2,19 +2,18 @@ package com.pj.satoken.at;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
/**
* Sa-Token 权限证工具类 (User版)
* Sa-Token 权限证工具类 (User版)
* @author kong
*/
@Component
public class StpUserUtil {
/**
@@ -35,6 +34,16 @@ public class StpUserUtil {
return stpLogic.getLoginType();
}
/**
* 重置 StpLogic 对象
* @param stpLogic /
*/
public static void setStpLogic(StpLogic stpLogic) {
StpUtil.stpLogic = stpLogic;
// 防止自定义 stpLogic 被覆盖
SaManager.putStpLogic(stpLogic);
}
// =================== 获取token 相关 ===================
@@ -46,6 +55,14 @@ public class StpUserUtil {
return stpLogic.getTokenName();
}
/**
* 在当前会话写入当前TokenValue
* @param tokenValue token值
*/
public static void setTokenValue(String tokenValue){
stpLogic.setTokenValue(tokenValue);
}
/**
* 在当前会话写入当前TokenValue
* @param tokenValue token值
@@ -63,6 +80,14 @@ public class StpUserUtil {
return stpLogic.getTokenValue();
}
/**
* 获取当前TokenValue (不裁剪前缀)
* @return /
*/
public static String getTokenValueNotCut(){
return stpLogic.getTokenValueNotCut();
}
/**
* 获取当前会话的Token信息
* @return token信息
@@ -74,6 +99,8 @@ public class StpUserUtil {
// =================== 登录相关操作 ===================
// --- 登录
/**
* 会话登录
* @param id 账号id,建议的类型:(long | int | String
@@ -108,16 +135,56 @@ public class StpUserUtil {
public static void login(Object id, SaLoginModel loginModel) {
stpLogic.login(id, loginModel);
}
/**
* 创建指定账号id的登录会话
* @param id 登录id,建议的类型:(long | int | String
* @return 返回会话令牌
*/
public static String createLoginSession(Object id) {
return stpLogic.createLoginSession(id);
}
/**
* 创建指定账号id的登录会话
* @param id 登录id,建议的类型:(long | int | String
* @param loginModel 此次登录的参数Model
* @return 返回会话令牌
*/
public static String createLoginSession(Object id, SaLoginModel loginModel) {
return stpLogic.createLoginSession(id, loginModel);
}
// --- 注销
/**
* 会话注销
* 会话注销
*/
public static void logout() {
stpLogic.logout();
}
/**
* 会话注销,根据指定Token
* 会话注销,根据账号id
* @param loginId 账号id
*/
public static void logout(Object loginId) {
stpLogic.logout(loginId);
}
/**
* 会话注销,根据账号id 和 设备标识
*
* @param loginId 账号id
* @param device 设备标识 (填null代表所有注销设备)
*/
public static void logout(Object loginId, String device) {
stpLogic.logout(loginId, device);
}
/**
* 会话注销,根据指定 Token
*
* @param tokenValue 指定token
*/
public static void logoutByTokenValue(String tokenValue) {
@@ -125,24 +192,48 @@ public class StpUserUtil {
}
/**
* 会话注销,根据账号id (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
* 踢人下线,根据账号id
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
*
* @param loginId 账号id
*/
public static void logoutByLoginId(Object loginId) {
stpLogic.logoutByLoginId(loginId);
public static void kickout(Object loginId) {
stpLogic.kickout(loginId);
}
/**
* 踢人下线,根据账号id 和 设备标识
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
*
* @param loginId 账号id
* @param device 设备标识 (填null代表踢出所有设备)
*/
public static void kickout(Object loginId, String device) {
stpLogic.kickout(loginId, device);
}
/**
* 会话注销,根据账号id & 设备标识 (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
* @param loginId 账号id
* @param device 设备标识
* 踢人下线,根据指定 Token
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
*
* @param tokenValue 指定token
*/
public static void logoutByLoginId(Object loginId, String device) {
stpLogic.logoutByLoginId(loginId, device);
public static void kickoutByTokenValue(String tokenValue) {
stpLogic.kickoutByTokenValue(tokenValue);
}
/**
* 顶人下线,根据账号id 和 设备标识
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-4 </p>
*
* @param loginId 账号id
* @param device 设备标识 (填null代表顶替所有设备)
*/
public static void replaced(Object loginId, String device) {
stpLogic.replaced(loginId, device);
}
// 查询相关
/**
@@ -220,7 +311,7 @@ public class StpUserUtil {
}
// =================== session相关 ===================
// =================== User-Session 相关 ===================
/**
* 获取指定账号id的Session, 如果Session尚未创建,isCreate=是否新建并返回
@@ -268,7 +359,7 @@ public class StpUserUtil {
}
// =================== token专属session ===================
// =================== Token-Session 相关 ===================
/**
* 获取指定Token-Session,如果Session尚未创建,则新建并返回
@@ -288,7 +379,7 @@ public class StpUserUtil {
}
// =================== [临时期] 验证相关 ===================
// =================== [临时有效期] 验证相关 ===================
/**
* 检查当前token 是否已经[临时过期],如果已经过期则抛出异常
@@ -310,7 +401,7 @@ public class StpUserUtil {
// =================== 过期时间相关 ===================
/**
* 获取当前登录者的token剩余有效时间 (单位: 秒)
* 获取当前登录者的 token 剩余有效时间 (单位: 秒)
* @return token剩余有效时间
*/
public static long getTokenTimeout() {
@@ -318,7 +409,7 @@ public class StpUserUtil {
}
/**
* 获取当前登录者的Session剩余有效时间 (单位: 秒)
* 获取当前登录者的 User-Session 剩余有效时间 (单位: 秒)
* @return token剩余有效时间
*/
public static long getSessionTimeout() {
@@ -326,7 +417,7 @@ public class StpUserUtil {
}
/**
* 获取当前token的专属Session剩余有效时间 (单位: 秒)
* 获取当前 Token-Session 剩余有效时间 (单位: 秒)
* @return token剩余有效时间
*/
public static long getTokenSessionTimeout() {
@@ -334,8 +425,8 @@ public class StpUserUtil {
}
/**
* 获取当前token[临时过期]剩余有效时间 (单位: 秒)
* @return token[临时过期]剩余有效时间
* 获取当前 token [临时过期] 剩余有效时间 (单位: 秒)
* @return token [临时过期] 剩余有效时间
*/
public static long getTokenActivityTimeout() {
return stpLogic.getTokenActivityTimeout();
@@ -345,8 +436,34 @@ public class StpUserUtil {
// =================== 角色验证操作 ===================
/**
* 获取:当前账号的角色集合
* @return /
*/
public static List<String> getRoleList() {
return stpLogic.getRoleList();
}
/**
* 获取:指定账号的角色集合
* @param loginId 指定账号id
* @return /
*/
public static List<String> getRoleList(Object loginId) {
return stpLogic.getRoleList(loginId);
}
/**
* 指定账号id是否含有角色标识, 返回true或false
* 判断:当前账号是否拥有指定角色, 返回true或false
* @param role 角色标识
* @return 是否含有指定角色标识
*/
public static boolean hasRole(String role) {
return stpLogic.hasRole(role);
}
/**
* 判断:指定账号是否含有指定角色标识, 返回true或false
* @param loginId 账号id
* @param role 角色标识
* @return 是否含有指定角色标识
@@ -356,16 +473,25 @@ public class StpUserUtil {
}
/**
* 当前账号是否含有指定角色标识, 返回true或false
* @param role 角色标识
* @return 是否含有指定角色标识
* 判断:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
* @param roleArray 角色标识数组
* @return true或false
*/
public static boolean hasRole(String role) {
return stpLogic.hasRole(role);
public static boolean hasRoleAnd(String... roleArray){
return stpLogic.hasRoleAnd(roleArray);
}
/**
* 当前账号是否含有指定角色标识, 如果验证通过,则抛出异常: NotRoleException
* 判断:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
* @param roleArray 角色标识数组
* @return true或false
*/
public static boolean hasRoleOr(String... roleArray){
return stpLogic.hasRoleOr(roleArray);
}
/**
* 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
* @param role 角色标识
*/
public static void checkRole(String role) {
@@ -373,7 +499,7 @@ public class StpUserUtil {
}
/**
* 当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
* 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
* @param roleArray 角色标识数组
*/
public static void checkRoleAnd(String... roleArray){
@@ -381,18 +507,44 @@ public class StpUserUtil {
}
/**
* 当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
* 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
* @param roleArray 角色标识数组
*/
public static void checkRoleOr(String... roleArray){
stpLogic.checkRoleOr(roleArray);
}
// =================== 权限验证操作 ===================
/**
* 获取:当前账号的权限码集合
* @return /
*/
public static List<String> getPermissionList() {
return stpLogic.getPermissionList();
}
/**
* 获取:指定账号的权限码集合
* @param loginId 指定账号id
* @return /
*/
public static List<String> getPermissionList(Object loginId) {
return stpLogic.getPermissionList(loginId);
}
/**
* 指定账号id是否含有指定权限, 返回true或false
* 判断:当前账号是否含有指定权限, 返回true或false
* @param permission 权限码
* @return 是否含有指定权限
*/
public static boolean hasPermission(String permission) {
return stpLogic.hasPermission(permission);
}
/**
* 判断:指定账号id是否含有指定权限, 返回true或false
* @param loginId 账号id
* @param permission 权限码
* @return 是否含有指定权限
@@ -402,16 +554,25 @@ public class StpUserUtil {
}
/**
* 当前账号是否含有指定权限, 返回true或false
* @param permission 权限码
* @return 是否含有指定权限
* 判断:当前账号是否含有指定权限, [指定多个,必须全部具有]
* @param permissionArray 权限码数组
* @return true 或 false
*/
public static boolean hasPermission(String permission) {
return stpLogic.hasPermission(permission);
}
public static boolean hasPermissionAnd(String... permissionArray){
return stpLogic.hasPermissionAnd(permissionArray);
}
/**
* 当前账号是否含有指定权限, 如果验证通过,则抛出异常: NotPermissionException
* 判断:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
* @param permissionArray 权限码数组
* @return true 或 false
*/
public static boolean hasPermissionOr(String... permissionArray){
return stpLogic.hasPermissionOr(permissionArray);
}
/**
* 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
* @param permission 权限码
*/
public static void checkPermission(String permission) {
@@ -419,7 +580,7 @@ public class StpUserUtil {
}
/**
* 当前账号是否含有指定权限 [指定多个,必须全部验证通过]
* 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
* @param permissionArray 权限码数组
*/
public static void checkPermissionAnd(String... permissionArray) {
@@ -427,7 +588,7 @@ public class StpUserUtil {
}
/**
* 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
* 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
* @param permissionArray 权限码数组
*/
public static void checkPermissionOr(String... permissionArray) {
@@ -602,7 +763,7 @@ public class StpUserUtil {
/**
* 在当前会话 开启二级认证
* @param timeout 维持时间 (单位: 秒)
* @param safeTime 维持时间 (单位: 秒)
*/
public static void openSafe(long safeTime) {
stpLogic.openSafe(safeTime);
@@ -625,7 +786,7 @@ public class StpUserUtil {
/**
* 获取当前会话的二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证)
* @return
* @return 剩余有效时间
*/
public static long getSafeTime() {
return stpLogic.getSafeTime();
@@ -643,6 +804,7 @@ public class StpUserUtil {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.getLoginType() ,使用方式保持不变 </h1>
*
* 获取当前StpLogin的loginKey
* @return 当前StpLogin的loginKey
*/
@@ -653,6 +815,7 @@ public class StpUserUtil {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
*
* 在当前会话上登录id
* @param loginId 登录id,建议的类型:(long | int | String
*/
@@ -663,6 +826,7 @@ public class StpUserUtil {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
*
* 在当前会话上登录id, 并指定登录设备
* @param loginId 登录id,建议的类型:(long | int | String
* @param device 设备标识
@@ -674,6 +838,7 @@ public class StpUserUtil {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
*
* 在当前会话上登录id, 并指定登录设备
* @param loginId 登录id,建议的类型:(long | int | String
* @param isLastingCookie 是否为持久Cookie
@@ -685,6 +850,7 @@ public class StpUserUtil {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
*
* 在当前会话上登录id, 并指定所有登录参数Model
* @param loginId 登录id,建议的类型:(long | int | String
* @param loginModel 此次登录的参数Model
@@ -694,4 +860,29 @@ public class StpUserUtil {
stpLogic.login(loginId, loginModel);
}
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
*
* 会话注销,根据账号id (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
* @param loginId 账号id
*/
@Deprecated
public static void logoutByLoginId(Object loginId) {
stpLogic.kickout(loginId);
}
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
*
* 会话注销,根据账号id and 设备标识 (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 </p>
* @param loginId 账号id
* @param device 设备标识 (填null代表所有注销设备)
*/
@Deprecated
public static void logoutByLoginId(Object loginId, String device) {
stpLogic.kickout(loginId, device);
}
}
@@ -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.SaCheckBasic;
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 kong
*
*/
@RestController
@RequestMapping("/at/")
public class AtController {
// 登录认证,登录之后才可以进入方法 ---- http://localhost:8081/at/checkLogin
@SaCheckLogin
@RequestMapping("checkLogin")
public SaResult checkLogin() {
return SaResult.ok();
}
// 权限认证,具备user-add权限才可以进入方法 ---- http://localhost:8081/at/checkPermission
@SaCheckPermission("user-add")
@RequestMapping("checkPermission")
public SaResult checkPermission() {
return SaResult.ok();
}
// 权限认证,同时具备所有权限才可以进入 ---- http://localhost:8081/at/checkPermissionAnd
@SaCheckPermission({"user-add", "user-delete", "user-update"})
@RequestMapping("checkPermissionAnd")
public SaResult checkPermissionAnd() {
return SaResult.ok();
}
// 权限认证,只要具备其中一个就可以进入 ---- http://localhost:8081/at/checkPermissionOr
@SaCheckPermission(value = {"user-add", "user-delete", "user-update"}, mode = SaMode.OR)
@RequestMapping("checkPermissionOr")
public SaResult checkPermissionOr() {
return SaResult.ok();
}
// 角色认证,只有具备admin角色才可以进入 ---- http://localhost:8081/at/checkRole
@SaCheckRole("admin")
@RequestMapping("checkRole")
public SaResult checkRole() {
return SaResult.ok();
}
// 完成二级认证 ---- http://localhost:8081/at/openSafe
@RequestMapping("openSafe")
public SaResult openSafe() {
StpUtil.openSafe(200); // 打开二级认证,有效期为200秒
return SaResult.ok();
}
// 通过二级认证后才可以进入 ---- http://localhost:8081/at/checkSafe
@SaCheckSafe
@RequestMapping("checkSafe")
public SaResult checkSafe() {
return SaResult.ok();
}
// 通过Basic认证后才可以进入 ---- http://localhost:8081/at/checkBasic
@SaCheckBasic(account = "sa:123456")
@RequestMapping("checkBasic")
public SaResult checkBasic() {
return SaResult.ok();
}
}
@@ -1,105 +0,0 @@
package com.pj.test;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.exception.DisableLoginException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
/**
* 全局异常处理
*/
@ControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin")
public class GlobalException {
// 在当前类每个方法进入之前触发的操作
@ModelAttribute
public void get(HttpServletRequest request) throws IOException {
}
// 全局异常拦截(拦截项目中的所有异常)
@ResponseBody
@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.getCode());
} else if(e instanceof DisableLoginException) { // 如果是被封禁异常
DisableLoginException ee = (DisableLoginException) e;
aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封");
} else { // 普通异常, 输出:500 + 异常信息
aj = AjaxJson.getError(e.getMessage());
}
// 返回给前端
return aj;
// 输出到客户端
// response.setContentType("application/json; charset=utf-8"); // http说明,我要返回JSON对象
// response.getWriter().print(new ObjectMapper().writeValueAsString(aj));
}
// 全局异常拦截(拦截项目中的NotLoginException异常)
// @ExceptionHandler(NotLoginException.class)
// public AjaxJson handlerNotLoginException(NotLoginException nle, HttpServletRequest request, HttpServletResponse response)
// throws Exception {
//
// // 打印堆栈,以供调试
// nle.printStackTrace();
//
// // 判断场景值,定制化异常信息
// String message = "";
// if(nle.getType().equals(NotLoginException.NOT_TOKEN)) {
// message = "未提供token";
// }
// else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) {
// message = "token无效";
// }
// else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) {
// message = "token已过期";
// }
// else if(nle.getType().equals(NotLoginException.BE_REPLACED)) {
// message = "token已被顶下线";
// }
// else if(nle.getType().equals(NotLoginException.KICK_OUT)) {
// message = "token已被踢下线";
// }
// else {
// message = "当前会话未登录";
// }
//
// // 返回给前端
// return AjaxJson.getError(message);
// }
}
@@ -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 kong
*
*/
@RestController
@RequestMapping("/acc/")
public class LoginController {
// 测试登录 ---- http://localhost:8081/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:8081/acc/isLogin
@RequestMapping("isLogin")
public SaResult isLogin() {
return SaResult.ok("是否登录:" + StpUtil.isLogin());
}
// 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
@RequestMapping("tokenInfo")
public SaResult tokenInfo() {
return SaResult.data(StpUtil.getTokenInfo());
}
// 测试注销 ---- http://localhost:8081/acc/logout
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}
@@ -186,7 +186,7 @@ public class TestController {
// 先登录上
StpUtil.login(10001);
// 踢下线
StpUtil.logoutByLoginId(10001);
StpUtil.kickout(10001);
// 再尝试获取
StpUtil.getLoginId();
// 返回
@@ -235,12 +235,11 @@ public class TestController {
return AjaxJson.getSuccessData("登录成功");
}
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public AjaxJson test() {
System.out.println("进来了");
return AjaxJson.getSuccess();
System.out.println("------------进来了");
return AjaxJson.getSuccess();
}
// 测试 浏览器访问: http://localhost:8081/test/test2
@@ -249,5 +248,4 @@ public class TestController {
return AjaxJson.getSuccess();
}
}
@@ -1,34 +0,0 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
/**
* 登录测试
* @author kong
*
*/
@RestController
@RequestMapping("/user/")
public class UserController {
// 测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456
@RequestMapping("doLogin")
public String doLogin(String username, String password) {
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
if("zhang".equals(username) && "123456".equals(password)) {
StpUtil.login(10001);
return "登录成功";
}
return "登录失败";
}
// 查询登录状态,浏览器访问: http://localhost:8081/user/isLogin
@RequestMapping("isLogin")
public String isLogin(String username, String password) {
return "当前会话是否登录:" + StpUtil.isLogin();
}
}
@@ -18,7 +18,7 @@ sa-token:
token-style: uuid
# 是否输出操作日志
is-log: false
spring:
# redis配置
redis:
@@ -42,9 +42,9 @@ sa.ajax = function(url, data, successFn) {
// ----------------------------------- 相关事件 -----------------------------------
// 检查当前是否已经登录,如果已登录则直接开始跳转,如果未登录则等待用户输入账号密码
sa.ajax("/getRedirectUrl", {redirect: getParam('redirect', '')}, function(res) {
sa.ajax("/getRedirectUrl", {redirect: getParam('redirect', ''), mode: getParam('mode', '')}, function(res) {
if(res.code == 200) {
// redirect地址有效,开始跳转
// 已登录,并且redirect地址有效,开始跳转
location.href = decodeURIComponent(res.data);
} else if(res.code == 401) {
console.log('未登录');

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