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

Compare commits

..

145 Commits

Author SHA1 Message Date
shengzhang 95a133040b 更新日志 2021-02-09 01:20:05 +08:00
shengzhang 30b47573c4 v1.13.0更新 2021-02-09 01:17:52 +08:00
shengzhang b38091217a 完善文档,准备发版 2021-02-08 20:17:48 +08:00
shengzhang deed69f80d 完善集群分布式下的解决方案 2021-02-08 20:01:31 +08:00
shengzhang a11ad64d41 完成同域模式下的单点登录 2021-02-08 19:23:37 +08:00
省长 af0b22854b update README.md. 2021-02-07 22:16:30 +08:00
RockMan 0f582395cf !11 完善readme
Merge pull request !11 from RockMan/N/A
2021-02-07 12:02:27 +08:00
RockMan 64a0f4a6cd 晚上readme 2021-02-07 12:01:51 +08:00
RockMan 51f35403d5 update sa-token-doc/doc/fun/not-login-scene.md. 2021-02-07 10:35:22 +08:00
shengzhang be7d08639c 优化首页介绍 2021-02-06 20:35:49 +08:00
省长 bbadbc76d2 update README.md. 2021-02-06 20:34:28 +08:00
省长 7580b21810 update sa-token-doc/doc/use/dao-extend.md. 2021-02-06 16:14:08 +08:00
RockMan 9e7619e689 update sa-token-doc/doc/fun/not-login-scene.md. 2021-02-06 15:54:29 +08:00
RockMan a608f29ed1 update sa-token-doc/doc/fun/token-timeout.md. 2021-02-06 15:52:49 +08:00
RockMan 48307dcd4a update sa-token-doc/doc/more/link.md. 2021-02-06 15:44:40 +08:00
RockMan d4eb41dd6a update sa-token-doc/doc/use/config.md. 2021-02-06 15:34:23 +08:00
RockMan 59d3b985ec update sa-token-doc/doc/use/token-style.md. 2021-02-06 15:33:43 +08:00
RockMan 6e3f6103ca update sa-token-doc/doc/use/route-check.md. 2021-02-06 15:24:11 +08:00
RockMan ce9dd4f5e8 update sa-token-doc/doc/use/many-account.md. 2021-02-06 15:17:58 +08:00
RockMan 8aa1852870 update sa-token-doc/doc/use/not-cookie.md. 2021-02-06 14:31:41 +08:00
RockMan e329928177 update sa-token-doc/doc/use/dao-extend.md. 2021-02-06 14:06:26 +08:00
RockMan 8c58c489d7 update sa-token-doc/doc/use/kick.md. 2021-02-06 13:53:46 +08:00
RockMan 2d90aac488 update sa-token-doc/doc/use/session.md.
update sa-token-doc/doc/use/session.md.
2021-02-06 13:51:30 +08:00
RockMan b8f31da9a2 update sa-token-doc/doc/start/example.md.
update sa-token-doc/doc/start/example.md.
2021-02-06 13:42:42 +08:00
RockMan 285f02578a update README.md.
优化介绍
2021-02-06 12:16:00 +08:00
shengzhang 487e15b1f1 Maven版本号更改为变量形式 2021-02-04 23:03:02 +08:00
省长 643d82cde6 完善贡献者名单
完善贡献者名单
2021-02-04 22:10:27 +08:00
shengzhang 5dd02b3528 新增autoRenew配置,用于控制是否打开自动续签模式 2021-02-04 21:54:54 +08:00
省长 b211de69ca 完善readme.md
完善readme.md
2021-02-04 21:19:46 +08:00
shengzhang 25a40b3797 抽象到 2021-02-04 21:12:05 +08:00
shengzhang b18b8a07b5 异常统一继承SaTokenException、SaSession的创建抽象到接口 2021-02-04 21:10:45 +08:00
省长 10f0571ef2 update README.md.
修改群聊二维码尺寸
2021-02-04 12:17:53 +08:00
省长 b3544ed655 !10 update README.md.
Merge pull request !10 from ZhuBJ0510/N/A
2021-02-04 12:00:20 +08:00
ZhuBJ0510 a5a4da10b6 update README.md. 2021-02-04 11:45:15 +08:00
shengzhang 1678ee7475 完善文档 2021-02-04 00:11:11 +08:00
shengzhang f3742aeca7 完善readme介绍 2021-02-04 00:07:02 +08:00
省长 0989bf8bb7 更改拼写错误
更改拼写错误
2021-02-02 11:11:18 +08:00
RockMan 2052a11e9e update sa-token-doc/doc/use/mock-person.md.
fix: 文档示例错误
2021-02-01 11:30:21 +08:00
shengzhang 47e879ad5e 完善文档 2021-01-31 23:28:42 +08:00
shengzhang 40331c2e3f 修复文档权限认证处错误之处 2021-01-31 01:58:40 +08:00
shengzhang 26ee628b33 完善源码注释 2021-01-30 18:06:14 +08:00
shengzhang 03e38c549a 增加微信交流群二维码 2021-01-29 18:57:06 +08:00
省长 bf02d7a1e5 增加微信交流群二维码
增加微信交流群二维码
2021-01-29 18:33:36 +08:00
shengzhang a3ed0ec1fd 去除排名提示信息 2021-01-24 23:24:25 +08:00
shengzhang 8b0976f8c6 优化readme.md 2021-01-24 23:22:25 +08:00
shengzhang a068441829 优化文档 2021-01-24 22:23:17 +08:00
shengzhang 0d652e42b7 优化文档 2021-01-24 20:18:28 +08:00
省长 cb0fd8cdcf !9 style 优化 if else 结构
Merge pull request !9 from xiaoshitou/dev
2021-01-24 17:44:29 +08:00
xiaoshitou 43151743b8 style 优化 if else 结构
equals 调用时应该 常量值应该在前, 避免空指针异常
2021-01-23 23:51:40 +08:00
shengzhang c3303e4212 完善注释 2021-01-22 20:58:49 +08:00
shengzhang 575f8f38bc 优化文档样式 2021-01-21 18:09:50 +08:00
RockMan 56d197b90c 完善readme.md
完善readme.md
2021-01-21 17:34:25 +08:00
RockMan 4b38feb60b 新增文章
新增文章
2021-01-19 13:57:19 +08:00
shengzhang bbc2ccb860 优化readme 2021-01-17 17:31:40 +08:00
省长 99fd9ebf65 完善 README.md.
完善 README.md.
2021-01-17 17:22:17 +08:00
RockMan ab8667a0c4 添加博客链接
添加博客链接
2021-01-16 10:13:40 +08:00
RockMan 94ce6d0d73 update README.md.
新增知乎专栏链接
2021-01-16 10:03:11 +08:00
RockMan b3373214be update sa-token-doc/doc/use/mock-person.md.
修复描述错误
2021-01-14 14:55:49 +08:00
shengzhang 87292cb7d5 v1.12.1更新 2021-01-14 02:06:39 +08:00
shengzhang 3b9f6b71e9 集成Gitalk评论系统 2021-01-13 20:35:33 +08:00
省长 251ac4cf0f update README.md.
优化描述
2021-01-13 20:32:02 +08:00
RockMan 5f9193095e update sa-token-doc/doc/use/route-check.md.
完善新增特性文档
2021-01-13 09:42:12 +08:00
shengzhang d1d25f4d49 新增SaRouterUtil,可优雅的路由拦截式鉴权 2021-01-13 02:00:47 +08:00
RockMan 37bcee92ed update sa-token-doc/doc/use/route-check.md.
简化示例
2021-01-12 22:53:16 +08:00
RockMan 028e4e56f0 update sa-token-doc/doc/use/at-check.md.
写通顺点
2021-01-12 22:31:45 +08:00
RockMan d65469ba3e !8 将aop模式和注册拦截器模式说明位置互换
Merge pull request !8 from AppleOfGray/N/A
2021-01-12 22:00:27 +08:00
AppleOfGray bcb82395d5 将aop模式和注册拦截器模式说明位置互换 2021-01-12 19:16:17 +08:00
RockMan 88d1f47227 readme.md完善代码示例
readme.md完善代码示例
2021-01-12 11:49:35 +08:00
click33 866c0de19d Merge pull request #6 from auster9021/patch-1
Update StpLogic.java
2021-01-12 01:32:56 +08:00
shengzhang ba640295c5 v1.12.0 版本更新 2021-01-12 01:01:23 +08:00
shengzhang 1ce7b945d8 完善文档 2021-01-11 23:17:15 +08:00
shengzhang 4bfad95bad 新增身份临时切换功能 2021-01-11 23:10:11 +08:00
shengzhang c7f3b6d493 路由拦截器 增加参数 2021-01-11 21:18:55 +08:00
shengzhang c3bedaef99 v.1.12.0新特性:路由拦截式鉴权 2021-01-11 20:08:17 +08:00
Auster 983c16aa58 Update StpLogic.java 2021-01-11 17:26:02 +08:00
shengzhang ac4ac37175 取消判断同账号免重复登陆 2021-01-10 23:01:34 +08:00
shengzhang 713758c304 新增jwt测试 2021-01-10 18:39:32 +08:00
shengzhang 857e260a0a jwt集成示例 2021-01-10 18:36:35 +08:00
shengzhang f1503d93a0 优化文档首页介绍 2021-01-10 00:55:45 +08:00
shengzhang 1648422617 修正更新日期 2021-01-10 00:49:29 +08:00
shengzhang 600cc98e20 v1.11.0更新 2021-01-10 00:15:23 +08:00
shengzhang 2d3a262e2f 优化自动生成token的算法 2021-01-09 19:34:43 +08:00
shengzhang 6a677ca779 更改模块引用名称 2021-01-09 17:32:40 +08:00
shengzhang dc5bfb6d84 模块名称更换为spring-aop 2021-01-09 17:31:35 +08:00
shengzhang fbff086ed9 v1.11.0 新特性:AOP注解鉴权 2021-01-09 17:24:44 +08:00
shengzhang 1028ac0fe6 修复更新日期标注错误 2021-01-09 02:33:48 +08:00
shengzhang 4abb72bb65 更改文档 2021-01-09 02:23:20 +08:00
shengzhang 79cb976439 优化readme 2021-01-09 01:27:36 +08:00
shengzhang f26424537f v1.10.0 版本更新, 提供会话治理接口 2021-01-09 01:24:37 +08:00
shengzhang 2328f9d654 Merge branch 'dev' of https://gitee.com/sz6/sa-token 2021-01-07 22:05:00 +08:00
shengzhang 22826dac86 v1.10.0新特性:查询所有会话 2021-01-07 22:03:44 +08:00
省长 bbce343a01 !7 update sa-token-doc/doc/use/jur-auth.md.
Merge pull request !7 from AppleOfGray/N/A
2021-01-06 21:19:59 +08:00
AppleOfGray 8ed9166abf update sa-token-doc/doc/use/jur-auth.md. 2021-01-06 21:11:27 +08:00
shengzhang a1ec710efd Merge branch 'master' of https://gitee.com/sz6/sa-token 2021-01-06 18:21:03 +08:00
shengzhang ae9ba2d1fd 优化readme.md 2021-01-06 18:15:32 +08:00
省长 97a5fb2f40 !6 v1.9.0 版本更新
Merge pull request !6 from 省长/dev
2021-01-06 02:43:35 +08:00
shengzhang 128ab7614e 更换文档标题 2021-01-06 02:42:15 +08:00
shengzhang 7f0a3aa1c6 修改文档cdn 2021-01-06 00:48:47 +08:00
shengzhang 69d01e3e6e v1.9.0 版本更新 2021-01-05 18:04:14 +08:00
shengzhang 82ee90f712 同端互斥登录的文档 2021-01-05 16:07:21 +08:00
shengzhang 9733c8777a v1.9.0 新特性 同端互斥登录 2021-01-05 13:58:51 +08:00
shengzhang 4ff6a87ef5 修复权限认证处示例错误 2021-01-05 00:47:17 +08:00
shengzhang 7ffe6cb0e6 补上注解拦截器里漏掉验证@SaCheckRole的bug 2021-01-04 21:42:32 +08:00
shengzhang dbba90d846 spring-boot-starter-data-redis2.3.7.RELEASE 改为 2.3.3.RELEASE 2021-01-04 18:12:46 +08:00
shengzhang 064ef4f12c 优化readme 2021-01-03 19:24:09 +08:00
省长 a8688cc07f !5 update sa-token-doc/doc/use/jur-auth.md.
Merge pull request !5 from legg/N/A
2021-01-02 22:36:48 +08:00
legg 4678e34203 update sa-token-doc/doc/use/jur-auth.md. 2021-01-02 21:55:06 +08:00
shengzhang 81c0200981 修复更新日期标注错误 2021-01-02 04:10:43 +08:00
shengzhang 2c6e656834 v1.8.0更新 2021-01-02 04:00:49 +08:00
shengzhang 5ec35cce28 UPDATE LICENSE 2021-01-01 03:22:38 +08:00
shengzhang 453d83cea4 update license 2021-01-01 03:22:06 +08:00
省长 0091fbad09 update README.md. 2020-12-28 22:17:49 +08:00
省长 13760ccf4d update README.md.
优化readme
2020-12-28 22:14:47 +08:00
shengzhang d9e2d98390 调整示例 2020-12-28 02:05:08 +08:00
shengzhang 47140cea07 新增角色验证与权限验证完全分离 2020-12-28 02:00:32 +08:00
shengzhang 43308bf593 权限码限定必须为String类型 2020-12-28 01:14:34 +08:00
shengzhang d3792ad286 在无token时自动创建会话,完美兼容token-session会话模型! 2020-12-28 00:40:35 +08:00
shengzhang 930c28df6d 增加配置,指定在获取token专属session时是否必须登录 2020-12-27 23:45:47 +08:00
shengzhang a59ee9408b 添加token专有session 2020-12-27 22:47:00 +08:00
shengzhang 20f6bd7b3d dao层默认实现增加定时清理过期数据功能 2020-12-27 18:18:08 +08:00
shengzhang 67abf576f0 优化文档配色,更舒服的代码展示 2020-12-27 12:04:14 +08:00
shengzhang 72252ab6b4 简写项目目录结构 2020-12-27 00:12:48 +08:00
shengzhang 60e2afe76f 提供sa-token集成redis的starter方案 2020-12-26 23:55:46 +08:00
省长 62bd2febd0 !4 update sa-token-doc/doc/use/token-style.md.
Merge pull request !4 from 离你多远/N/A
2020-12-26 21:06:28 +08:00
离你多远 434049c4a3 update sa-token-doc/doc/use/token-style.md. 2020-12-26 21:04:33 +08:00
shengzhang 6277a1841b 完善注释 2020-12-26 20:54:15 +08:00
shengzhang 97ad4a783b 修改项目文件夹名字 2020-12-26 10:43:23 +08:00
省长 a503828e23 !3 update sa-token-doc/doc/use/token-style.md.
Merge pull request !3 from RockMan/N/A
2020-12-25 12:15:08 +08:00
RockMan 52abba6a98 update sa-token-doc/doc/use/token-style.md. 2020-12-25 12:11:28 +08:00
shengzhang ebe6b0917b 优化readme 2020-12-25 01:20:21 +08:00
shengzhang 4b1370e603 增加v1.6文档连接 2020-12-25 00:30:24 +08:00
shengzhang 9ddc1eb7d0 完善示例 2020-12-25 00:21:02 +08:00
shengzhang 34f1e1f2db 完善v1.7.0文档 2020-12-25 00:16:12 +08:00
shengzhang d9836f00ca v1.7.0 版本更新 2020-12-24 22:51:57 +08:00
shengzhang 5c684ac7f1 v1.7.0新特性,token临时过期时间设定 2020-12-24 18:11:12 +08:00
shengzhang ded6da5554 v1.7.0 新特性,在Cookie模式下timeout过期时间有效 2020-12-23 18:49:22 +08:00
shengzhang 149b2e54c9 ADD LICENSE 2020-12-22 01:22:32 +08:00
shengzhang aad953c6eb 重构为maven多模块架构... 2020-12-22 01:14:17 +08:00
shengzhang 4f22be308e 重构为maven多模块架构 2020-12-22 01:11:28 +08:00
shengzhang 4962742988 v1.6.0 版本更新... 2020-12-18 00:57:54 +08:00
shengzhang 1d9ed55438 v1.6.0 版本更新 2020-12-18 00:42:38 +08:00
shengzhang 4db058cdf0 v1.6.0 版本更新 2020-12-18 00:39:14 +08:00
shengzhang d776501c3d v1.6.0新特性:提供花式token生成方案.. 2020-12-17 02:41:30 +08:00
shengzhang 235e9afcd3 v1.6.0新特性:提供花式token生成方案 2020-12-17 02:19:56 +08:00
shengzhang 983af6691e 修改readme.md 2020-12-17 02:04:34 +08:00
139 changed files with 8867 additions and 3095 deletions
+13
View File
@@ -0,0 +1,13 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.factorypath
/.factorypath
.idea/
+197 -17
View File
@@ -1,21 +1,201 @@
MIT License
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
Copyright (c) 2020 www.uviewui.com
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1. Definitions.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2011-2019 hubin.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+174 -52
View File
@@ -1,11 +1,11 @@
<p align="center">
<img alt="logo" src="https://gitee.com/sz6/sa-token/raw/master/sa-token-doc/doc/logo.png" width="150" height="150" style="margin-bottom: 10px;">
<img alt="logo" src="https://gitee.com/sz6/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.5.1</h1>
<h4 align="center">一个JavaWeb轻量级权限认证框架,功能全面,上手简单</h4>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sa-token v1.13.0</h1>
<h4 align="center">这可能是史上功能最全的Java权限认证框架!</h4>
<h4 align="center">
<a href="https://gitee.com/sz6/sa-token/stargazers"><img src="https://gitee.com/sz6/sa-token/badge/star.svg"></a>
<a href="https://github.com/click33/sa-token"><img src="https://img.shields.io/badge/sa--token-v1.5.1-2B9939"></a>
<a href="https://github.com/click33/sa-token"><img src="https://img.shields.io/badge/sa--token-v1.13.0-2B9939"></a>
<a href="https://github.com/click33/sa-token/stargazers"><img src="https://img.shields.io/github/stars/click33/sa-token"></a>
<a href="https://github.com/click33/sa-token/watchers"><img src="https://img.shields.io/github/watchers/click33/sa-token"></a>
<a href="https://github.com/click33/sa-token/network/members"><img src="https://img.shields.io/github/forks/click33/sa-token"></a>
@@ -16,76 +16,198 @@
---
## 😘 在线资料
- ##### ⚡&nbsp;[官网首页:http://sa-token.dev33.cn/](http://sa-token.dev33.cn/)
- ##### ⚡&nbsp;[在线文档http://sa-token.dev33.cn/doc/index.html](http://sa-token.dev33.cn/doc/index.html)
- ##### ⚡&nbsp;[需求提交:我们深知一个优秀的项目需要海纳百川,点我在线提交需求](http://sa-app.dev33.cn/wall.html?name=sa-token)
- ##### ⚡&nbsp;[开源不易,求鼓励,点个star吧](https://github.com/click33/sa-token)
## 在线资料
- [官网首页http://sa-token.dev33.cn/](http://sa-token.dev33.cn/)
- [在线文档:http://sa-token.dev33.cn/doc/index.html](http://sa-token.dev33.cn/doc/index.html)
- [需求提交:我们深知一个优秀的项目需要海纳百川,点我在线提交需求](http://sa-app.dev33.cn/wall.html?name=sa-token)
- [开源不易,求鼓励,点个star吧](###)
## Sa-Token是什么?
sa-token是一个轻量级Java权限认证框架,主要解决:登录认证、权限认证、Session会话、单点登录 等一系列权限相关问题
近年来,有关权限认证的解决方案层出不穷,例如单点登录、OAuth2.0、分布式Session等等难题,无一不有着各种优秀框架大行其道
然而当我们把视线放低,那些最基础的有如:登录认证、权限认证、Session会话等基础问题却一直被两大上古神兽 `Apache Shiro``Spring Security` 所把持
在此并非专门diss此两大框架,诚然两大框架背景强大,历史悠久,其生态也比较齐全。但是它们毕竟已经是十几年前的产物,那是一个还在用 `jsp` 写页面的时代,两大框架的很多功能都是为jsp那一套量身定做。
在前后台分离已成标配的今天,两大框架的很多设计理念已经比较滞后,已经不能和我们的项目进行无缝适配,很多功能点都需要进行二次封装,甚至找一大堆扩展插件才能集成,已经逐渐不太适合现代化项目的应用
所以,为什么不能有一个自底向上,从最基础的登录、权限做起,以业务需求为核心,做到开箱即用的轻量级权限认证框架?
秉承着这个目的,`sa-token` 诞生了!
## Sa-Token 优点?
sa-token架构设计精简,不引入各种复杂的概念,如丝般顺滑的API调用,大量高级特性统统只需一行代码即可实现
- 在sa-token之前,权限认证业务概念纷飞、代码复杂,在sa-token之后,权限认证将会变成:逻辑清晰,架构简单,人人可写
- 在sa-token之前,你会在百度上频繁搜索: xx框架如何集成Redis?前后台分离下如何鉴权?踢人下线的原理是什么?在sa-token之后,你将轻松知道这一切的答案
- 在sa-token之前,权限认证、分布式会话、单点登录、多账号认证,你需要找不同的框架,在sa-token之后,你只需要这一个框架就足够了
与其它权限认证框架相比,你将会从以下方面感受到 `sa-token` 的优势:
1. **简单** :可零配置启动框架,真正的开箱即用,低成本上手
2. **强大** :目前已集成几十项权限相关特性,涵盖了大部分业务场景的解决方案
3. **易用** :如丝般顺滑的API调用,大量高级特性统统只需一行代码即可实现
4. **高扩展** :几乎所有组件都提供了扩展接口,90%以上的逻辑都可以按需重写
有了sa-token,你所有的权限认证问题,都不再是问题!
## Sa-Token 能做什么?
- **登录验证** —— 轻松登录鉴权,并提供五种细分场景值
- **权限验证** —— 适配RBAC权限模型,不同角色不同授权
- **Session会话** —— 专业的数据缓存中心
- **踢人下线** —— 将违规用户立刻清退下线
- **持久层扩展** —— 可集成Redis、Memcached等专业缓存中间件,重启数据不丢失
- **分布式会话** —— 提供jwt集成和共享数据中心两种分布式会话方案
- **单点登录** —— 一处登录,处处通行
- **模拟他人账号** —— 实时操作任意用户状态数据
- **临时身份切换** —— 将会话身份临时切换为其它账号
- **无Cookie模式** —— APP、小程序等前后台分离场景
- **同端互斥登录** —— 像QQ一样手机电脑同时在线,但是两个手机上互斥登录
- **多账号认证体系** —— 比如一个商城项目的user表和admin表分开鉴权
- **花式token生成** —— 内置六种token风格,还可自定义token生成策略
- **注解式鉴权** —— 优雅的将鉴权与业务代码分离
- **路由拦截式鉴权** —— 设定全局路由拦截,并排除指定路由
- **自动续签** —— 提供两种token过期策略,灵活搭配使用,还可自动续签
- **会话治理** —— 提供方便灵活的会话查询接口
- **组件自动注入** —— 零配置与Spring等框架集成
- **更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流
## 代码示例
sa-token的API调用非常简单,有多简单呢?以登录验证为例,你只需要:
## ⭐ sa-token是什么?
- **sa-token是一个JavaWeb轻量级权限认证框架,其API调用非常简单,有多简单呢?以登录验证为例,你只需要:**
``` java
// 在登录时写入当前会话的账号id
// 在登录时写入当前会话的账号id
StpUtil.setLoginId(10001);
```
- **然后在任意需要验证登录权限的地方:**
``` java
// 检测是否登录 --- 如果当前会话未登录,下面这句代码会抛出 `NotLoginException`异常
// 然后在任意需要校验登录处调用以下API
// 如果当前会话未登录,这句代码会抛出 `NotLoginException`异常
StpUtil.checkLogin();
```
至此,我们已经借助sa-token框架完成登录授权!
此时的你小脑袋可能飘满了问号,就这么简单?自定义Realm呢?全局过滤器呢?我不用写各种配置文件吗?
事实上在此我可以负责的告诉你,在sa-token中,登录授权就是如此的简单,不需要什么全局过滤器,不需要各种乱七八糟的配置!只需要这一行简单的API调用,即可完成会话的登录授权!
当你受够Shiro、Security等框架的三拜九叩之后,你就会明白,相对于这些传统老牌框架,sa-token的API设计是多么的清爽!
权限认证示例 (只有具有`user:add`权限的会话才可以进入请求)
``` java
@SaCheckPermission("user:add")
@RequestMapping("/user/insert")
public String insert(SysUser user) {
return "用户增加";
}
```
将某个账号踢下线 (待到对方再次访问系统时会抛出`NotLoginException`异常)
``` java
// 使账号id为10001的会话注销登录
StpUtil.logoutByLoginId(10001);
```
除了以上的示例,sa-token还可以一行代码完成以下功能:
``` java
StpUtil.setLoginId(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.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值
StpUtil.setLoginId(10001, "PC"); // 指定设备标识登录
StpUtil.logoutByLoginId(10001, "PC"); // 指定设备标识进行强制注销 (不同端不受影响)
StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
```
sa-token的API众多,请恕此处无法为您逐一展示,更多示例请戳官方在线文档
- **没有复杂的封装!不要任何的配置!先写入,后鉴权!只需这两行简单的调用,即可轻松完成系统登录鉴权!**
## 迭代模式
sa-token的功能提案主要来源于社区,这意味着人人都可以参与到sa-token的功能定制,决定框架的未来走向,
如果你有好的想法,可以在issues提出或者加入群一起交流,对于社区的提出的功能要求,主要分为以下几类:
- 对框架新增特性功能且比较简单,会在第一时间进行开发
- 对框架新增特性功能但比较复杂,会延后几个版本制定相应的计划后进行开发
- 与框架设计理念不太相符,或超出权限认证范畴,将会视需求人数决定是否开发
## 🔥 框架设计思想
与其它权限认证框架相比,`sa-token`尽力保证两点:
- 上手简单:能自动化的配置全部自动化,不让你费脑子
- 功能强大:能涵盖的功能全部涵盖,不让你用个框架还要自己给框架打各种补丁
## 参与贡献
众人拾柴火焰高,万丈高楼众人起!
sa-token秉承着开放的思想,欢迎大家贡献代码,为框架添砖加瓦,对框架有卓越贡献者将会出现在贡献者名单里
## 💦️ 涵盖功能
-**登录验证** —— 轻松登录鉴权,并提供五种细分场景值
-**权限验证** —— 拦截违规调用,不同角色不同授权
-**自定义session会话** —— 专业的数据缓存中心
-**踢人下线** —— 将违规用户立刻清退下线
-**模拟他人账号** —— 实时操作任意用户状态数据
-**持久层扩展** —— 可集成redis、MongoDB等专业缓存中间件
-**多账号认证体系** —— 比如一个商城项目的user表和admin表分开鉴权
-**无cookie模式** —— APP、小程序等前后台分离场景
-**注解式鉴权** —— 优雅的将鉴权与业务代码分离
-**组件自动注入** —— 零配置与Spring等框架集成
-**更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流
## 🔨 贡献代码
sa-token欢迎大家贡献代码,为框架添砖加瓦
1. 在github上fork一份到自己的仓库
1. 在gitee或者github上fork一份代码到自己的仓库
2. clone自己的仓库到本地电脑
3. 在本地电脑修改、commit、push
4. 提交pr(点击:New Pull Request
5. 等待合并
## 🌱 建议贡献的地方
- 修复源码现有bug,或增加新的实用功能
- 完善在线文档,或者修复现有错误之处
- 更多demo示例:比如SSM版搭建步骤
- 您可以参考项目issues与需求墙进行贡献
- 如果更新实用功能,可在文档友情链接处留下自己的推广链接
作者寄语:参与贡献不光只有提交代码一个选择,点一个star、提一个issues都是对开源项目的促进,
如果框架帮助到了你,欢迎你把框架推荐给你的朋友、同事使用,为sa-token的推广做一份贡献
## 🚀 友情链接
[**[ okhttps ]** 一个轻量级http通信框架,支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps)
## 建议贡献的地方
目前框架的主要有以下部分需要大家一起参与贡献:
- 核心代码:该部分需要开发者了解整个框架的架构,遵循已有代码规范进行bug修复或提交新功能
- 文档部分:需要以清晰明了的语句书写文档,力求简单易读,授人以鱼同时更授人以渔
- 社区建设:如果框架帮助到了您,希望您可以加入qq群参与交流,对不熟悉框架的新人进行排难解惑
- 框架推广:一个优秀的开源项目不能仅靠闭门造车,它还需要一定的推广方案让更多的人一起参与到项目中
- 其它部分:您可以参考项目issues与需求墙进行贡献
## 😎 交流群
QQ交流群:[1002350610 点击加入](https://jq.qq.com/?_wv=1027&k=45H977HM) ,欢迎你的加入
## 贡献者名单
[省长](https://gitee.com/sz6)、
[RockMan](https://gitee.com/njx33)、
[click33](https://github.com/click33)、
[AppleOfGray](https://gitee.com/appleOfGray)、
[Auster](https://github.com/auster9021)、
[ZhuBJ0510](https://gitee.com/zhubj0510)、
[legg](https://gitee.com/legg321)、
[xiaoshitou](https://gitee.com/smallstoneZ)、
[zhangjiaxiaozhuo](https://gitee.com/zhangjiaxiaozhuo)、
[离你多远](https://gitee.com/liniduoyuan)
## 知乎专栏
- [初识sa-token,一行代码搞定登录授权!](https://zhuanlan.zhihu.com/p/344106099)
- [一个登录功能也能玩出这么多花样?sa-token带你轻松搞定多地登录、单地登录、同端互斥登录](https://zhuanlan.zhihu.com/p/344511415)
- [浅谈踢人下线的设计思路!(附代码实现方案)](https://zhuanlan.zhihu.com/p/345844002)
- 文章已在 [csdn](https://blog.csdn.net/shengzhang_/article/details/112593247)、
[掘金](https://juejin.cn/post/6917250126650015751)、
[开源中国](https://my.oschina.net/u/3503445/blog/4897816)、
[博客园](https://www.cnblogs.com/shengzhang/p/14275558.html)、
[知乎](https://zhuanlan.zhihu.com/p/344106099)
等平台连载中...欢迎投稿
## 使用sa-token的开源项目
[**[ sa-plus]** 一个基于springboot架构的快速开发框架,内置代码生成器](https://gitee.com/sz6/sa-plus)
如果您的项目使用了sa-token,欢迎提交pr
## 友情链接
[**[ okhttps ]** 一个轻量级http通信框架,API设计无比优雅,支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps)
## 交流群
QQ交流群:[1002350610 点击加入](https://jq.qq.com/?_wv=1027&k=45H977HM)
![扫码加群](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/qq-group.png ':size=150')
**微信群**
![微信群](https://images.gitee.com/uploads/images/2021/0204/121632_63ee1287_1766140.png "sa-token-wx.jpg")
<br>
+120
View File
@@ -0,0 +1,120 @@
<?xml version='1.0' encoding='utf-8'?>
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 基础信息 -->
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<packaging>pom</packaging>
<version>1.13.0</version>
<!-- 项目介绍 -->
<name>sa-token</name>
<description>A Java Web lightweight authority authentication framework, comprehensive function, easy to use</description>
<url>https://github.com/click33/sa-token</url>
<!-- 所有模块 -->
<modules>
<module>sa-token-core</module>
<module>sa-token-spring-boot-starter</module>
<module>sa-token-dao-redis</module>
<module>sa-token-dao-redis-jackson</module>
<module>sa-token-spring-aop</module>
</modules>
<!-- 开源协议 apache 2.0 -->
<licenses>
<license>
<name>Apache 2</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
<comments>A business-friendly OSS license</comments>
</license>
</licenses>
<!-- 一些属性 -->
<properties>
<sa-token-version>1.13.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>
</properties>
<!-- 仓库信息 -->
<scm>
<tag>master</tag>
<url>https://github.com/click33/sa-token.git</url>
<connection>scm:git:https://github.com/click33/sa-token.git</connection>
<developerConnection>scm:git:https://github.com/click33/sa-token.git</developerConnection>
</scm>
<!-- 作者信息 -->
<developers>
<developer>
<name>shengzhang</name>
<email>2393584716@qq.com</email>
</developer>
</developers>
<!-- 父仓库 -->
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
<relativePath/>
</parent>
<!-- 仓库依赖 -->
<dependencies>
</dependencies>
<!-- 项目构建 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<versionRange>[1.0.0,)</versionRange>
<goals>
<goal>enforce</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore />
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
+28
View File
@@ -0,0 +1,28 @@
<?xml version='1.0' encoding='utf-8'?>
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.13.0</version>
</parent>
<packaging>jar</packaging>
<name>sa-token-core</name>
<artifactId>sa-token-core</artifactId>
<description>A Java Web lightweight authority authentication framework, comprehensive function, easy to use</description>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,149 @@
package cn.dev33.satoken;
import cn.dev33.satoken.action.SaTokenAction;
import cn.dev33.satoken.action.SaTokenActionDefaultImpl;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.config.SaTokenConfigFactory;
import cn.dev33.satoken.cookie.SaTokenCookie;
import cn.dev33.satoken.cookie.SaTokenCookieDefaultImpl;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.SaTokenDaoDefaultImpl;
import cn.dev33.satoken.servlet.SaTokenServlet;
import cn.dev33.satoken.servlet.SaTokenServletDefaultImpl;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
* 管理sa-token所有接口对象
* @author kong
*
*/
public class SaTokenManager {
/**
* 配置文件 Bean
*/
private static SaTokenConfig config;
public static SaTokenConfig getConfig() {
if (config == null) {
initConfig();
}
return config;
}
public static void setConfig(SaTokenConfig config) {
SaTokenManager.config = config;
if(config.getIsV()) {
SaTokenInsideUtil.printSaToken();
}
}
public synchronized static void initConfig() {
if (config == null) {
setConfig(SaTokenConfigFactory.createConfig());
}
}
/**
* 持久化 Bean
*/
public static SaTokenDao saTokenDao;
public static SaTokenDao getSaTokenDao() {
if (saTokenDao == null) {
initSaTokenDao();
}
return saTokenDao;
}
public static void setSaTokenDao(SaTokenDao saTokenDao) {
if(SaTokenManager.saTokenDao != null && (SaTokenManager.saTokenDao instanceof SaTokenDaoDefaultImpl)) {
((SaTokenDaoDefaultImpl)SaTokenManager.saTokenDao).endRefreshTimer();
}
SaTokenManager.saTokenDao = saTokenDao;
}
public synchronized static void initSaTokenDao() {
if (saTokenDao == null) {
setSaTokenDao(new SaTokenDaoDefaultImpl());
}
}
/**
* 权限认证 Bean
*/
public static StpInterface stpInterface;
public static StpInterface getStpInterface() {
if (stpInterface == null) {
initStpInterface();
}
return stpInterface;
}
public static void setStpInterface(StpInterface stpInterface) {
SaTokenManager.stpInterface = stpInterface;
}
public synchronized static void initStpInterface() {
if (stpInterface == null) {
setStpInterface(new StpInterfaceDefaultImpl());
}
}
/**
* 框架行为 Bean
*/
public static SaTokenAction saTokenAction;
public static SaTokenAction getSaTokenAction() {
if (saTokenAction == null) {
initSaTokenAction();
}
return saTokenAction;
}
public static void setSaTokenAction(SaTokenAction saTokenAction) {
SaTokenManager.saTokenAction = saTokenAction;
}
public synchronized static void initSaTokenAction() {
if (saTokenAction == null) {
setSaTokenAction(new SaTokenActionDefaultImpl());
}
}
/**
* Cookie操作 Bean
*/
public static SaTokenCookie saTokenCookie;
public static SaTokenCookie getSaTokenCookie() {
if (saTokenCookie == null) {
initSaTokenCookie();
}
return saTokenCookie;
}
public static void setSaTokenCookie(SaTokenCookie saTokenCookie) {
SaTokenManager.saTokenCookie = saTokenCookie;
}
public synchronized static void initSaTokenCookie() {
if (saTokenCookie == null) {
setSaTokenCookie(new SaTokenCookieDefaultImpl());
}
}
/**
* Servlet操作 Bean
*/
public static SaTokenServlet saTokenServlet;
public static SaTokenServlet getSaTokenServlet() {
if (saTokenServlet == null) {
initSaTokenServlet();
}
return saTokenServlet;
}
public static void setSaTokenServlet(SaTokenServlet saTokenServlet) {
SaTokenManager.saTokenServlet = saTokenServlet;
}
public synchronized static void initSaTokenServlet() {
if (saTokenServlet == null) {
setSaTokenServlet(new SaTokenServletDefaultImpl());
}
}
}
@@ -0,0 +1,28 @@
package cn.dev33.satoken.action;
import cn.dev33.satoken.session.SaSession;
/**
* sa-token逻辑代理接口
* <p>此接口将会代理框架内部的一些关键性逻辑,方便开发者进行按需重写</p>
* @author kong
*
*/
public interface SaTokenAction {
/**
* 根据一定的算法生成一个token
* @param loginId 账号id
* @param loginKey 账号体系key
* @return 一个token
*/
public String createToken(Object loginId, String loginKey);
/**
* 根据 SessionId 创建一个 Session
* @param sessionId Session的Id
* @return 创建后的Session
*/
public SaSession createSession(String sessionId);
}
@@ -0,0 +1,62 @@
package cn.dev33.satoken.action;
import java.util.UUID;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.util.SaTokenConsts;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
* 对 SaTokenAction 接口的默认实现
* @author kong
*
*/
public class SaTokenActionDefaultImpl implements SaTokenAction {
/**
* 生成一个token
*/
@Override
public String createToken(Object loginId, String loginKey) {
// 根据配置的tokenStyle生成不同风格的token
String tokenStyle = SaTokenManager.getConfig().getTokenStyle();
// uuid
if(SaTokenConsts.TOKEN_STYLE_UUID.equals(tokenStyle)) {
return UUID.randomUUID().toString();
}
// 简单uuid (不带下划线)
if(SaTokenConsts.TOKEN_STYLE_SIMPLE_UUID.equals(tokenStyle)) {
return UUID.randomUUID().toString().replaceAll("-", "");
}
// 32位随机字符串
if(SaTokenConsts.TOKEN_STYLE_RANDOM_32.equals(tokenStyle)) {
return SaTokenInsideUtil.getRandomString(32);
}
// 64位随机字符串
if(SaTokenConsts.TOKEN_STYLE_RANDOM_64.equals(tokenStyle)) {
return SaTokenInsideUtil.getRandomString(64);
}
// 128位随机字符串
if(SaTokenConsts.TOKEN_STYLE_RANDOM_128.equals(tokenStyle)) {
return SaTokenInsideUtil.getRandomString(128);
}
// tik风格 (2_14_16)
if(SaTokenConsts.TOKEN_STYLE_RANDOM_TIK.equals(tokenStyle)) {
return SaTokenInsideUtil.getRandomString(2) + "_" + SaTokenInsideUtil.getRandomString(14) + "_" + SaTokenInsideUtil.getRandomString(16) + "__";
}
// 默认,还是uuid
return UUID.randomUUID().toString();
}
/**
* 根据 SessionId 创建一个 Session
*/
@Override
public SaSession createSession(String sessionId) {
return new SaSession(sessionId);
}
}
@@ -6,7 +6,8 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 标注一个路由方法当前会话必须已登录才能通过
* 登录校验标注一个方法当前会话必须已登录才能进入该方法
* <p> 可标注在类上其效果等同于标注在此类的所有方法上
* @author kong
*
*/
@@ -0,0 +1,30 @@
package cn.dev33.satoken.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限校验:标注在一个方法上,当前会话必须具有指定权限才能进入该方法
* <p> 可标注在类上,其效果等同于标注在此类的所有方法上
* @author kong
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface SaCheckPermission {
/**
* 需要校验的权限码
* @return 需要校验的权限码
*/
String [] value() default {};
/**
* 验证模式:AND | OR,默认AND
* @return 验证模式
*/
SaMode mode() default SaMode.AND;
}
@@ -0,0 +1,30 @@
package cn.dev33.satoken.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 角色校验:标注在一个方法上,当前会话必须具有指定角色标识才能进入该方法
* <p> 可标注在类上,其效果等同于标注在此类的所有方法上
* @author kong
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface SaCheckRole {
/**
* 需要校验的角色标识
* @return 需要校验的角色标识
*/
String [] value() default {};
/**
* 验证模式:AND | OR,默认AND
* @return 验证模式
*/
SaMode mode() default SaMode.AND;
}
@@ -0,0 +1,20 @@
package cn.dev33.satoken.annotation;
/**
* 注解鉴权的验证模式
* @author kong
*
*/
public enum SaMode {
/**
* 必须具有所有的选项
*/
AND,
/**
* 只需具有其中一个选项
*/
OR
}
@@ -0,0 +1,277 @@
package cn.dev33.satoken.config;
/**
* sa-token 配置类 Model
* <p>
* 你可以通过yml、properties、java代码等形式配置本类参数,具体请查阅官方文档: http://sa-token.dev33.cn/
*
* @author kong
*
*/
public class SaTokenConfig {
/** token名称 (同时也是cookie名称) */
private String tokenName = "satoken";
/** token的长久有效期(单位:秒) 默认30天, -1代表永久 */
private long timeout = 30 * 24 * 60 * 60;
/**
* token临时有效期 [指定时间内无操作就视为token过期] (单位: 秒), 默认-1 代表不限制
* (例如可以设置为1800代表30分钟内无操作就过期)
*/
private long activityTimeout = -1;
/** 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) */
private Boolean allowConcurrentLogin = true;
/** 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) */
private Boolean isShare = true;
/** 是否尝试从请求体里读取token */
private Boolean isReadBody = true;
/** 是否尝试从header里读取token */
private Boolean isReadHead = true;
/** 是否尝试从cookie里读取token */
private Boolean isReadCookie = true;
/** token风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik) */
private String tokenStyle = "uuid";
/** 默认dao层实现类中,每次清理过期数据间隔的时间 (单位: 秒) ,默认值30秒,设置为-1代表不启动定时清理 */
private int dataRefreshPeriod = 30;
/** 获取[token专属session]时是否必须登录 (如果配置为true,会在每次获取[token-session]时校验是否登录) */
private Boolean tokenSessionCheckLogin = true;
/** 是否打开自动续签 (如果此值为true, 框架会在每次直接或间接调用getLoginId()时进行一次过期检查与续签操作) */
private Boolean autoRenew = true;
/** 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景 */
private String cookieDomain;
/** 是否在初始化配置时打印版本字符画 */
private Boolean isV = true;
/**
* @return token名称 (同时也是cookie名称)
*/
public String getTokenName() {
return tokenName;
}
/**
* @param tokenName token名称 (同时也是cookie名称)
*/
public void setTokenName(String tokenName) {
this.tokenName = tokenName;
}
/**
* @return token的长久有效期(单位:秒) 默认30天, -1代表永久
*/
public long getTimeout() {
return timeout;
}
/**
* @param timeout token的长久有效期(单位:秒) 默认30天, -1代表永久
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* @return token临时有效期 [指定时间内无操作就视为token过期] (单位: 秒), 默认-1 代表不限制
* (例如可以设置为1800代表30分钟内无操作就过期)
*/
public long getActivityTimeout() {
return activityTimeout;
}
/**
* @param activityTimeout token临时有效期 [指定时间内无操作就视为token过期] (单位: 秒), 默认-1 代表不限制
* (例如可以设置为1800代表30分钟内无操作就过期)
*/
public void setActivityTimeout(long activityTimeout) {
this.activityTimeout = activityTimeout;
}
/**
* @return 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
*/
public Boolean getAllowConcurrentLogin() {
return allowConcurrentLogin;
}
/**
* @param allowConcurrentLogin 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
*/
public void setAllowConcurrentLogin(Boolean allowConcurrentLogin) {
this.allowConcurrentLogin = allowConcurrentLogin;
}
/**
* @return 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
*/
public Boolean getIsShare() {
return isShare;
}
/**
* @param isShare 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
*/
public void setIsShare(Boolean isShare) {
this.isShare = isShare;
}
/**
* @return 是否尝试从请求体里读取token
*/
public Boolean getIsReadBody() {
return isReadBody;
}
/**
* @param isReadBody 是否尝试从请求体里读取token
*/
public void setIsReadBody(Boolean isReadBody) {
this.isReadBody = isReadBody;
}
/**
* @return 是否尝试从header里读取token
*/
public Boolean getIsReadHead() {
return isReadHead;
}
/**
* @param isReadHead 是否尝试从header里读取token
*/
public void setIsReadHead(Boolean isReadHead) {
this.isReadHead = isReadHead;
}
/**
* @return 是否尝试从cookie里读取token
*/
public Boolean getIsReadCookie() {
return isReadCookie;
}
/**
* @param isReadCookie 是否尝试从cookie里读取token
*/
public void setIsReadCookie(Boolean isReadCookie) {
this.isReadCookie = isReadCookie;
}
/**
* @return token风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
*/
public String getTokenStyle() {
return tokenStyle;
}
/**
* @param tokenStyle token风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
*/
public void setTokenStyle(String tokenStyle) {
this.tokenStyle = tokenStyle;
}
/**
* @return 默认dao层实现类中,每次清理过期数据间隔的时间 (单位: 秒) ,默认值30秒,设置为-1代表不启动定时清理
*/
public int getDataRefreshPeriod() {
return dataRefreshPeriod;
}
/**
* @param dataRefreshPeriod 默认dao层实现类中,每次清理过期数据间隔的时间 (单位: 秒)
* ,默认值30秒,设置为-1代表不启动定时清理
*/
public void setDataRefreshPeriod(int dataRefreshPeriod) {
this.dataRefreshPeriod = dataRefreshPeriod;
}
/**
* @return 获取[token专属session]时是否必须登录 (如果配置为true,会在每次获取[token-session]时校验是否登录)
*/
public Boolean getTokenSessionCheckLogin() {
return tokenSessionCheckLogin;
}
/**
* @param tokenSessionCheckLogin 获取[token专属session]时是否必须登录
* (如果配置为true,会在每次获取[token-session]时校验是否登录)
*/
public void setTokenSessionCheckLogin(Boolean tokenSessionCheckLogin) {
this.tokenSessionCheckLogin = tokenSessionCheckLogin;
}
/**
* @return 是否打开了自动续签 (如果此值为true, 框架会在每次直接或间接调用getLoginId()时进行一次过期检查与续签操作)
*/
public Boolean getAutoRenew() {
return autoRenew;
}
/**
* @param autoRenew 是否打开自动续签 (如果此值为true, 框架会在每次直接或间接调用getLoginId()时进行一次过期检查与续签操作)
*/
public void setAutoRenew(Boolean autoRenew) {
this.autoRenew = autoRenew;
}
/**
* @return 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景
*/
public String getCookieDomain() {
return cookieDomain;
}
/**
* @param cookieDomain 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景
*/
public void setCookieDomain(String cookieDomain) {
this.cookieDomain = cookieDomain;
}
/**
* @return 是否在初始化配置时打印版本字符画
*/
public Boolean getIsV() {
return isV;
}
/**
* @param isV 是否在初始化配置时打印版本字符画
*/
public void setIsV(Boolean isV) {
this.isV = isV;
}
/**
* toString
*/
@Override
public String toString() {
return "SaTokenConfig [tokenName=" + tokenName + ", timeout=" + timeout + ", activityTimeout=" + activityTimeout
+ ", allowConcurrentLogin=" + allowConcurrentLogin + ", isShare=" + isShare + ", isReadBody="
+ isReadBody + ", isReadHead=" + isReadHead + ", isReadCookie=" + isReadCookie + ", tokenStyle="
+ tokenStyle + ", dataRefreshPeriod=" + dataRefreshPeriod + ", tokenSessionCheckLogin="
+ tokenSessionCheckLogin + ", autoRenew=" + autoRenew + ", cookieDomain=" + cookieDomain + ", isV="
+ isV + "]";
}
}
@@ -0,0 +1,138 @@
package cn.dev33.satoken.config;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* sa-token配置文件的构建工厂类
* <p>
* 只有在非IOC环境下才会用到此类
*
* @author kong
*
*/
public class SaTokenConfigFactory {
/**
* 配置文件地址
*/
public static String configPath = "sa-token.properties";
/**
* 根据configPath路径获取配置信息
*
* @return 一个SaTokenConfig对象
*/
public static SaTokenConfig createConfig() {
Map<String, String> map = readPropToMap(configPath);
if (map == null) {
// throw new RuntimeException("找不到配置文件:" + configPath, null);
}
return (SaTokenConfig) initPropByMap(map, new SaTokenConfig());
}
/**
* 工具方法: 将指定路径的properties配置文件读取到Map中
*
* @param propertiesPath 配置文件地址
* @return 一个Map
*/
private static Map<String, String> readPropToMap(String propertiesPath) {
Map<String, String> map = new HashMap<String, String>(16);
try {
InputStream is = SaTokenConfigFactory.class.getClassLoader().getResourceAsStream(propertiesPath);
if (is == null) {
return null;
}
Properties prop = new Properties();
prop.load(is);
for (String key : prop.stringPropertyNames()) {
map.put(key, prop.getProperty(key));
}
} catch (IOException e) {
throw new RuntimeException("配置文件(" + propertiesPath + ")加载失败", e);
}
return map;
}
/**
* 工具方法: 将 Map 的值映射到一个 Model 上
*
* @param map 属性集合
* @param obj 对象, 或类型
* @return 返回实例化后的对象
*/
private static Object initPropByMap(Map<String, String> map, Object obj) {
if (map == null) {
map = new HashMap<String, String>(16);
}
// 1、取出类型
Class<?> cs = null;
if (obj instanceof Class) {
// 如果是一个类型,则将obj=null,以便完成静态属性反射赋值
cs = (Class<?>) obj;
obj = null;
} else {
// 如果是一个对象,则取出其类型
cs = obj.getClass();
}
// 2、遍历类型属性,反射赋值
for (Field field : cs.getDeclaredFields()) {
String value = map.get(field.getName());
if (value == null) {
// 如果为空代表没有配置此项
continue;
}
try {
Object valueConvert = getObjectByClass(value, field.getType());
field.setAccessible(true);
field.set(obj, valueConvert);
} catch (IllegalArgumentException e) {
throw new RuntimeException("属性赋值出错:" + field.getName(), e);
} catch (IllegalAccessException e) {
throw new RuntimeException("属性赋值出错:" + field.getName(), e);
}
}
return obj;
}
/**
* 工具方法: 将字符串转化为指定数据类型
*
* @param str 值
* @param cs 要转换的类型
* @return 转化好的结果
*/
@SuppressWarnings("unchecked")
private static <T> T getObjectByClass(String str, Class<T> cs) {
Object value = null;
if (str == null) {
value = null;
} else if (cs.equals(String.class)) {
value = str;
} else if (cs.equals(int.class) || cs.equals(Integer.class)) {
value = new Integer(str);
} else if (cs.equals(long.class) || cs.equals(Long.class)) {
value = new Long(str);
} else if (cs.equals(short.class) || cs.equals(Short.class)) {
value = new Short(str);
} else if (cs.equals(float.class) || cs.equals(Float.class)) {
value = new Float(str);
} else if (cs.equals(double.class) || cs.equals(Double.class)) {
value = new Double(str);
} else if (cs.equals(boolean.class) || cs.equals(Boolean.class)) {
value = new Boolean(str);
} else {
throw new RuntimeException("未能将值:" + str + ",转换类型为:" + cs, null);
}
return (T) value;
}
}
@@ -0,0 +1,55 @@
package cn.dev33.satoken.cookie;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* sa-token 对cookie的相关操作 接口类
*
* @author kong
*
*/
public interface SaTokenCookie {
/**
* 在request对象中获取指定Cookie
*
* @param request request对象
* @param cookieName Cookie名称
* @return 查找到的Cookie对象
*/
public Cookie getCookie(HttpServletRequest request, String cookieName);
/**
* 添加Cookie
*
* @param response response对象
* @param name Cookie名称
* @param value Cookie值
* @param path Cookie路径
* @param domain Cookie的作用域
* @param timeout 过期时间 (秒)
*/
public void addCookie(HttpServletResponse response, String name, String value, String path, String domain, int timeout);
/**
* 删除Cookie
*
* @param request request对象
* @param response response对象
* @param name Cookie名称
*/
public void delCookie(HttpServletRequest request, HttpServletResponse response, String name);
/**
* 修改Cookie的value值
*
* @param request request对象
* @param response response对象
* @param name Cookie名称
* @param value Cookie值
*/
public void updateCookie(HttpServletRequest request, HttpServletResponse response, String name, String value);
}
@@ -4,41 +4,44 @@ import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.dev33.satoken.util.SaCookieUtil;
/**
* sa-token 对cookie的相关操作 接口实现类
* sa-token 对cookie的相关操作 接口实现类
*
* @author kong
*
*/
public class SaCookieOperDefaultImpl implements SaCookieOper {
public class SaTokenCookieDefaultImpl implements SaTokenCookie {
/**
* 获取指定cookie
* 获取指定cookie
*/
@Override
public Cookie getCookie(HttpServletRequest request, String cookieName) {
return SaCookieUtil.getCookie(request, cookieName);
return SaTokenCookieUtil.getCookie(request, cookieName);
}
/**
* 添加cookie
* 添加cookie
*/
public void addCookie(HttpServletResponse response, String name, String value, String path, int timeout) {
SaCookieUtil.addCookie(response, name, value, path, timeout);
@Override
public void addCookie(HttpServletResponse response, String name, String value, String path, String domain, int timeout) {
SaTokenCookieUtil.addCookie(response, name, value, path, domain, timeout);
}
/**
* 删除cookie
* 删除cookie
*/
@Override
public void delCookie(HttpServletRequest request, HttpServletResponse response, String name) {
SaCookieUtil.delCookie(request, response, name);
SaTokenCookieUtil.delCookie(request, response, name);
}
/**
* 修改cookie的value值
* 修改cookie的value值
*/
@Override
public void updateCookie(HttpServletRequest request, HttpServletResponse response, String name, String value) {
SaCookieUtil.updateCookie(request, response, name, value);
SaTokenCookieUtil.updateCookie(request, response, name, value);
}
}
@@ -1,23 +1,24 @@
package cn.dev33.satoken.util;
package cn.dev33.satoken.cookie;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
* cookie操作工具类
* Cookie操作工具类
*
* @author kong
*
*/
public class SaCookieUtil {
public class SaTokenCookieUtil {
/**
* 获取指定cookie .
* 在request对象中获取指定Cookie
*
* @param request .
* @param cookieName .
* @return .
* @param request request对象
* @param cookieName Cookie名称
* @return 查找到的Cookie对象
*/
public static Cookie getCookie(HttpServletRequest request, String cookieName) {
Cookie[] cookies = request.getCookies();
@@ -32,37 +33,41 @@ public class SaCookieUtil {
}
/**
* 添加cookie
* 添加cookie
*
* @param response .
* @param name .
* @param value .
* @param path .
* @param timeout .
* @param response response
* @param name Cookie名称
* @param value Cookie值
* @param path Cookie写入路径
* @param domain Cookie的作用域
* @param timeout Cookie有效期 ()
*/
public static void addCookie(HttpServletResponse response, String name, String value, String path, int timeout) {
public static void addCookie(HttpServletResponse response, String name, String value, String path, String domain, int timeout) {
Cookie cookie = new Cookie(name, value);
if (path == null) {
if(SaTokenInsideUtil.isEmpty(path) == false) {
path = "/";
}
if(SaTokenInsideUtil.isEmpty(domain) == false) {
cookie.setDomain(domain);
}
cookie.setPath(path);
cookie.setMaxAge(timeout);
response.addCookie(cookie);
}
/**
* 删除cookie .
* 删除Cookie
*
* @param request .
* @param response .
* @param name .
* @param request request对象
* @param response response对象
* @param name Cookie名称
*/
public static void delCookie(HttpServletRequest request, HttpServletResponse response, String name) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie != null && (name).equals(cookie.getName())) {
addCookie(response, name, null, null, 0);
addCookie(response, name, null, null, null, 0);
return;
}
}
@@ -70,12 +75,12 @@ public class SaCookieUtil {
}
/**
* 修改cookie的value值
* 修改cookie的value值
*
* @param request .
* @param response .
* @param name .
* @param value .
* @param request request对象
* @param response response对象
* @param name Cookie名称
* @param value Cookie值
*/
public static void updateCookie(HttpServletRequest request, HttpServletResponse response, String name,
String value) {
@@ -83,7 +88,7 @@ public class SaCookieUtil {
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie != null && (name).equals(cookie.getName())) {
addCookie(response, name, value, cookie.getPath(), cookie.getMaxAge());
addCookie(response, name, value, cookie.getPath(), cookie.getDomain(), cookie.getMaxAge());
return;
}
}
@@ -0,0 +1,121 @@
package cn.dev33.satoken.dao;
import java.util.List;
import cn.dev33.satoken.session.SaSession;
/**
* sa-token持久层的接口
* @author kong
*/
public interface SaTokenDao {
/** 常量,表示一个key永不过期 (在一个key被标注为永远不过期时返回此值) */
public static final Long NEVER_EXPIRE = -1L;
/** 常量,表示系统中不存在这个缓存 (在对不存在的key获取剩余存活时间时返回此值) */
public static final Long NOT_VALUE_EXPIRE = -2L;
// --------------------- token相关 ---------------------
/**
* 根据key获取value,如果没有,则返回空
* @param key 键名称
* @return value
*/
public String getValue(String key);
/**
* 写入指定key-value键值对,并设定过期时间 (单位: 秒)
* @param key 键名称
* @param value 值
* @param timeout 过期时间 (单位: 秒)
*/
public void setValue(String key, String value, long timeout);
/**
* 修改指定key-value键值对 (过期时间取原来的值)
* @param key 键名称
* @param value 值
*/
public void updateValue(String key, String value);
/**
* 删除一个指定的key
* @param key 键名称
*/
public void deleteKey(String key);
/**
* 获取指定key的剩余存活时间 (单位: 秒)
* @param key 指定key
* @return 这个key的剩余存活时间
*/
public long getTimeout(String key);
/**
* 修改指定key的剩余存活时间 (单位: 秒)
* @param key 指定key
* @param timeout 过期时间
*/
public void updateTimeout(String key, long timeout);
// --------------------- Session相关 ---------------------
/**
* 根据指定key的Session,如果没有,则返回空
* @param sessionId 键名称
* @return SaSession
*/
public SaSession getSession(String sessionId);
/**
* 将指定Session持久化
* @param session 要保存的session对象
* @param timeout 过期时间 (单位: 秒)
*/
public void saveSession(SaSession session, long timeout);
/**
* 更新指定session
* @param session 要更新的session对象
*/
public void updateSession(SaSession session);
/**
* 删除一个指定的session
* @param sessionId sessionId
*/
public void deleteSession(String sessionId);
/**
* 获取指定SaSession的剩余存活时间 (单位: 秒)
* @param sessionId 指定SaSession
* @return 这个SaSession的剩余存活时间 (单位: 秒)
*/
public long getSessionTimeout(String sessionId);
/**
* 修改指定SaSession的剩余存活时间 (单位: 秒)
* @param sessionId sessionId
* @param timeout 过期时间
*/
public void updateSessionTimeout(String sessionId, long timeout);
// --------------------- 会话管理 ---------------------
/**
* 搜索数据
* @param prefix 前缀
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
* @return 查询到的数据集合
*/
public List<String> searchData(String prefix, String keyword, int start, int size);
}
@@ -0,0 +1,224 @@
package cn.dev33.satoken.dao;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.concurrent.ConcurrentHashMap;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.util.SaTaskUtil;
import cn.dev33.satoken.util.SaTaskUtil.FunctionRunClass;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
* sa-token持久层默认的实现类 , 基于内存Map
* @author kong
*
*/
public class SaTokenDaoDefaultImpl implements SaTokenDao {
/**
* 数据集合
*/
public Map<String, Object> dataMap = new ConcurrentHashMap<String, Object>();
/**
* 过期时间集合 (单位: 毫秒) , 记录所有key的到期时间 [注意不是剩余存活时间]
*/
public Map<String, Long> expireMap = new ConcurrentHashMap<String, Long>();
/**
* 构造函数
*/
public SaTokenDaoDefaultImpl() {
initRefreshTimer();
}
// ------------------------ String 读写操作
@Override
public String getValue(String key) {
clearKeyByTimeout(key);
return (String)dataMap.get(key);
}
@Override
public void setValue(String key, String value, long timeout) {
dataMap.put(key, value);
expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
}
@Override
public void updateValue(String key, String value) {
if(getKeyTimeout(key) == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
dataMap.put(key, value);
}
@Override
public void deleteKey(String key) {
dataMap.remove(key);
expireMap.remove(key);
}
@Override
public long getTimeout(String key) {
return getKeyTimeout(key);
}
@Override
public void updateTimeout(String key, long timeout) {
expireMap.put(key, System.currentTimeMillis() + timeout * 1000);
}
// ------------------------ Session 读写操作
@Override
public SaSession getSession(String sessionId) {
clearKeyByTimeout(sessionId);
return (SaSession)dataMap.get(sessionId);
}
@Override
public void saveSession(SaSession session, long timeout) {
dataMap.put(session.getId(), session);
expireMap.put(session.getId(), (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
}
@Override
public void updateSession(SaSession session) {
if(getKeyTimeout(session.getId()) == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
// 无动作
}
@Override
public void deleteSession(String sessionId) {
dataMap.remove(sessionId);
expireMap.remove(sessionId);
}
@Override
public long getSessionTimeout(String sessionId) {
return getKeyTimeout(sessionId);
}
@Override
public void updateSessionTimeout(String sessionId, long timeout) {
expireMap.put(sessionId, System.currentTimeMillis() + timeout * 1000);
}
// ------------------------ 过期时间相关操作
/**
* 如果指定key已经过期,则立即清除它
* @param key 指定key
*/
void clearKeyByTimeout(String key) {
Long expirationTime = expireMap.get(key);
// 清除条件:如果不为空 && 不是[永不过期] && 已经超过过期时间
if(expirationTime != null && expirationTime != SaTokenDao.NEVER_EXPIRE && expirationTime < System.currentTimeMillis()) {
dataMap.remove(key);
expireMap.remove(key);
}
}
/**
* 获取指定key的剩余存活时间 (单位:秒)
*/
long getKeyTimeout(String key) {
// 先检查是否已经过期
clearKeyByTimeout(key);
// 获取过期时间
Long expire = expireMap.get(key);
// 如果根本没有这个值
if(expire == null) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
// 如果被标注为永不过期
if(expire == SaTokenDao.NEVER_EXPIRE) {
return SaTokenDao.NEVER_EXPIRE;
}
// ---- 计算剩余时间并返回
long timeout = (expire - System.currentTimeMillis()) / 1000;
// 小于零时,视为不存在
if(timeout < 0) {
dataMap.remove(key);
expireMap.remove(key);
return SaTokenDao.NOT_VALUE_EXPIRE;
}
return timeout;
}
// --------------------- 定时清理过期数据
/**
* 定时任务对象
*/
public Timer refreshTimer;
/**
* 清理所有已经过期的key
*/
public void refreshDataMap() {
Iterator<String> keys = expireMap.keySet().iterator();
while (keys.hasNext()) {
clearKeyByTimeout(keys.next());
}
}
/**
* 初始化定时任务
*/
public void initRefreshTimer() {
// 如果已经被初始化过了, 则停止它
if(this.refreshTimer != null) {
this.endRefreshTimer();
}
// 开始新的定时任务
if(SaTokenManager.getConfig().getDataRefreshPeriod() < 0) {
return;
}
int period = SaTokenManager.getConfig().getDataRefreshPeriod() * 1000;
this.refreshTimer = SaTaskUtil.setInterval(new FunctionRunClass() {
@Override
public void run() {
refreshDataMap();
}
}, period, period);
}
/**
* 结束定时任务
*/
public void endRefreshTimer() {
this.refreshTimer.cancel();
}
// --------------------- 会话管理
@Override
public List<String> searchData(String prefix, String keyword, int start, int size) {
return SaTokenInsideUtil.searchList(expireMap.keySet(), prefix, keyword, start, size);
}
}
@@ -4,14 +4,13 @@ import java.util.Arrays;
import java.util.List;
/**
* 没有登陆抛出的异常
* @author kong
*
* 一个异常代表用户没有登录
* @author kong
*/
public class NotLoginException extends RuntimeException {
public class NotLoginException extends SaTokenException {
/**
*
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130142L;
@@ -57,9 +56,10 @@ public class NotLoginException extends RuntimeException {
* 异常类型
*/
private String type;
/**
* 获取异常类型
* @return
* @return 异常类型
*/
public String getType() {
return type;
@@ -70,59 +70,49 @@ public class NotLoginException extends RuntimeException {
* loginKey
*/
private String loginKey;
/**
* 获得loginKey
* @return login_key
* @return loginKey
*/
public String getLoginKey() {
return loginKey;
}
// /**
// * 创建一个
// */
// public NotLoginException() {
// this(StpUtil.stpLogic.loginKey);
// }
/**
* 创建一个
* 构造方法创建一个
* @param message 异常消息
* @param loginKey loginKey
* @param type 类型
*/
public NotLoginException(String message, String loginKey, String type) {
// 这里到底要不要拼接上login_key呢纠结
super(message);
this.loginKey = loginKey;
this.type = type;
}
/**
* 静态方法构建一个NotLoginException
* @param loginKey loginKey
* 静态方法构建一个NotLoginException
* @param loginKey loginKey
* @param type 场景类型
* @return 构建完毕的异常对象
*/
public static NotLoginException newInstance(String loginKey, String type) {
String message = null;
if(type.equals(NOT_TOKEN)) {
if(NOT_TOKEN.equals(type)) {
message = NOT_TOKEN_MESSAGE;
}
else if(type.equals(INVALID_TOKEN)) {
else if(INVALID_TOKEN.equals(type)) {
message = INVALID_TOKEN_MESSAGE;
}
else if(type.equals(TOKEN_TIMEOUT)) {
else if(TOKEN_TIMEOUT.equals(type)) {
message = TOKEN_TIMEOUT_MESSAGE;
}
else if(type.equals(BE_REPLACED)) {
else if(BE_REPLACED.equals(type)) {
message = BE_REPLACED_MESSAGE;
}
else if(type.equals(KICK_OUT)) {
else if(KICK_OUT.equals(type)) {
message = KICK_OUT_MESSAGE;
}
else {
@@ -0,0 +1,52 @@
package cn.dev33.satoken.exception;
import cn.dev33.satoken.stp.StpUtil;
/**
* 没有指定权限码,抛出的异常
*
* @author kong
*
*/
public class NotPermissionException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130142L;
/** 权限码 */
private String code;
/**
* @return 获得权限码
*/
public String getCode() {
return code;
}
/**
* loginKey
*/
private String loginKey;
/**
* 获得loginKey
*
* @return loginKey
*/
public String getLoginKey() {
return loginKey;
}
public NotPermissionException(String code) {
this(code, StpUtil.stpLogic.loginKey);
}
public NotPermissionException(String code, String loginKey) {
super("无此权限:" + code);
this.code = code;
this.loginKey = loginKey;
}
}
@@ -0,0 +1,53 @@
package cn.dev33.satoken.exception;
import cn.dev33.satoken.stp.StpUtil;
/**
* 没有指定角色标识,抛出的异常
*
* @author kong
*
*/
public class NotRoleException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 8243974276159004739L;
/** 角色标识 */
private String role;
/**
* @return 获得角色标识
*/
public String getRole() {
return role;
}
/**
* loginKey
*/
private String loginKey;
/**
* 获得loginKey
*
* @return loginKey
*/
public String getLoginKey() {
return loginKey;
}
public NotRoleException(String role) {
this(role, StpUtil.stpLogic.loginKey);
}
public NotRoleException(String role, String loginKey) {
// 这里到底要不要拼接上loginKey呢?纠结
super("无此角色:" + role);
this.role = role;
this.loginKey = loginKey;
}
}
@@ -0,0 +1,35 @@
package cn.dev33.satoken.exception;
/**
* sa-token框架内部逻辑发生错误抛出的异常
* (自定义此异常可方便开发者在做全局异常处理时分辨异常类型)
*
* @author kong
*
*/
public class SaTokenException extends RuntimeException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130132L;
/**
* 构建一个异常
*
* @param message 异常描述信息
*/
public SaTokenException(String message) {
super(message);
}
/**
* 构建一个异常
*
* @param cause 异常对象
*/
public SaTokenException(Throwable cause) {
super(cause);
}
}
@@ -0,0 +1,36 @@
package cn.dev33.satoken.fun;
/**
* 根据boolean变量,决定是否执行一个函数
*
* @author kong
*
*/
public class IsRunFunction {
/**
* 变量
*/
public final Boolean isRun;
/**
* 设定一个变量,如果为true,则执行exe函数
*
* @param isRun 变量
*/
public IsRunFunction(boolean isRun) {
this.isRun = isRun;
}
/**
* 根据变量决定是否执行此函数
*
* @param function 函数
*/
public void exe(SaFunction function) {
if (isRun) {
function.run();
}
}
}
@@ -0,0 +1,16 @@
package cn.dev33.satoken.fun;
/**
* 设定一个函数,方便在Lambda表达式下的函数式编程
*
* @author kong
*
*/
public interface SaFunction {
/**
* 执行的方法
*/
public void run();
}
@@ -0,0 +1,28 @@
package cn.dev33.satoken.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet相关操作接口
*
* @author kong
*
*/
public interface SaTokenServlet {
/**
* 获取当前请求的 Request 对象
*
* @return 当前请求的Request对象
*/
public HttpServletRequest getRequest();
/**
* 获取当前请求的 Response 对象
*
* @return 当前请求的response对象
*/
public HttpServletResponse getResponse();
}
@@ -0,0 +1,32 @@
package cn.dev33.satoken.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.dev33.satoken.exception.SaTokenException;
/**
* sa-token 对SaTokenServlet接口默认实现类
*
* @author kong
*
*/
public class SaTokenServletDefaultImpl implements SaTokenServlet {
/**
* 获取当前请求的Request对象
*/
@Override
public HttpServletRequest getRequest() {
throw new SaTokenException("请实现SaTokenServlet接口后进行Servlet相关操作");
}
/**
* 获取当前请求的Response对象
*/
@Override
public HttpServletResponse getResponse() {
throw new SaTokenException("请实现SaTokenServlet接口后进行Servlet相关操作");
}
}
@@ -0,0 +1,250 @@
package cn.dev33.satoken.session;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import cn.dev33.satoken.SaTokenManager;
/**
* Session Model
*
* @author kong
*
*/
public class SaSession implements Serializable {
private static final long serialVersionUID = 1L;
/** 此Session的id */
private String id;
/** 此Session的创建时间 */
private long createTime;
/** 此Session的所有挂载数据 */
private Map<String, Object> dataMap = new ConcurrentHashMap<String, Object>();
// ----------------------- 构建相关
/**
* 构建一个Session对象
*/
public SaSession() {
}
/**
* 构建一个Session对象
* @param id Session的id
*/
public SaSession(String id) {
this.id = id;
this.createTime = System.currentTimeMillis();
}
/**
* 获取此Session的id
* @return 此会话的id
*/
public String getId() {
return id;
}
/**
* 写入此Session的id
* @param id SessionId
* @return 对象自身
*/
public SaSession setId(String id) {
this.id = id;
return this;
}
/**
* 返回当前会话创建时间
* @return 时间戳
*/
public long getCreateTime() {
return createTime;
}
/**
* 写入此Session的创建时间
* @param createTime 时间戳
* @return 对象自身
*/
public SaSession setCreateTime(long createTime) {
this.createTime = createTime;
return this;
}
// ----------------------- TokenSign相关
/**
* 此Session绑定的token签名列表
*/
private List<TokenSign> tokenSignList = new Vector<TokenSign>();
/**
* 返回token签名列表的拷贝副本
*
* @return token签名列表
*/
public List<TokenSign> getTokenSignList() {
return new Vector<>(tokenSignList);
}
/**
* 查找一个token签名
*
* @param tokenValue token值
* @return 查找到的tokenSign
*/
public TokenSign getTokenSign(String tokenValue) {
for (TokenSign tokenSign : getTokenSignList()) {
if (tokenSign.getValue().equals(tokenValue)) {
return tokenSign;
}
}
return null;
}
/**
* 添加一个token签名
*
* @param tokenSign token签名
*/
public void addTokenSign(TokenSign tokenSign) {
// 如果已经存在于列表中,则无需再次添加
for (TokenSign tokenSign2 : getTokenSignList()) {
if (tokenSign2.getValue().equals(tokenSign.getValue())) {
return;
}
}
// 添加并更新
tokenSignList.add(tokenSign);
update();
}
/**
* 移除一个token签名
*
* @param tokenValue token名称
*/
public void removeTokenSign(String tokenValue) {
TokenSign tokenSign = getTokenSign(tokenValue);
if (tokenSignList.remove(tokenSign)) {
update();
}
}
// ----------------------- 存取值
/**
* 写入一个值
*
* @param key 名称
* @param value 值
*/
public void setAttribute(String key, Object value) {
dataMap.put(key, value);
update();
}
/**
* 取出一个值
*
* @param key 名称
* @return 值
*/
public Object getAttribute(String key) {
return dataMap.get(key);
}
/**
* 取值,并指定取不到值时的默认值
*
* @param key 名称
* @param defaultValue 取不到值的时候返回的默认值
* @return value
*/
public Object getAttribute(String key, Object defaultValue) {
Object value = getAttribute(key);
if (value != null) {
return value;
}
return defaultValue;
}
/**
* 移除一个值
*
* @param key 要移除的值的名字
*/
public void removeAttribute(String key) {
dataMap.remove(key);
update();
}
/**
* 清空所有值
*/
public void clearAttribute() {
dataMap.clear();
update();
}
/**
* 是否含有指定key
*
* @param key 是否含有指定值
* @return 是否含有
*/
public boolean containsAttribute(String key) {
return dataMap.keySet().contains(key);
}
/**
* 返回当前session会话所有key
*
* @return 所有值的key列表
*/
public Set<String> attributeKeys() {
return dataMap.keySet();
}
/**
* 获取数据挂载集合(如果更新map里的值,请调用session.update()方法避免产生脏数据
*
* @return 返回底层储存值的map对象
*/
public Map<String, Object> getDataMap() {
return dataMap;
}
// ----------------------- 一些操作
/**
* 将这个Session从持久库更新一下
*/
public void update() {
SaTokenManager.getSaTokenDao().updateSession(this);
}
/** 注销会话 (注销后,此session会话将不再存储服务器上) */
public void logout() {
SaTokenManager.getSaTokenDao().deleteSession(this.id);
}
/** 当Session上的tokenSign数量为零时,注销会话 */
public void logoutByTokenSignCountToZero() {
if (tokenSignList.size() == 0) {
logout();
}
}
}
@@ -0,0 +1,73 @@
package cn.dev33.satoken.session;
import cn.dev33.satoken.SaTokenManager;
/**
* 自定义Session工具类
*
* @author kong
*
*/
public class SaSessionCustomUtil {
/**
* 添加上指定前缀,防止恶意伪造session
*/
public static String sessionKey = "custom";
/**
* 组织一下自定义Session的id
*
* @param sessionId 会话id
* @return sessionId
*/
public static String getSessionKey(String sessionId) {
return SaTokenManager.getConfig().getTokenName() + ":" + sessionKey + ":session:" + sessionId;
}
/**
* 验证指定key的Session是否存在
*
* @param sessionId session的id
* @return 是否存在
*/
public boolean isExists(String sessionId) {
return SaTokenManager.getSaTokenDao().getSession(getSessionKey(sessionId)) != null;
}
/**
* 获取指定key的Session
*
* @param sessionId key
* @param isCreate 如果此Session尚未在DB创建,是否新建并返回
* @return SaSession
*/
public static SaSession getSessionById(String sessionId, boolean isCreate) {
SaSession session = SaTokenManager.getSaTokenDao().getSession(getSessionKey(sessionId));
if (session == null && isCreate) {
session = SaTokenManager.getSaTokenAction().createSession(sessionId);
SaTokenManager.getSaTokenDao().saveSession(session, SaTokenManager.getConfig().getTimeout());
}
return session;
}
/**
* 获取指定key的Session, 如果此Session尚未在DB创建,则新建并返回
*
* @param sessionId key
* @return session对象
*/
public static SaSession getSessionById(String sessionId) {
return getSessionById(sessionId, true);
}
/**
* 删除指定key的session
*
* @param sessionId 指定key
*/
public static void deleteSessionById(String sessionId) {
SaTokenManager.getSaTokenDao().deleteSession(getSessionKey(sessionId));
}
}
@@ -0,0 +1,64 @@
package cn.dev33.satoken.session;
import java.io.Serializable;
/**
* token签名 Model
*
* 挂在到SaSession上的token签名
*
* @author kong
*
*/
public class TokenSign implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1406115065849845073L;
/**
* token值
*/
private String value;
/**
* 所在设备标识
*/
private String device;
/** 构建一个 */
public TokenSign() {
}
/**
* 构建一个
*
* @param value token值
* @param device 所在设备标识
*/
public TokenSign(String value, String device) {
this.value = value;
this.device = device;
}
/**
* @return token value
*/
public String getValue() {
return value;
}
/**
* @return token登录设备
*/
public String getDevice() {
return device;
}
@Override
public String toString() {
return "TokenSign [value=" + value + ", device=" + device + "]";
}
}
@@ -0,0 +1,192 @@
package cn.dev33.satoken.stp;
/**
* token信息Model: 用来描述一个token的常用参数
*
* @author kong
*
*/
public class SaTokenInfo {
/** token名称 */
public String tokenName;
/** token值 */
public String tokenValue;
/** 此token是否已经登录 */
public Boolean isLogin;
/** 此token对应的LoginId,未登录时为null */
public Object loginId;
/** LoginKey账号体系标识 */
public String loginKey;
/** token剩余有效期 (单位: 秒) */
public long tokenTimeout;
/** User-Session剩余有效时间 (单位: 秒) */
public long sessionTimeout;
/** Token-Session剩余有效时间 (单位: 秒) */
public long tokenSessionTimeout;
/** token剩余无操作有效时间 (单位: 秒) */
public long tokenActivityTimeout;
/** 登录设备标识 */
public String loginDevice;
/**
* @return token名称
*/
public String getTokenName() {
return tokenName;
}
/**
* @param tokenName token名称
*/
public void setTokenName(String tokenName) {
this.tokenName = tokenName;
}
/**
* @return token值
*/
public String getTokenValue() {
return tokenValue;
}
/**
* @param tokenValue token值
*/
public void setTokenValue(String tokenValue) {
this.tokenValue = tokenValue;
}
/**
* @return 此token是否已经登录
*/
public Boolean getIsLogin() {
return isLogin;
}
/**
* @param isLogin 此token是否已经登录
*/
public void setIsLogin(Boolean isLogin) {
this.isLogin = isLogin;
}
/**
* @return 此token对应的LoginId,未登录时为null
*/
public Object getLoginId() {
return loginId;
}
/**
* @param loginId 此token对应的LoginId,未登录时为null
*/
public void setLoginId(Object loginId) {
this.loginId = loginId;
}
/**
* @return LoginKey账号体系标识
*/
public String getLoginKey() {
return loginKey;
}
/**
* @param loginKey LoginKey账号体系标识
*/
public void setLoginKey(String loginKey) {
this.loginKey = loginKey;
}
/**
* @return token剩余有效期 (单位: 秒)
*/
public long getTokenTimeout() {
return tokenTimeout;
}
/**
* @param tokenTimeout token剩余有效期 (单位: 秒)
*/
public void setTokenTimeout(long tokenTimeout) {
this.tokenTimeout = tokenTimeout;
}
/**
* @return User-Session剩余有效时间 (单位: 秒)
*/
public long getSessionTimeout() {
return sessionTimeout;
}
/**
* @param sessionTimeout User-Session剩余有效时间 (单位: 秒)
*/
public void setSessionTimeout(long sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
/**
* @return Token-Session剩余有效时间 (单位: 秒)
*/
public long getTokenSessionTimeout() {
return tokenSessionTimeout;
}
/**
* @param tokenSessionTimeout Token-Session剩余有效时间 (单位: 秒)
*/
public void setTokenSessionTimeout(long tokenSessionTimeout) {
this.tokenSessionTimeout = tokenSessionTimeout;
}
/**
* @return token剩余无操作有效时间 (单位: 秒)
*/
public long getTokenActivityTimeout() {
return tokenActivityTimeout;
}
/**
* @param tokenActivityTimeout token剩余无操作有效时间 (单位: 秒)
*/
public void setTokenActivityTimeout(long tokenActivityTimeout) {
this.tokenActivityTimeout = tokenActivityTimeout;
}
/**
* @return 登录设备标识
*/
public String getLoginDevice() {
return loginDevice;
}
/**
* @param loginDevice 登录设备标识
*/
public void setLoginDevice(String loginDevice) {
this.loginDevice = loginDevice;
}
/**
* toString
*/
@Override
public String toString() {
return "SaTokenInfo [tokenName=" + tokenName + ", tokenValue=" + tokenValue + ", isLogin=" + isLogin
+ ", loginId=" + loginId + ", loginKey=" + loginKey + ", tokenTimeout=" + tokenTimeout
+ ", sessionTimeout=" + sessionTimeout + ", tokenSessionTimeout=" + tokenSessionTimeout
+ ", tokenActivityTimeout=" + tokenActivityTimeout + ", loginDevice=" + loginDevice + "]";
}
}
@@ -0,0 +1,30 @@
package cn.dev33.satoken.stp;
import java.util.List;
/**
* 权限认证接口,实现此接口即可集成权限认证功能
*
* @author kong
*/
public interface StpInterface {
/**
* 返回指定 LoginId 所拥有的权限码集合
*
* @param loginId 账号id
* @param loginKey 账号体系标识
* @return 该账号id具有的权限码集合
*/
public List<String> getPermissionList(Object loginId, String loginKey);
/**
* 返回指定loginId所拥有的角色标识集合
*
* @param loginId 账号id
* @param loginKey 账号体系标识
* @return 该账号id具有的角色标识集合
*/
public List<String> getRoleList(Object loginId, String loginKey);
}
@@ -0,0 +1,25 @@
package cn.dev33.satoken.stp;
import java.util.ArrayList;
import java.util.List;
/**
* 对StpInterface接口默认的实现类
* <p>
* 如果开发者没有实现StpInterface接口,则使用此默认实现
*
* @author kong
*/
public class StpInterfaceDefaultImpl implements StpInterface {
@Override
public List<String> getPermissionList(Object loginId, String loginKey) {
return new ArrayList<String>();
}
@Override
public List<String> getRoleList(Object loginId, String loginKey) {
return new ArrayList<String>();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,526 @@
package cn.dev33.satoken.stp;
import java.util.List;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.session.SaSession;
/**
* 一个默认的实现
* @author kong
*/
public class StpUtil {
/**
* 底层的 StpLogic 对象
*/
public static StpLogic stpLogic = new StpLogic("login");
/**
* 获取当前StpLogin的loginKey
* @return 当前StpLogin的loginKey
*/
public static String getLoginKey(){
return stpLogic.getLoginKey();
}
// =================== 获取token 相关 ===================
/**
* 返回token名称
* @return 此StpLogic的token名称
*/
public static String getTokenName() {
return stpLogic.getTokenName();
}
/**
* 获取当前tokenValue
* @return 当前tokenValue
*/
public static String getTokenValue() {
return stpLogic.getTokenValue();
}
/**
* 获取当前会话的token信息
* @return token信息
*/
public static SaTokenInfo getTokenInfo() {
return stpLogic.getTokenInfo();
}
// =================== 登录相关操作 ===================
/**
* 在当前会话上登录id
* @param loginId 登录id,建议的类型:(long | int | String
*/
public static void setLoginId(Object loginId) {
stpLogic.setLoginId(loginId);
}
/**
* 在当前会话上登录id
* @param loginId 登录id,建议的类型:(long | int | String
* @param device 设备标识
*/
public static void setLoginId(Object loginId, String device) {
stpLogic.setLoginId(loginId, device);
}
/**
* 当前会话注销登录
*/
public static void logout() {
stpLogic.logout();
}
/**
* 指定token的会话注销登录
* @param tokenValue 指定token
*/
public static void logoutByTokenValue(String tokenValue) {
stpLogic.logoutByTokenValue(tokenValue);
}
/**
* 指定loginId的会话注销登录(踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
* @param loginId 账号id
*/
public static void logoutByLoginId(Object loginId) {
stpLogic.logoutByLoginId(loginId);
}
/**
* 指定loginId指定设备的会话注销登录(踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
* @param loginId 账号id
* @param device 设备标识
*/
public static void logoutByLoginId(Object loginId, String device) {
stpLogic.logoutByLoginId(loginId, device);
}
// 查询相关
/**
* 获取当前会话是否已经登录
* @return 是否已登录
*/
public static boolean isLogin() {
return stpLogic.isLogin();
}
/**
* 检验当前会话是否已经登录,如未登录,则抛出异常
*/
public static void checkLogin() {
stpLogic.checkLogin();
}
/**
* 获取当前会话账号id, 如果未登录,则抛出异常
* @return 账号id
*/
public static Object getLoginId() {
return stpLogic.getLoginId();
}
/**
* 获取当前会话登录id, 如果未登录,则返回默认值
* @param <T> 返回类型
* @param defaultValue 默认值
* @return 登录id
*/
public static <T> T getLoginId(T defaultValue) {
return stpLogic.getLoginId(defaultValue);
}
/**
* 获取当前会话登录id, 如果未登录,则返回null
* @return 账号id
*/
public static Object getLoginIdDefaultNull() {
return stpLogic.getLoginIdDefaultNull();
}
/**
* 获取当前会话登录id, 并转换为String
* @return 账号id
*/
public static String getLoginIdAsString() {
return stpLogic.getLoginIdAsString();
}
/**
* 获取当前会话登录id, 并转换为int
* @return 账号id
*/
public static int getLoginIdAsInt() {
return stpLogic.getLoginIdAsInt();
}
/**
* 获取当前会话登录id, 并转换为long
* @return 账号id
*/
public static long getLoginIdAsLong() {
return stpLogic.getLoginIdAsLong();
}
/**
* 获取指定token对应的登录id,如果未登录,则返回 null
* @param tokenValue token
* @return 登录id
*/
public static Object getLoginIdByToken(String tokenValue) {
return stpLogic.getLoginIdByToken(tokenValue);
}
// =================== session相关 ===================
/**
* 获取指定loginId的session, 如果session尚未创建,isCreate=是否新建并返回
* @param loginId 账号id
* @param isCreate 是否新建
* @return SaSession
*/
public static SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
return stpLogic.getSessionByLoginId(loginId, isCreate);
}
/**
* 获取指定key的session, 如果session尚未创建,则返回null
* @param sessionId sessionId
* @return session对象
*/
public static SaSession getSessionBySessionId(String sessionId) {
return stpLogic.getSessionBySessionId(sessionId);
}
/**
* 获取指定loginId的session,如果session尚未创建,则新建并返回
* @param loginId 账号id
* @return session会话
*/
public static SaSession getSessionByLoginId(Object loginId) {
return stpLogic.getSessionByLoginId(loginId);
}
/**
* 获取当前会话的session, 如果session尚未创建,isCreate=是否新建并返回
* @param isCreate 是否新建
* @return 当前会话的session
*/
public static SaSession getSession(boolean isCreate) {
return stpLogic.getSession(isCreate);
}
/**
* 获取当前会话的session,如果session尚未创建,则新建并返回
* @return 当前会话的session
*/
public static SaSession getSession() {
return stpLogic.getSession();
}
// =================== token专属session ===================
/**
* 获取指定token的专属session,如果session尚未创建,则新建并返回
* @param tokenValue token值
* @return session会话
*/
public static SaSession getTokenSessionByToken(String tokenValue) {
return stpLogic.getTokenSessionByToken(tokenValue);
}
/**
* 获取当前token的专属-session,如果session尚未创建,则新建并返回
* @return session会话
*/
public static SaSession getTokenSession() {
return stpLogic.getTokenSession();
}
// =================== [临时过期] 验证相关 ===================
/**
* 检查当前token 是否已经[临时过期],如果已经过期则抛出异常
*/
public static void checkActivityTimeout() {
stpLogic.checkActivityTimeout();
}
/**
* 续签当前token:(将 [最后操作时间] 更新为当前时间戳)
* <h1>请注意: 即时token已经 [临时过期] 也可续签成功,
* 如果此场景下需要提示续签失败,可在此之前调用 checkActivityTimeout() 强制检查是否过期即可 </h1>
*/
public static void updateLastActivityToNow() {
stpLogic.updateLastActivityToNow();
}
// =================== 过期时间相关 ===================
/**
* 获取当前登录者的token剩余有效时间 (单位: 秒)
* @return token剩余有效时间
*/
public static long getTokenTimeout() {
return stpLogic.getTokenTimeout();
}
/**
* 获取当前登录者的Session剩余有效时间 (单位: 秒)
* @return token剩余有效时间
*/
public static long getSessionTimeout() {
return stpLogic.getSessionTimeout();
}
/**
* 获取当前token的专属Session剩余有效时间 (单位: 秒)
* @return token剩余有效时间
*/
public static long getTokenSessionTimeout() {
return stpLogic.getTokenSessionTimeout();
}
/**
* 获取当前token[临时过期]剩余有效时间 (单位: 秒)
* @return token[临时过期]剩余有效时间
*/
public static long getTokenActivityTimeout() {
return stpLogic.getTokenActivityTimeout();
}
// =================== 角色验证操作 ===================
/**
* 指定账号id是否含有角色标识, 返回true或false
* @param loginId 账号id
* @param role 角色标识
* @return 是否含有指定角色标识
*/
public static boolean hasRole(Object loginId, String role) {
return stpLogic.hasRole(loginId, role);
}
/**
* 当前账号是否含有指定角色标识, 返回true或false
* @param role 角色标识
* @return 是否含有指定角色标识
*/
public static boolean hasRole(String role) {
return stpLogic.hasRole(role);
}
/**
* 当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
* @param role 角色标识
*/
public static void checkRole(String role) {
stpLogic.checkRole(role);
}
/**
* 当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
* @param roleArray 角色标识数组
*/
public static void checkRoleAnd(String... roleArray){
stpLogic.checkRoleAnd(roleArray);
}
/**
* 当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
* @param roleArray 角色标识数组
*/
public static void checkRoleOr(String... roleArray){
stpLogic.checkRoleOr(roleArray);
}
// =================== 权限验证操作 ===================
/**
* 指定账号id是否含有指定权限, 返回true或false
* @param loginId 账号id
* @param permission 权限码
* @return 是否含有指定权限
*/
public static boolean hasPermission(Object loginId, String permission) {
return stpLogic.hasPermission(loginId, permission);
}
/**
* 当前账号是否含有指定权限, 返回true或false
* @param permission 权限码
* @return 是否含有指定权限
*/
public static boolean hasPermission(String permission) {
return stpLogic.hasPermission(permission);
}
/**
* 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
* @param permission 权限码
*/
public static void checkPermission(String permission) {
stpLogic.checkPermission(permission);
}
/**
* 当前账号是否含有指定权限 [指定多个,必须全部验证通过]
* @param permissionArray 权限码数组
*/
public static void checkPermissionAnd(String... permissionArray) {
stpLogic.checkPermissionAnd(permissionArray);
}
/**
* 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
* @param permissionArray 权限码数组
*/
public static void checkPermissionOr(String... permissionArray) {
stpLogic.checkPermissionOr(permissionArray);
}
// =================== id 反查token 相关操作 ===================
/**
* 获取指定loginId的tokenValue
* <p> 在配置为允许并发登录时,此方法只会返回队列的最后一个token,
* 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
* @param loginId 账号id
* @return token值
*/
public static String getTokenValueByLoginId(Object loginId) {
return stpLogic.getTokenValueByLoginId(loginId);
}
/**
* 获取指定loginId指定设备端的tokenValue
* <p> 在配置为允许并发登录时,此方法只会返回队列的最后一个token,
* 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
* @param loginId 账号id
* @param device 设备标识
* @return token值
*/
public static String getTokenValueByLoginId(Object loginId, String device) {
return stpLogic.getTokenValueByLoginId(loginId, device);
}
/**
* 获取指定loginId的tokenValue集合
* @param loginId 账号id
* @return 此loginId的所有相关token
*/
public static List<String> getTokenValueListByLoginId(Object loginId) {
return stpLogic.getTokenValueListByLoginId(loginId);
}
/**
* 获取指定loginId指定设备端的tokenValue集合
* @param loginId 账号id
* @param device 设备标识
* @return 此loginId的所有相关token
*/
public static List<String> getTokenValueListByLoginId(Object loginId, String device) {
return stpLogic.getTokenValueListByLoginId(loginId, device);
}
/**
* 返回当前token的登录设备
* @return 当前令牌的登录设备
*/
public static String getLoginDevice() {
return stpLogic.getLoginDevice();
}
// =================== 会话管理 ===================
/**
* 根据条件查询token
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
* @return token集合
*/
public static List<String> searchTokenValue(String keyword, int start, int size) {
return stpLogic.searchTokenValue(keyword, start, size);
}
/**
* 根据条件查询SessionId
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
* @return sessionId集合
*/
public static List<String> searchSessionId(String keyword, int start, int size) {
return stpLogic.searchSessionId(keyword, start, size);
}
/**
* 根据条件查询token专属Session的Id
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
* @return sessionId集合
*/
public static List<String> searchTokenSessionId(String keyword, int start, int size) {
return stpLogic.searchTokenSessionId(keyword, start, size);
}
// =================== 身份切换 ===================
/**
* 临时切换身份为指定loginId
* @param loginId 指定loginId
*/
public static void switchTo(Object loginId) {
stpLogic.switchTo(loginId);
}
/**
* 结束临时切换身份
*/
public static void endSwitch() {
stpLogic.endSwitch();
}
/**
* 当前是否正处于[身份临时切换]中
* @return 是否正处于[身份临时切换]中
*/
public static boolean isSwitch() {
return stpLogic.isSwitch();
}
/**
* 在一个代码段里方法内,临时切换身份为指定loginId
* @param loginId 指定loginId
* @param function 要执行的方法
*/
public static void switchTo(Object loginId, SaFunction function) {
stpLogic.switchTo(loginId, function);
}
}
@@ -0,0 +1,60 @@
package cn.dev33.satoken.util;
import java.util.Timer;
import java.util.TimerTask;
/**
* 任务调度Util
* @author kong
*
*/
public class SaTaskUtil {
/**
* 延时指定毫秒执行一个函数
* @param fc 要执行的函数
* @param delay 延时的毫秒数
* @return timer任务对象
*/
public static Timer setTimeout(FunctionRunClass fc, int delay) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
fc.run();
timer.cancel();
}
}, delay);
return timer;
}
/**
* 延时delay毫秒,每隔period毫秒执行一个函数
* @param fc 要执行的函数
* @param delay 延时的毫秒数
* @param period 每隔多少毫秒执行一次
* @return timer任务对象
*/
public static Timer setInterval(FunctionRunClass fc, int delay, int period) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
fc.run();
}
}, delay, period);
return timer;
}
/**
* 封装一个内部类,便于操作
* @author kong
*/
public static interface FunctionRunClass{
/**
* 要执行的方法
*/
public void run();
}
}
@@ -0,0 +1,80 @@
package cn.dev33.satoken.util;
/**
* sa-token常量类
* @author kong
*
*/
public class SaTokenConsts {
// =================== sa-token版本信息 ===================
/**
* sa-token 版本号
*/
public static final String VERSION_NO = "v1.13.0";
/**
* sa-token 开源地址
*/
public static final String GITHUB_URL = "https://github.com/click33/sa-token";
// =================== 常量key标记 ===================
/**
* 常量key标记: 如果token为本次请求新创建的,则以此字符串为key存储在当前request中
*/
public static final String JUST_CREATED_SAVE_KEY = "JUST_CREATED_SAVE_KEY_";
/**
* 常量key标记: 如果本次请求已经验证过[无操作过期], 则以此值存储在当前request中
*/
public static final String TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY = "TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY_";
/**
* 常量key标记: 在登录时,默认使用的设备名称
*/
public static final String DEFAULT_LOGIN_DEVICE = "default-device";
/**
* 常量key标记: 在进行临时身份切换时使用的key
*/
public static final String SWITCH_TO_SAVE_KEY = "SWITCH_TO_SAVE_KEY_";
// =================== token-style 相关 ===================
/**
* token风格: uuid
*/
public static final String TOKEN_STYLE_UUID = "uuid";
/**
* token风格: 简单uuid (不带下划线)
*/
public static final String TOKEN_STYLE_SIMPLE_UUID = "simple-uuid";
/**
* token风格: 32位随机字符串
*/
public static final String TOKEN_STYLE_RANDOM_32 = "random-32";
/**
* token风格: 64位随机字符串
*/
public static final String TOKEN_STYLE_RANDOM_64 = "random-64";
/**
* token风格: 128位随机字符串
*/
public static final String TOKEN_STYLE_RANDOM_128 = "random-128";
/**
* token风格: tik风格 (2_14_16)
*/
public static final String TOKEN_STYLE_RANDOM_TIK = "tik";
}
@@ -0,0 +1,117 @@
package cn.dev33.satoken.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
/**
* sa-token 内部代码工具类
*
* @author kong
*
*/
public class SaTokenInsideUtil {
/**
* 打印 sa-token 版本字符画
*/
public static void printSaToken() {
String str = "____ ____ ___ ____ _ _ ____ _ _ \r\n" + "[__ |__| __ | | | |_/ |___ |\\ | \r\n"
+ "___] | | | |__| | \\_ |___ | \\| \r\n" + "sa-token" + SaTokenConsts.VERSION_NO
+ " \r\n" + "GitHub" + SaTokenConsts.GITHUB_URL; // + "\r\n";
System.out.println(str);
}
/**
* 生成指定长度的随机字符串
*
* @param length 字符串的长度
* @return 一个随机字符串
*/
public static String getRandomString(int length) {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
/**
* 指定字符串是否为null或者空字符串
* @param str 指定字符串
* @return 是否为null或者空字符串
*/
public static boolean isEmpty(String str) {
return str == null || "".equals(str);
}
/**
* 以当前时间戳和随机int数字拼接一个随机字符串
*
* @return 随机字符串
*/
public static String getMarking28() {
return System.currentTimeMillis() + "" + new Random().nextInt(Integer.MAX_VALUE);
}
/**
* 从集合里查询数据
*
* @param dataList 数据集合
* @param prefix 前缀
* @param keyword 关键字
* @param start 起始位置 (-1代表查询所有)
* @param size 获取条数
* @return 符合条件的新数据集合
*/
public static List<String> searchList(Collection<String> dataList, String prefix, String keyword, int start,
int size) {
if (prefix == null) {
prefix = "";
}
if (keyword == null) {
keyword = "";
}
// 挑选出所有符合条件的
List<String> list = new ArrayList<String>();
Iterator<String> keys = dataList.iterator();
while (keys.hasNext()) {
String key = keys.next();
if (key.startsWith(prefix) && key.indexOf(keyword) > -1) {
list.add(key);
}
}
// 取指定段数据
return searchList(list, start, size);
}
/**
* 从集合里查询数据
*
* @param list 数据集合
* @param start 起始位置 (-1代表查询所有)
* @param size 获取条数
* @return 符合条件的新数据集合
*/
public static List<String> searchList(List<String> list, int start, int size) {
// 取指定段数据
if (start < 0) {
return list;
}
int end = start + size;
List<String> list2 = new ArrayList<String>();
for (int i = start; i < end; i++) {
if (i >= list.size()) {
return list2;
}
list2.add(list.get(i));
}
return list2;
}
}
+12
View File
@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.factorypath
.idea/
+35
View File
@@ -0,0 +1,35 @@
<?xml version='1.0' encoding='utf-8'?>
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.13.0</version>
</parent>
<packaging>jar</packaging>
<name>sa-token-dao-redis-jackson</name>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<description>sa-token integrate redis (to jackson)</description>
<dependencies>
<!-- sa-token-spring-boot-starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- RedisTemplate 相关操作API -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,226 @@
package cn.dev33.satoken.dao;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
* sa-token持久层的实现类, 基于redis (使用 jackson 序列化方式)
*
* @author kong
*
*/
@Component
public class SaTokenDaoRedisJackson implements SaTokenDao {
/**
* ObjectMapper对象 (以public作用于暴露出此对象,方便开发者二次更改配置)
*/
public ObjectMapper objectMapper;
/**
* string专用
*/
@Autowired
public StringRedisTemplate stringRedisTemplate;
/**
* SaSession专用
*/
public RedisTemplate<String, SaSession> sessionRedisTemplate;
@Autowired
public void setSessionRedisTemplate(RedisConnectionFactory connectionFactory) {
// 指定相应的序列化方案
StringRedisSerializer keySerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer();
// 通过反射获取Mapper对象, 配置[忽略未知字段], 增强兼容性
try {
Field field = GenericJackson2JsonRedisSerializer.class.getDeclaredField("mapper");
field.setAccessible(true);
ObjectMapper objectMapper = (ObjectMapper) field.get(valueSerializer);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.objectMapper = objectMapper;
} catch (Exception e) {
System.err.println(e.getMessage());
}
// 构建RedisTemplate
RedisTemplate<String, SaSession> template = new RedisTemplate<String, SaSession>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(keySerializer);
template.setHashKeySerializer(keySerializer);
template.setValueSerializer(valueSerializer);
template.setHashValueSerializer(valueSerializer);
template.afterPropertiesSet();
if(this.sessionRedisTemplate == null) {
this.sessionRedisTemplate = template;
}
}
/**
* 根据key获取value,如果没有,则返回空
*/
@Override
public String getValue(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
/**
* 写入指定key-value键值对,并设定过期时间(单位:秒)
*/
@Override
public void setValue(String key, String value, long timeout) {
// 判断是否为永不过期
if(timeout == SaTokenDao.NEVER_EXPIRE) {
stringRedisTemplate.opsForValue().set(key, value);
} else {
stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
}
/**
* 修改指定key-value键值对 (过期时间取原来的值)
*/
@Override
public void updateValue(String key, String value) {
long expire = getTimeout(key);
// -2 = 无此键
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.setValue(key, value, expire);
}
/**
* 删除一个指定的key
*/
@Override
public void deleteKey(String key) {
stringRedisTemplate.delete(key);
}
/**
* 根据key获取value,如果没有,则返回空
*/
@Override
public long getTimeout(String key) {
return stringRedisTemplate.getExpire(key);
}
/**
* 修改指定key的剩余存活时间 (单位: 秒)
*/
@Override
public void updateTimeout(String key, long timeout) {
// 判断是否想要设置为永久
if(timeout == SaTokenDao.NEVER_EXPIRE) {
long expire = getTimeout(key);
if(expire == SaTokenDao.NEVER_EXPIRE) {
// 如果其已经被设置为永久,则不作任何处理
} else {
// 如果尚未被设置为永久,那么再次set一次
this.setValue(key, this.getValue(key), timeout);
}
return;
}
stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 根据指定key的Session,如果没有,则返回空
*/
@Override
public SaSession getSession(String sessionId) {
return sessionRedisTemplate.opsForValue().get(sessionId);
}
/**
* 将指定Session持久化
*/
@Override
public void saveSession(SaSession session, long timeout) {
// 判断是否为永不过期
if(timeout == SaTokenDao.NEVER_EXPIRE) {
sessionRedisTemplate.opsForValue().set(session.getId(), session);
} else {
sessionRedisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS);
}
}
/**
* 更新指定session
*/
@Override
public void updateSession(SaSession session) {
long expire = getSessionTimeout(session.getId());
// -2 = 无此键
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.saveSession(session, expire);
}
/**
* 删除一个指定的session
*/
@Override
public void deleteSession(String sessionId) {
sessionRedisTemplate.delete(sessionId);
}
/**
* 获取指定SaSession的剩余存活时间 (单位: 秒)
*/
@Override
public long getSessionTimeout(String sessionId) {
return sessionRedisTemplate.getExpire(sessionId);
}
/**
* 修改指定SaSession的剩余存活时间 (单位: 秒)
*/
@Override
public void updateSessionTimeout(String sessionId, long timeout) {
// 判断是否想要设置为永久
if(timeout == SaTokenDao.NEVER_EXPIRE) {
long expire = getSessionTimeout(sessionId);
if(expire == SaTokenDao.NEVER_EXPIRE) {
// 如果其已经被设置为永久,则不作任何处理
} else {
// 如果尚未被设置为永久,那么再次set一次
this.saveSession(this.getSession(sessionId), timeout);
}
return;
}
sessionRedisTemplate.expire(sessionId, timeout, TimeUnit.SECONDS);
}
/**
* 搜索数据
*/
@Override
public List<String> searchData(String prefix, String keyword, int start, int size) {
Set<String> keys = stringRedisTemplate.keys(prefix + "*" + keyword + "*");
List<String> list = new ArrayList<String>(keys);
return SaTokenInsideUtil.searchList(list, start, size);
}
}
@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.dao.SaTokenDaoRedisJackson
+12
View File
@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.factorypath
.idea/
+35
View File
@@ -0,0 +1,35 @@
<?xml version='1.0' encoding='utf-8'?>
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.13.0</version>
</parent>
<packaging>jar</packaging>
<name>sa-token-dao-redis</name>
<artifactId>sa-token-dao-redis</artifactId>
<description>sa-token integrate redis</description>
<dependencies>
<!-- sa-token-spring-boot-starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- RedisTemplate 相关操作API -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,210 @@
package cn.dev33.satoken.dao;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
* sa-token持久层的实现类, 基于redis
*
* @author kong
*
*/
@Component
public class SaTokenDaoRedis implements SaTokenDao {
/**
* string专用
*/
@Autowired
public StringRedisTemplate stringRedisTemplate;
/**
* SaSession专用
*/
public RedisTemplate<String, SaSession> sessionRedisTemplate;
@Autowired
public void setSessionRedisTemplate(RedisConnectionFactory connectionFactory) {
// 指定相应的序列化方案
StringRedisSerializer keySerializer = new StringRedisSerializer();
JdkSerializationRedisSerializer valueSerializer = new JdkSerializationRedisSerializer();
// 构建RedisTemplate
RedisTemplate<String, SaSession> template = new RedisTemplate<String, SaSession>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(keySerializer);
template.setHashKeySerializer(keySerializer);
template.setValueSerializer(valueSerializer);
template.setHashValueSerializer(valueSerializer);
template.afterPropertiesSet();
if(this.sessionRedisTemplate == null) {
this.sessionRedisTemplate = template;
}
}
/**
* 根据key获取value,如果没有,则返回空
*/
@Override
public String getValue(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
/**
* 写入指定key-value键值对,并设定过期时间(单位:秒)
*/
@Override
public void setValue(String key, String value, long timeout) {
// 判断是否为永不过期
if(timeout == SaTokenDao.NEVER_EXPIRE) {
stringRedisTemplate.opsForValue().set(key, value);
} else {
stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
}
/**
* 修改指定key-value键值对 (过期时间取原来的值)
*/
@Override
public void updateValue(String key, String value) {
long expire = getTimeout(key);
// -2 = 无此键
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.setValue(key, value, expire);
}
/**
* 删除一个指定的key
*/
@Override
public void deleteKey(String key) {
stringRedisTemplate.delete(key);
}
/**
* 根据key获取value,如果没有,则返回空
*/
@Override
public long getTimeout(String key) {
return stringRedisTemplate.getExpire(key);
}
/**
* 修改指定key的剩余存活时间 (单位: 秒)
*/
@Override
public void updateTimeout(String key, long timeout) {
// 判断是否想要设置为永久
if(timeout == SaTokenDao.NEVER_EXPIRE) {
long expire = getTimeout(key);
if(expire == SaTokenDao.NEVER_EXPIRE) {
// 如果其已经被设置为永久,则不作任何处理
} else {
// 如果尚未被设置为永久,那么再次set一次
this.setValue(key, this.getValue(key), timeout);
}
return;
}
stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 根据指定key的Session,如果没有,则返回空
*/
@Override
public SaSession getSession(String sessionId) {
return sessionRedisTemplate.opsForValue().get(sessionId);
}
/**
* 将指定Session持久化
*/
@Override
public void saveSession(SaSession session, long timeout) {
// 判断是否为永不过期
if(timeout == SaTokenDao.NEVER_EXPIRE) {
sessionRedisTemplate.opsForValue().set(session.getId(), session);
} else {
sessionRedisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS);
}
}
/**
* 更新指定session
*/
@Override
public void updateSession(SaSession session) {
long expire = getSessionTimeout(session.getId());
// -2 = 无此键
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.saveSession(session, expire);
}
/**
* 删除一个指定的session
*/
@Override
public void deleteSession(String sessionId) {
sessionRedisTemplate.delete(sessionId);
}
/**
* 获取指定SaSession的剩余存活时间 (单位: 秒)
*/
@Override
public long getSessionTimeout(String sessionId) {
return sessionRedisTemplate.getExpire(sessionId);
}
/**
* 修改指定SaSession的剩余存活时间 (单位: 秒)
*/
@Override
public void updateSessionTimeout(String sessionId, long timeout) {
// 判断是否想要设置为永久
if(timeout == SaTokenDao.NEVER_EXPIRE) {
long expire = getSessionTimeout(sessionId);
if(expire == SaTokenDao.NEVER_EXPIRE) {
// 如果其已经被设置为永久,则不作任何处理
} else {
// 如果尚未被设置为永久,那么再次set一次
this.saveSession(this.getSession(sessionId), timeout);
}
return;
}
sessionRedisTemplate.expire(sessionId, timeout, TimeUnit.SECONDS);
}
/**
* 搜索数据
*/
@Override
public List<String> searchData(String prefix, String keyword, int start, int size) {
Set<String> keys = stringRedisTemplate.keys(prefix + "*" + keyword + "*");
List<String> list = new ArrayList<String>(keys);
return SaTokenInsideUtil.searchList(list, start, size);
}
}
@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.dao.SaTokenDaoRedis
+12
View File
@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath
+86
View File
@@ -0,0 +1,86 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-jwt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.13.0</sa-token-version>
</properties>
<dependencies>
<!-- springboot依赖 -->
<dependency>
<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/ -->
<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>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis</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> -->
<!-- 提供redis连接池 -->
<!-- <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>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,16 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaTokenManager;
@SpringBootApplication
public class SaTokenJwtDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenJwtDemoApplication.class, args);
System.out.println("\n启动成功:sa-token配置如下:" + SaTokenManager.getConfig());
}
}
@@ -0,0 +1,232 @@
package com.pj.satoken.jwt;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.config.SaTokenConfig;
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.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("79e7c69681b8270162386e6daa53d1d3")) {
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.stpLogic.loginKey, NotLoginException.TOKEN_TIMEOUT);
return NotLoginException.TOKEN_TIMEOUT;
} catch (MalformedJwtException e) {
throw NotLoginException.newInstance(StpUtil.stpLogic.loginKey, 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.stpLogic = new StpLogic("login") {
// 重写 (随机生成一个tokenValue)
@Override
public String createTokenValue(Object loginId) {
return SaTokenJwtUtil.createToken(loginId);
}
// 重写 (在当前会话上登录id )
@Override
public void setLoginId(Object loginId, String device) {
// ------ 1、获取相应对象
HttpServletRequest request = SaTokenManager.getSaTokenServlet().getRequest();
SaTokenConfig config = getConfig();
// ------ 2、生成一个token
String tokenValue = createTokenValue(loginId);
request.setAttribute(getKeyJustCreatedSave(), tokenValue); // 将token保存到本次request里
if(config.getIsReadCookie() == true){ // cookie注入
SaTokenManager.getSaTokenCookie().addCookie(SaTokenManager.getSaTokenServlet().getResponse(), 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){
SaTokenManager.getSaTokenCookie().delCookie(SaTokenManager.getSaTokenServlet().getRequest(), SaTokenManager.getSaTokenServlet().getResponse(), 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.loginKey = getLoginKey();
info.tokenTimeout = getTokenTimeout();
// info.sessionTimeout = getSessionTimeout();
// info.tokenSessionTimeout = getTokenSessionTimeout();
// info.tokenActivityTimeout = getTokenActivityTimeout();
info.loginDevice = getLoginDevice();
return info;
}
};
}
}
@@ -0,0 +1,100 @@
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;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
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)
throws Exception {
// 打印堆栈,以供调试
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 { // 普通异常, 输出: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,80 @@
package com.pj.test;
import java.util.Date;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
/**
* 测试专用Controller
* @author kong
*
*/
@RestController
@RequestMapping("/test/")
public class TestJwtController {
// 测试登录接口, 浏览器访问: http://localhost:8081/test/login
@RequestMapping("login")
public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
System.out.println("======================= 进入方法,测试登录接口 ========================= ");
System.out.println("当前会话的token" + StpUtil.getTokenValue());
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前登录账号:" + StpUtil.getLoginIdDefaultNull());
StpUtil.setLoginId(id); // 在当前会话登录此账号
System.out.println("登录成功");
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前登录账号:" + StpUtil.getLoginId());
// System.out.println("当前登录账号并转为int" + StpUtil.getLoginIdAsInt());
System.out.println("当前登录设备:" + StpUtil.getLoginDevice());
// System.out.println("当前token信息:" + StpUtil.getTokenInfo());
return AjaxJson.getSuccess();
}
// 打印当前token信息, 浏览器访问: http://localhost:8081/test/tokenInfo
@RequestMapping("tokenInfo")
public AjaxJson tokenInfo() {
System.out.println("======================= 进入方法,打印当前token信息 ========================= ");
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
System.out.println(tokenInfo);
return AjaxJson.getSuccessData(tokenInfo);
}
// 测试会话session接口, 浏览器访问: http://localhost:8081/test/session
@RequestMapping("session")
public AjaxJson session() throws JsonProcessingException {
System.out.println("======================= 进入方法,测试会话session接口 ========================= ");
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前登录账号session的id" + StpUtil.getSession().getId());
System.out.println("当前登录账号session的id" + StpUtil.getSession().getId());
System.out.println("测试取值name" + StpUtil.getSession().getAttribute("name"));
StpUtil.getSession().setAttribute("name", new Date()); // 写入一个值
System.out.println("测试取值name" + StpUtil.getSession().getAttribute("name"));
System.out.println( new ObjectMapper().writeValueAsString(StpUtil.getSession()));
return AjaxJson.getSuccess();
}
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public AjaxJson test() {
StpUtil.getTokenSession().logout();
StpUtil.logoutByLoginId(10001);
return AjaxJson.getSuccess();
}
}
@@ -1,4 +1,4 @@
package com.pj.test;
package com.pj.util;
import java.io.Serializable;
import java.util.List;
@@ -0,0 +1,45 @@
# 端口
server:
port: 8081
spring:
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
allow-concurrent-login: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# redis配置
redis:
# Redis数据库索引(默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 10000ms
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
+3 -1
View File
@@ -7,4 +7,6 @@ unpackage/
.classpath
.project
.idea/
.idea/
.factorypath
+46 -12
View File
@@ -11,7 +11,13 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.13.0</sa-token-version>
</properties>
<dependencies>
@@ -25,26 +31,54 @@
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 开发测试 -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dev</artifactId>
<version>1.5.1</version>
</dependency> -->
<!-- sa-token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token</artifactId>
<version>1.5.1</version>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- SpringBoot整合redis -->
<!-- sa-token整合redis (使用jdk默认序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis</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> -->
<!-- 提供redis连接池 -->
<!-- <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>
<artifactId>spring-boot-starter-redis</artifactId>
<version>RELEASE</version>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- hutool工具类,用来生成雪花算法唯一id -->
<!-- <dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.4</version>
</dependency> -->
</dependencies>
@@ -0,0 +1,67 @@
package com.pj;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
/**
* 跨域过滤器
* @author kong
*/
@Component
public class CorsFilter implements Filter {
static final String OPTIONS = "OPTIONS";
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 获得客户端domain
String origin = request.getHeader("Origin");
if (origin == null) {
origin = request.getHeader("Referer");
}
// 允许指定域访问跨域资源
response.setHeader("Access-Control-Allow-Origin", origin);
// 允许客户端携带跨域cookie,此时origin值不能为“*”,只能为指定单一域名
response.setHeader("Access-Control-Allow-Credentials", "true");
// 允许所有请求方式
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
// 有效时间
response.setHeader("Access-Control-Max-Age", "3600");
// 允许的header参数
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,satoken");
// 允许的header参数
// response.setHeader("Access-Control-Allow-Headers", "*");
// 如果是预检请求,直接返回
if (OPTIONS.equals(request.getMethod())) {
System.out.println("=======================浏览器发来了OPTIONS预检请求==========");
response.getWriter().print("");
return;
}
// System.out.println("*********************************过滤器被使用**************************2233");
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}
@@ -4,17 +4,13 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.spring.SaTokenSetup;
@SaTokenSetup // 必须有这个注解,用来标注加载sa-token
@SpringBootApplication
public class SaTokenDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenDemoApplication.class, args);
System.out.println("启动成功:sa-token配置如下:" + SaTokenManager.getConfig());
// StpUtil.getSessionByLoginId(10001)
System.out.println("\n启动成功:sa-token配置如下:" + SaTokenManager.getConfig());
}
}
@@ -1,13 +1,11 @@
package com.pj.satoken;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import cn.dev33.satoken.annotation.SaCheckInterceptor;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
/**
* sa-token代码方式进行配置
@@ -16,24 +14,24 @@ import cn.dev33.satoken.config.SaTokenConfig;
public class MySaTokenConfig implements WebMvcConfigurer {
// 获取配置Bean (以代码的方式配置sa-token, 此配置会覆盖yml中的配置 )
@Primary
@Bean(name="MySaTokenConfig")
// @Primary
// @Bean(name="MySaTokenConfig")
public SaTokenConfig getSaTokenConfig() {
SaTokenConfig config = new SaTokenConfig();
config.setTokenName("satoken"); // token名称 (同时也是cookie名称)
config.setTimeout(30 * 24 * 60 * 60); // token有效期,单位s 默认30天
config.setIsShare(true); // 在多人登录同一账号时,是否共享会话 (为true时共用一个,为false时新登录挤掉旧登录)
config.setIsReadBody(true); // 是否尝试从请求体里读取token
config.setIsReadHead(true); // 是否尝试从header里读取token
config.setIsReadCookie(true); // 是否尝试从cookie里读取token
config.setIsV(true); // 是否在初始化配置时打印版本字符画
config.setActivityTimeout(-1); // token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
config.setAllowConcurrentLogin(true); // 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
config.setIsShare(true); // 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
config.setTokenStyle("uuid"); // token风格
return config;
}
// 注册sa-token的拦截器,打开注解式鉴权功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaCheckInterceptor()).addPathPatterns("/**"); // 全局拦截器
// 注册注解拦截器
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
}
@@ -1,103 +0,0 @@
package com.pj.satoken;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.session.SaSession;
/**
* sa-token持久层的实现类 , 基于redis
*/
//@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token与redis的集成
public class SaTokenDaoRedis implements SaTokenDao {
// string专用
@Autowired
StringRedisTemplate stringRedisTemplate;
// SaSession专用
RedisTemplate<String, SaSession> redisTemplate;
@Autowired
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setRedisTemplate(RedisTemplate redisTemplate) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
this.redisTemplate = redisTemplate;
}
// 根据key获取value ,如果没有,则返回空
@Override
public String getValue(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
// 写入指定key-value键值对,并设定过期时间(单位:秒)
@Override
public void setValue(String key, String value, long timeout) {
stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
// 更新指定key-value键值对 (过期时间取原来的值)
@Override
public void updateValue(String key, String value) {
long expire = redisTemplate.getExpire(key);
if(expire == -2) { // -2 = 无此键
return;
}
stringRedisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
}
// 删除一个指定的key
@Override
public void delKey(String key) {
stringRedisTemplate.delete(key);
}
// 根据指定key的session,如果没有,则返回空
@Override
public SaSession getSaSession(String sessionId) {
return redisTemplate.opsForValue().get(sessionId);
}
// 将指定session持久化
@Override
public void saveSaSession(SaSession session, long timeout) {
redisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS);
}
// 更新指定session
@Override
public void updateSaSession(SaSession session) {
long expire = redisTemplate.getExpire(session.getId());
if(expire == -2) { // -2 = 无此键
return;
}
redisTemplate.opsForValue().set(session.getId(), session, expire, TimeUnit.SECONDS);
}
// 删除一个指定的session
@Override
public void deleteSaSession(String sessionId) {
redisTemplate.delete(sessionId);
}
}
@@ -8,15 +8,18 @@ import org.springframework.stereotype.Component;
import cn.dev33.satoken.stp.StpInterface;
/**
* 自定义权限验证接口扩展
* 自定义权限验证接口扩展
*/
@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
// 返回一个账号所拥有的权限码集合
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<Object> getPermissionCodeList(Object login_id, String login_key) {
List<Object> list = new ArrayList<Object>(); // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
public List<String> getPermissionList(Object loginId, String loginKey) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
List<String> list = new ArrayList<String>();
list.add("101");
list.add("user-add");
list.add("user-delete");
@@ -26,4 +29,16 @@ public class StpInterfaceImpl implements StpInterface {
return list;
}
/**
* 返回一个账号所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginKey) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
List<String> list = new ArrayList<String>();
list.add("admin");
list.add("super-admin");
return list;
}
}
@@ -1,238 +0,0 @@
package com.pj.satoken;
import java.util.Map;
import org.springframework.stereotype.Service;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpLogic;
/**
* 一个默认的实现
* @author kong
*/
@Service
public class StpUserUtil {
/**
* 底层的 StpLogic 对象
*/
public static StpLogic stpLogic = new StpLogic("user") {
@Override
public String getKeyTokenName() {
return SaTokenManager.getConfig().getTokenName() + "-user";
}
};
// =================== 获取token 相关 ===================
/**
* 返回token名称
* @return 此StpLogic的token名称
*/
public static String getTokenName() {
return stpLogic.getTokenName();
}
/**
* 获取当前tokenValue
* @return 当前tokenValue
*/
public static String getTokenValue() {
return stpLogic.getTokenValue();
}
/**
* 获取指定id的tokenValue
* @param loginId .
* @return
*/
public static String getTokenValueByLoginId(Object loginId) {
return stpLogic.getTokenValueByLoginId(loginId);
}
/**
* 获取当前会话的token信息:tokenName与tokenValue
* @return 一个Map对象
*/
public static Map<String, String> getTokenInfo() {
return stpLogic.getTokenInfo();
}
// =================== 登录相关操作 ===================
/**
* 在当前会话上登录id
* @param loginId 登录id ,建议的类型:(long | int | String
*/
public static void setLoginId(Object loginId) {
stpLogic.setLoginId(loginId);
}
/**
* 当前会话注销登录
*/
public static void logout() {
stpLogic.logout();
}
/**
* 指定loginId的会话注销登录(踢人下线)
* @param loginId 账号id
*/
public static void logoutByLoginId(Object loginId) {
stpLogic.logoutByLoginId(loginId);
}
// 查询相关
/**
* 获取当前会话是否已经登录
* @return 是否已登录
*/
public static boolean isLogin() {
return stpLogic.isLogin();
}
/**
* 检验当前会话是否已经登录,如未登录,则抛出异常
*/
public static void checkLogin() {
getLoginId();
}
/**
* 获取当前会话登录id, 如果未登录,则抛出异常
* @return .
*/
public static Object getLoginId() {
return stpLogic.getLoginId();
}
/**
* 获取当前会话登录id, 如果未登录,则返回默认值
* @param defaultValue .
* @return .
*/
public static <T> T getLoginId(T defaultValue) {
return stpLogic.getLoginId(defaultValue);
}
/**
* 获取当前会话登录id, 如果未登录,则返回null
* @return
*/
public static Object getLoginIdDefaultNull() {
return stpLogic.getLoginIdDefaultNull();
}
/**
* 获取当前会话登录id, 并转换为String
* @return
*/
public static String getLoginIdAsString() {
return stpLogic.getLoginIdAsString();
}
/**
* 获取当前会话登录id, 并转换为int
* @return
*/
public static int getLoginIdAsInt() {
return stpLogic.getLoginIdAsInt();
}
/**
* 获取当前会话登录id, 并转换为long
* @return
*/
public static long getLoginIdAsLong() {
return stpLogic.getLoginIdAsLong();
}
/**
* 获取指定token对应的登录id,如果未登录,则返回 null
* @return .
*/
public static Object getLoginIdByToken(String tokenValue) {
return stpLogic.getLoginIdByToken(tokenValue);
}
// =================== session相关 ===================
/**
* 获取指定loginId的session, 如果没有,isCreate=是否新建并返回
* @param loginId 登录id
* @param isCreate 是否新建
* @return SaSession
*/
public static SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
return stpLogic.getSessionByLoginId(loginId, isCreate);
}
/**
* 获取指定loginId的session
* @param loginId .
* @return .
*/
public static SaSession getSessionByLoginId(Object loginId) {
return stpLogic.getSessionByLoginId(loginId);
}
/**
* 获取当前会话的session
* @return
*/
public static SaSession getSession() {
return stpLogic.getSession();
}
// =================== 权限验证操作 ===================
/**
* 指定loginId是否含有指定权限
* @param loginId .
* @param pcode .
* @return .
*/
public static boolean hasPermission(Object loginId, Object pcode) {
return stpLogic.hasPermission(loginId, pcode);
}
/**
* 当前会话是否含有指定权限
* @param pcode .
* @return .
*/
public static boolean hasPermission(Object pcode) {
return stpLogic.hasPermission(pcode);
}
/**
* 当前账号是否含有指定权限 , 没有就抛出异常
* @param pcode .
*/
public static void checkPermission(Object pcode) {
stpLogic.checkPermission(pcode);
}
/**
* 当前账号是否含有指定权限 , 【指定多个,必须全都有】
* @param pcodeArray .
*/
public static void checkPermissionAnd(Object... pcodeArray) {
stpLogic.checkPermissionAnd(pcodeArray);
}
/**
* 当前账号是否含有指定权限 , 【指定多个,有一个就可以了】
* @param pcodeArray .
*/
public static void checkPermissionOr(Object... pcodeArray) {
stpLogic.checkPermissionOr(pcodeArray);
}
}
@@ -9,8 +9,11 @@ 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;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
/**
* 全局异常处理
@@ -40,6 +43,9 @@ public class GlobalException {
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());
@@ -0,0 +1,35 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.stp.StpUtil;
/**
* 测试: 同域单点登录
* @author kong
*/
@RestController
@RequestMapping("/sso/")
public class SSOController {
// 测试:进行登录
@RequestMapping("doLogin")
public AjaxJson doLogin(@RequestParam(defaultValue = "10001") String id) {
System.out.println("---------------- 进行登录 ");
StpUtil.setLoginId(id);
return AjaxJson.getSuccess("登录成功: " + id);
}
// 测试:是否登录
@RequestMapping("isLogin")
public AjaxJson isLogin() {
System.out.println("---------------- 是否登录 ");
boolean isLogin = StpUtil.isLogin();
return AjaxJson.getSuccess("是否登录: " + isLogin);
}
}
@@ -0,0 +1,66 @@
package com.pj.test;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.pj.util.AjaxJson;
import com.pj.util.Ttime;
import cn.dev33.satoken.stp.StpUtil;
/**
* 压力测试
* @author kong
*
*/
@RestController
@RequestMapping("/s-test/")
public class StressTestController {
// 测试 浏览器访问: http://localhost:8081/s-test/login
// 测试前,请先将 is-read-cookie 配置为 false
@RequestMapping("login")
public AjaxJson login() {
// StpUtil.getTokenSession().logout();
// StpUtil.logoutByLoginId(10001);
int count = 10; // 循环多少轮
int loginCount = 10000; // 每轮循环多少次
// 循环10次 取平均时间
List<Double> list = new ArrayList<>();
for (int i = 1; i <= count; i++) {
System.out.println("\n---------------------第" + i + "轮---------------------");
Ttime t = new Ttime().start();
// 每次登录的次数
for (int j = 1; j <= loginCount; j++) {
StpUtil.setLoginId("1000" + j, "PC-" + j);
if(j % 1000 == 0) {
System.out.println("已登录:" + j);
}
}
t.end();
list.add((t.returnMs() + 0.0) / 1000);
System.out.println("" + i + "" + "用时:" + t.toString());
}
// System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size());
System.out.println("\n---------------------测试结果---------------------");
System.out.println(list.size() + "次测试: " + list);
double ss = 0;
for (int i = 0; i < list.size(); i++) {
ss += list.get(i);
}
System.out.println("平均用时: " + ss / list.size());
return AjaxJson.getSuccess();
}
}
@@ -1,12 +1,24 @@
package com.pj.test;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pj.util.AjaxJson;
import com.pj.util.Ttime;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.session.SaSessionCustomUtil;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
/**
@@ -18,6 +30,7 @@ import cn.dev33.satoken.stp.StpUtil;
@RequestMapping("/test/")
public class TestController {
// 测试登录接口, 浏览器访问: http://localhost:8081/test/login
@RequestMapping("login")
public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
@@ -25,31 +38,54 @@ public class TestController {
System.out.println("当前会话的token" + StpUtil.getTokenValue());
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前登录账号:" + StpUtil.getLoginIdDefaultNull());
StpUtil.setLoginId(id); // 在当前会话登录此账号
System.out.println("登录成功");
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前登录账号:" + StpUtil.getLoginId());
System.out.println("当前登录账号:" + StpUtil.getLoginIdAsInt()); // 获取登录id并转为int
// StpUtil.logout();
// System.out.println("注销登录");
// System.out.println("当前是否登录:" + StpUtil.isLogin());
// System.out.println("当前登录账号:" + StpUtil.getLoginIdDefaultNull());
// StpUtil.setLoginId(id); // 在当前会话登录此账号
// System.out.println("根据token找登录id" + StpUtil.getLoginIdByToken(StpUtil.getTokenValue()));
System.out.println("当前token信息:" + StpUtil.getTokenInfo()); // 获取登录id并转为int
System.out.println("当前登录账号:" + StpUtil.getLoginIdDefaultNull());
// System.out.println("当前登录账号并转为int" + StpUtil.getLoginIdAsInt());
System.out.println("当前登录设备:" + StpUtil.getLoginDevice());
// System.out.println("当前token信息:" + StpUtil.getTokenInfo());
return AjaxJson.getSuccess();
}
// 测试权限接口 浏览器访问: http://localhost:8081/test/jur
@RequestMapping("jur")
public AjaxJson jur() {
// 测试退出登录 浏览器访问: http://localhost:8081/test/logout
@RequestMapping("logout")
public AjaxJson logout() {
StpUtil.logout();
// StpUtil.logoutByLoginId(10001);
return AjaxJson.getSuccess();
}
// 测试角色接口, 浏览器访问: http://localhost:8081/test/testRole
@RequestMapping("testRole")
public AjaxJson testRole() {
System.out.println("======================= 进入方法,测试角色接口 ========================= ");
System.out.println("是否具有角色标识 user " + StpUtil.hasRole("user"));
System.out.println("是否具有角色标识 admin " + StpUtil.hasRole("admin"));
System.out.println("没有admin权限就抛出异常");
StpUtil.checkRole("admin");
System.out.println("在【admin、user】中只要拥有一个就不会抛出异常");
StpUtil.checkRoleOr("admin", "user");
System.out.println("在【admin、user】中必须全部拥有才不会抛出异常");
StpUtil.checkRoleAnd("admin", "user");
System.out.println("角色测试通过");
return AjaxJson.getSuccess();
}
// 测试权限接口, 浏览器访问: http://localhost:8081/test/testJur
@RequestMapping("testJur")
public AjaxJson testJur() {
System.out.println("======================= 进入方法,测试权限接口 ========================= ");
System.out.println("是否具有权限101" + StpUtil.hasPermission(101));
System.out.println("是否具有权限101" + StpUtil.hasPermission("101"));
System.out.println("是否具有权限user-add" + StpUtil.hasPermission("user-add"));
System.out.println("是否具有权限article-get" + StpUtil.hasPermission("article-get"));
@@ -69,14 +105,15 @@ public class TestController {
// 测试会话session接口, 浏览器访问: http://localhost:8081/test/session
@RequestMapping("session")
public AjaxJson session() {
public AjaxJson session() throws JsonProcessingException {
System.out.println("======================= 进入方法,测试会话session接口 ========================= ");
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前登录账号session的id" + StpUtil.getSession().getId());
System.out.println("当前登录账号session的id" + StpUtil.getSession().getId());
System.out.println("测试取值name" + StpUtil.getSession().getAttribute("name"));
StpUtil.getSession().setAttribute("name", "张三"); // 写入一个值
StpUtil.getSession().setAttribute("name", new Date()); // 写入一个值
System.out.println("测试取值name" + StpUtil.getSession().getAttribute("name"));
System.out.println( new ObjectMapper().writeValueAsString(StpUtil.getSession()));
return AjaxJson.getSuccess();
}
@@ -93,67 +130,132 @@ public class TestController {
return AjaxJson.getSuccess();
}
// ----------
// 测试token专属session 浏览器访问: http://localhost:8081/test/getTokenSession
@RequestMapping("getTokenSession")
public AjaxJson getTokenSession() {
System.out.println("======================= 进入方法,测试会话session接口 ========================= ");
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前token专属session: " + StpUtil.getTokenSession().getId());
System.out.println("测试取值name" + StpUtil.getTokenSession().getAttribute("name"));
StpUtil.getTokenSession().setAttribute("name", "张三"); // 写入一个值
System.out.println("测试取值name" + StpUtil.getTokenSession().getAttribute("name"));
return AjaxJson.getSuccess();
}
// 打印当前token信息, 浏览器访问: http://localhost:8081/test/tokenInfo
@RequestMapping("tokenInfo")
public AjaxJson tokenInfo() {
System.out.println("======================= 进入方法,打印当前token信息 ========================= ");
System.out.println(StpUtil.getTokenInfo());
return AjaxJson.getSuccess();
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
System.out.println(tokenInfo);
return AjaxJson.getSuccessData(tokenInfo);
}
// 测试注解式鉴权, 浏览器访问: http://localhost:8081/test/atCheck
@SaCheckLogin // 注解式鉴权:当前会话必须登录才能通过
@SaCheckRole("super-admin") // 注解式鉴权:当前会话必须具有指定角色标识才能通过
@SaCheckPermission("user-add") // 注解式鉴权:当前会话必须具有指定权限才能通过
@RequestMapping("atCheck")
public AjaxJson atCheck() {
System.out.println("======================= 进入方法,测试注解鉴权接口 ========================= ");
System.out.println("只有通过注解鉴权,才能进入此方法");
// StpUtil.checkActivityTimeout();
// StpUtil.updateLastActivityToNow();
return AjaxJson.getSuccess();
}
// 测试注解式鉴权, 浏览器访问: http://localhost:8081/test/getInfo
@SaCheckLogin // 注解式鉴权:当前会话必须登录才能通过
@RequestMapping("getInfo")
public AjaxJson getInfo() {
// 测试注解式鉴权, 浏览器访问: http://localhost:8081/test/atJurOr
@RequestMapping("atJurOr")
@SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR) // 注解式鉴权:只要具有其中一个权限即可通过校验
public AjaxJson atJurOr() {
return AjaxJson.getSuccessData("用户信息");
}
// [活动时间] 续签: http://localhost:8081/test/rene
@RequestMapping("rene")
public AjaxJson rene() {
StpUtil.checkActivityTimeout();
StpUtil.updateLastActivityToNow();
return AjaxJson.getSuccess("续签成功");
}
// 测试踢人下线 浏览器访问: http://localhost:8081/test/kickOut
@RequestMapping("kickOut")
public AjaxJson kickOut() {
// 先登录上
StpUtil.setLoginId(10001);
// 清退下线
// StpUtil.logoutByLoginId(10001);
// 踢下线
StpUtil.kickoutByLoginId(10001);
StpUtil.logoutByLoginId(10001);
// 再尝试获取
StpUtil.getLoginId();
// 返回
return AjaxJson.getSuccess();
}
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public AjaxJson test() {
StpUtil.setLoginId(10001);
// StpUtil.getSession();
StpUtil.logout();
// System.out.println(StpUtil.getSession().getId());
// System.out.println(StpUserUtil.getSession().getId());
// StpUtil.getSessionByLoginId(10001).setAttribute("name", "123");
// System.out.println(StpUtil.getSessionByLoginId(10001).getAttribute("name"));
// 测试登录接口, 按照设备登录, 浏览器访问: http://localhost:8081/test/login2
@RequestMapping("login2")
public AjaxJson login2(@RequestParam(defaultValue="10001") String id, @RequestParam(defaultValue="PC") String device) {
StpUtil.setLoginId(id, device);
return AjaxJson.getSuccess();
}
// 测试身份临时切换: http://localhost:8081/test/switchTo
@RequestMapping("switchTo")
public AjaxJson switchTo() {
System.out.println("当前会话身份:" + StpUtil.getLoginIdDefaultNull());
System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch());
StpUtil.switchTo(10044, () -> {
System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch());
System.out.println("当前会话身份已被切换为:" + StpUtil.getLoginId());
});
System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch());
return AjaxJson.getSuccess();
}
// 测试会话治理 浏览器访问: http://localhost:8081/test/search
@RequestMapping("search")
public AjaxJson search() {
System.out.println("--------------");
Ttime t = new Ttime().start();
List<String> tokenValue = StpUtil.searchTokenValue("8feb8265f773", 0, 10);
for (String v : tokenValue) {
// SaSession session = StpUtil.getSessionBySessionId(sid);
System.out.println(v);
}
System.out.println("用时:" + t.end().toString());
return AjaxJson.getSuccess();
}
@Autowired
TestService TestService;
// 测试AOP注解鉴权: http://localhost:8081/test/testAOP
@RequestMapping("testAOP")
public AjaxJson testAOP() {
System.out.println("testAOP");
TestService.getList();
return AjaxJson.getSuccess();
}
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public AjaxJson test() {
// StpUtil.getTokenSession().logout();
// StpUtil.logoutByLoginId(10001);
StpUtil.getLoginId();
return AjaxJson.getSuccess();
}
// 测试 浏览器访问: http://localhost:8081/test/test2
@RequestMapping("test2")
public AjaxJson test2() {
return AjaxJson.getSuccess();
}
}
@@ -0,0 +1,25 @@
package com.pj.test;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import cn.dev33.satoken.annotation.SaCheckLogin;
/**
* 用来测试AOP注解鉴权
* @author kong
*
*/
@Service
public class TestService {
@SaCheckLogin
public List<String> getList() {
System.out.println("getList");
return new ArrayList<String>();
}
}
@@ -0,0 +1,22 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
@RestController
@RequestMapping("user")
public class UserController {
// 测试 浏览器访问: http://localhost:8081/user/doLogin
@RequestMapping("doLogin")
public String test(String username, String password) {
if("zhang".equals(username) && "123456".equals(password)) {
StpUtil.setLoginId(10001);
return "登录成功";
}
return "登录失败";
}
}
@@ -0,0 +1,162 @@
package com.pj.util;
import java.io.Serializable;
import java.util.List;
/**
* ajax请求返回Json格式数据的封装
*/
public class AjaxJson implements Serializable{
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200; // 成功状态码
public static final int CODE_ERROR = 500; // 错误状态码
public static final int CODE_WARNING = 501; // 警告状态码
public static final int CODE_NOT_JUR = 403; // 无权限状态码
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
public int code; // 状态码
public String msg; // 描述信息
public Object data; // 携带对象
public Long dataCount; // 数据总数,用于分页
/**
* 返回code
* @return
*/
public int getCode() {
return this.code;
}
/**
* 给msg赋值,连缀风格
*/
public AjaxJson setMsg(String msg) {
this.msg = msg;
return this;
}
public String getMsg() {
return this.msg;
}
/**
* 给data赋值,连缀风格
*/
public AjaxJson setData(Object data) {
this.data = data;
return this;
}
/**
* 将data还原为指定类型并返回
*/
@SuppressWarnings("unchecked")
public <T> T getData(Class<T> cs) {
return (T) data;
}
// ============================ 构建 ==================================
public AjaxJson(int code, String msg, Object data, Long dataCount) {
this.code = code;
this.msg = msg;
this.data = data;
this.dataCount = dataCount;
}
// 返回成功
public static AjaxJson getSuccess() {
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
}
public static AjaxJson getSuccess(String msg) {
return new AjaxJson(CODE_SUCCESS, msg, null, null);
}
public static AjaxJson getSuccess(String msg, Object data) {
return new AjaxJson(CODE_SUCCESS, msg, data, null);
}
public static AjaxJson getSuccessData(Object data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
public static AjaxJson getSuccessArray(Object... data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
// 返回失败
public static AjaxJson getError() {
return new AjaxJson(CODE_ERROR, "error", null, null);
}
public static AjaxJson getError(String msg) {
return new AjaxJson(CODE_ERROR, msg, null, null);
}
// 返回警告
public static AjaxJson getWarning() {
return new AjaxJson(CODE_ERROR, "warning", null, null);
}
public static AjaxJson getWarning(String msg) {
return new AjaxJson(CODE_WARNING, msg, null, null);
}
// 返回未登录
public static AjaxJson getNotLogin() {
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
}
// 返回没有权限的
public static AjaxJson getNotJur(String msg) {
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
}
// 返回一个自定义状态码的
public static AjaxJson get(int code, String msg){
return new AjaxJson(code, msg, null, null);
}
// 返回分页和数据的
public static AjaxJson getPageData(Long dataCount, Object data){
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
}
// 返回,根据受影响行数的(大于0=ok,小于0=error)
public static AjaxJson getByLine(int line){
if(line > 0){
return getSuccess("ok", line);
}
return getError("error").setData(line);
}
// 返回,根据布尔值来确定最终结果的 (true=okfalse=error)
public static AjaxJson getByBoolean(boolean b){
return b ? getSuccess("ok") : getError("error");
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@SuppressWarnings("rawtypes")
@Override
public String toString() {
String data_string = null;
if(data == null){
} else if(data instanceof List){
data_string = "List(length=" + ((List)data).size() + ")";
} else {
data_string = data.toString();
}
return "{"
+ "\"code\": " + this.getCode()
+ ", \"msg\": \"" + this.getMsg() + "\""
+ ", \"data\": " + data_string
+ ", \"dataCount\": " + dataCount
+ "}";
}
}
@@ -0,0 +1,63 @@
package com.pj.util;
/**
* 用于测试用时
* @author kong
*
*/
public class Ttime {
private long start=0; //开始时间
private long end=0; //结束时间
public static Ttime t = new Ttime(); //static快捷使用
/**
* 开始计时
* @return
*/
public Ttime start() {
start=System.currentTimeMillis();
return this;
}
/**
* 结束计时
*/
public Ttime end() {
end=System.currentTimeMillis();
return this;
}
/**
* 返回所用毫秒数
*/
public long returnMs() {
return end-start;
}
/**
* 格式化输出结果
*/
public void outTime() {
System.out.println(this.toString());
}
/**
* 结束并格式化输出结果
*/
public void endOutTime() {
this.end().outTime();
}
@Override
public String toString() {
return (returnMs() + 0.0) / 1000 + "s"; // 格式化为:0.01s
}
}
@@ -7,24 +7,22 @@ spring:
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位s 默认30天
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# 在多人登录同一账号时,是否共享会话 (为true时共用一个,为false时新登录挤掉旧登录)
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
allow-concurrent-login: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# 是否尝试从请求体里读取token
is-read-body: true
# 是否尝试从header里读取token
is-read-head: true
# 是否尝试从cookie里读取token
is-read-cookie: true
# 是否在初始化配置时打印版本字符画
is-v: true
# token风格
token-style: uuid
# redis配置
redis:
# Redis数据库索引(默认为0
database: 1
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
@@ -32,7 +30,7 @@ spring:
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 1000ms
timeout: 10000ms
lettuce:
pool:
# 连接池最大连接数
@@ -1,128 +0,0 @@
package cn.dev33.satoken;
import cn.dev33.satoken.action.SaTokenAction;
import cn.dev33.satoken.action.SaTokenActionDefaultImpl;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.config.SaTokenConfigFactory;
import cn.dev33.satoken.cookie.SaCookieOper;
import cn.dev33.satoken.cookie.SaCookieOperDefaultImpl;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.SaTokenDaoDefaultImpl;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
* 管理sa-token所有对象
* @author kong
*
*/
public class SaTokenManager {
/**
* 配置文件 Bean
*/
private static SaTokenConfig config;
public static SaTokenConfig getConfig() {
if (config == null) {
initConfig();
}
return config;
}
public static void setConfig(SaTokenConfig config) {
SaTokenManager.config = config;
if(config.getIsV()) {
SaTokenInsideUtil.printSaToken();
}
}
public synchronized static void initConfig() {
if (config == null) {
setConfig(SaTokenConfigFactory.createConfig());
}
}
/**
* 持久化 Bean
*/
public static SaTokenDao dao;
public static SaTokenDao getDao() {
if (dao == null) {
initDao();
}
return dao;
}
public static void setDao(SaTokenDao dao) {
SaTokenManager.dao = dao;
}
public synchronized static void initDao() {
if (dao == null) {
setDao(new SaTokenDaoDefaultImpl());
}
}
/**
* 权限认证 Bean
*/
public static StpInterface stp;
public static StpInterface getStp() {
if (stp == null) {
initStp();
}
return stp;
}
public static void setStp(StpInterface stp) {
SaTokenManager.stp = stp;
}
public synchronized static void initStp() {
if (stp == null) {
setStp(new StpInterfaceDefaultImpl());
}
}
/**
* sa-token行为 Bean
*/
public static SaTokenAction sta;
public static SaTokenAction getSta() {
if (sta == null) {
initSta();
}
return sta;
}
public static void setSta(SaTokenAction sta) {
SaTokenManager.sta = sta;
}
public synchronized static void initSta() {
if (sta == null) {
setSta(new SaTokenActionDefaultImpl());
}
}
/**
* sa-token cookie操作 Bean
*/
public static SaCookieOper saCookieOper;
public static SaCookieOper getSaCookieOper() {
if (saCookieOper == null) {
initgetSaCookieOper();
}
return saCookieOper;
}
public static void setSaCookieOper(SaCookieOper saCookieOper) {
SaTokenManager.saCookieOper = saCookieOper;
}
public synchronized static void initgetSaCookieOper() {
if (saCookieOper == null) {
setSaCookieOper(new SaCookieOperDefaultImpl());
}
}
}
@@ -1,30 +0,0 @@
package cn.dev33.satoken.action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface SaTokenAction {
/**
* 获取当前请求的Request对象
* @return 当前请求的Request对象
*/
public HttpServletRequest getCurrRequest();
/**
* 获取当前会话的 response
* @return 当前请求的response
*/
public HttpServletResponse getResponse();
/**
* 生成一个token
* @param loginId 账号id
* @param loginKey 登录标识key
* @return 一个token
*/
public String createToken(Object loginId, String loginKey);
}
@@ -1,47 +0,0 @@
package cn.dev33.satoken.action;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.dev33.satoken.util.SpringMvcUtil;
/**
* 对 SaTokenAction 接口的默认实现
* @author kong
*
*/
public class SaTokenActionDefaultImpl implements SaTokenAction {
/**
* 获取当前请求的Request对象
*/
@Override
public HttpServletRequest getCurrRequest() {
return SpringMvcUtil.getRequest();
}
/**
* 获取当前请求的Response对象
*/
@Override
public HttpServletResponse getResponse() {
return SpringMvcUtil.getResponse();
}
/**
* 生成一个token
*/
@Override
public String createToken(Object loginId, String loginKey) {
return UUID.randomUUID().toString();
}
}
@@ -1,109 +0,0 @@
package cn.dev33.satoken.annotation;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
/**
* 注解式鉴权 - 拦截器
* @author kong
*/
public class SaCheckInterceptor implements HandlerInterceptor {
/**
* 底层的 StpLogic 对象
*/
public StpLogic stpLogic = null;
/**
* 创建,并指定一个默认的 StpLogic
*/
public SaCheckInterceptor() {
this(StpUtil.stpLogic);
}
/**
* 创建,并指定一个的 StpLogic
* @param stpLogic 指定的StpLogic
*/
public SaCheckInterceptor(StpLogic stpLogic) {
this.stpLogic = stpLogic;
}
/**
* 每次请求之前触发
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 获取处理method
if (handler instanceof HandlerMethod == false) {
return true;
}
HandlerMethod method = (HandlerMethod ) handler;
// 验证登录
if(method.hasMethodAnnotation(SaCheckLogin.class) || method.getBeanType().isAnnotationPresent(SaCheckLogin.class)) {
stpLogic.checkLogin();
}
// 获取权限注解
SaCheckPermission scp = method.getMethodAnnotation(SaCheckPermission.class);
if(scp == null) {
scp = method.getBeanType().getAnnotation(SaCheckPermission.class);
}
if(scp == null) {
return true;
}
// 开始验证权限
Object[] codeArray = concatAbc(scp.value(), scp.valueInt(), scp.valueLong());
if(scp.isAnd()) {
stpLogic.checkPermissionAnd(codeArray); // 必须全部都有
} else {
stpLogic.checkPermissionOr(codeArray); // 有一个就行了
}
return true;
}
/**
* 合并三个数组
* @param a .
* @param b .
* @param c .
* @return .
*/
private Object[] concatAbc(String[] a, int[] b, long[] c) {
// 循环赋值
Object[] d = new Object[a.length + b.length + c.length];
for (int i = 0; i < a.length; i++) {
d[i] = a[i];
}
for (int i = 0; i < b.length; i++) {
d[a.length + i] = b[i];
}
for (int i = 0; i < c.length; i++) {
d[a.length + b.length + i] = c[i];
}
return d;
}
}
@@ -1,41 +0,0 @@
package cn.dev33.satoken.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 标注一个路由方法,当前会话必须具有指定权限才可以通过
* @author kong
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface SaCheckPermission {
/**
* 权限码数组 String类型
* @return .
*/
String [] value() default {};
/**
* 权限码数组 int类型
* @return .
*/
int [] valueInt() default {};
/**
* 权限码数组 long类型
* @return .
*/
long [] valueLong() default {};
/**
* 是否属于and型验证 ,true=必须全部具有,false=只要具有一个就可以通过
* @return .
*/
boolean isAnd() default true;
}
@@ -1,138 +0,0 @@
package cn.dev33.satoken.config;
/**
* sa-token 总配置类
* @author kong
*
*/
public class SaTokenConfig {
private String tokenName = "satoken"; // token名称 (同时也是cookie名称)
private long timeout = 30 * 24 * 60 * 60; // token有效期,单位s 默认30天
private Boolean isShare = true; // 在多人登录同一账号时,是否共享会话 (为true时共用一个,为false时新登录挤掉旧登录)
private Boolean isReadBody = true; // 是否尝试从请求体里读取token
private Boolean isReadHead = true; // 是否尝试从header里读取token
private Boolean isReadCookie = true; // 是否尝试从cookie里读取token
private Boolean isV = true; // 是否在初始化配置时打印版本字符画
/**
* @return tokenName
*/
public String getTokenName() {
return tokenName;
}
/**
* @param tokenName 要设置的 tokenName
*/
public void setTokenName(String tokenName) {
this.tokenName = tokenName;
}
/**
* @return timeout
*/
public long getTimeout() {
return timeout;
}
/**
* @param timeout 要设置的 timeout
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* @return isShare
*/
public Boolean getIsShare() {
return isShare;
}
/**
* @param isShare 要设置的 isShare
*/
public void setIsShare(Boolean isShare) {
this.isShare = isShare;
}
/**
* @return isReadCookie
*/
public Boolean getIsReadCookie() {
return isReadCookie;
}
/**
* @param isReadCookie 要设置的 isReadCookie
*/
public void setIsReadCookie(Boolean isReadCookie) {
this.isReadCookie = isReadCookie;
}
/**
* @return isReadHead
*/
public Boolean getIsReadHead() {
return isReadHead;
}
/**
* @param isReadHead 要设置的 isReadHead
*/
public void setIsReadHead(Boolean isReadHead) {
this.isReadHead = isReadHead;
}
/**
* @return isReadBody
*/
public Boolean getIsReadBody() {
return isReadBody;
}
/**
* @param isReadBody 要设置的 isReadBody
*/
public void setIsReadBody(Boolean isReadBody) {
this.isReadBody = isReadBody;
}
/**
* @return isV
*/
public Boolean getIsV() {
return isV;
}
/**
* @param isV 要设置的 isV
*/
public void setIsV(Boolean isV) {
this.isV = isV;
}
@Override
public String toString() {
return "SaTokenConfig [tokenName=" + tokenName + ", timeout=" + timeout + ", isShare=" + isShare
+ ", isReadBody=" + isReadBody + ", isReadHead=" + isReadHead + ", isReadCookie=" + isReadCookie
+ ", isV=" + isV + "]";
}
}
@@ -1,135 +0,0 @@
package cn.dev33.satoken.config;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 创建一个配置文件
* @author kong
*
*/
public class SaTokenConfigFactory {
/**
* 默认配置文件地址
*/
public static String configPath = "sa-token.properties";
/**
* 根据指定路径获取配置信息
* @return 一个SaTokenConfig对象
*/
public static SaTokenConfig createConfig() {
Map<String, String> map = readPropToMap(configPath);
if(map == null){
// throw new RuntimeException("找不到配置文件:" + configPath, null);
}
return (SaTokenConfig)initPropByMap(map, new SaTokenConfig());
}
/**
* 将指定路径的properties配置文件读取到Map中
* @param propertiesPath 配置文件地址
* @return 一个Map
*/
private static Map<String, String> readPropToMap(String propertiesPath){
Map<String, String> map = new HashMap<String, String>();
try {
InputStream is = SaTokenConfigFactory.class.getClassLoader().getResourceAsStream(propertiesPath);
if(is == null){
return null;
}
Properties prop = new Properties();
prop.load(is);
for (String key : prop.stringPropertyNames()) {
map.put(key, prop.getProperty(key));
}
} catch (IOException e) {
throw new RuntimeException("配置文件(" + propertiesPath + ")加载失败", e);
}
return map;
}
/**
* 将 Map 的值映射到 Model 上
* @param map 属性集合
* @param obj 对象,或类型
* @return 返回实例化后的对象
*/
private static Object initPropByMap(Map<String, String> map, Object obj){
if(map == null){
map = new HashMap<String, String>();
}
// 1、取出类型
Class<?> cs = null;
if(obj instanceof Class){ // 如果是一个类型,则将obj=null,以便完成静态属性反射赋值
cs = (Class<?>)obj;
obj = null;
}else{ // 如果是一个对象,则取出其类型
cs = obj.getClass();
}
// 2、遍历类型属性,反射赋值
for (Field field : cs.getDeclaredFields()) {
String value = map.get(field.getName());
if (value == null) {
continue; // 如果为空代表没有配置此项
}
try {
Object valueConvert = getObjectByClass(value, field.getType()); // 转换值类型
field.setAccessible(true);
field.set(obj, valueConvert);
} catch (IllegalArgumentException e) {
throw new RuntimeException("属性赋值出错:" + field.getName(), e);
} catch (IllegalAccessException e) {
throw new RuntimeException("属性赋值出错:" + field.getName(), e);
}
}
return obj;
}
/**
* 将字符串转化为指定数据类型
* @param str 值
* @param cs 要转换的类型
* @return .
*/
@SuppressWarnings("unchecked")
private static <T>T getObjectByClass(String str, Class<T> cs){
Object value = null;
if(str == null){
value = null;
}else if (cs.equals(String.class)) {
value = str;
} else if (cs.equals(int.class)||cs.equals(Integer.class)) {
value = new Integer(str);
} else if (cs.equals(long.class)||cs.equals(Long.class)) {
value = new Long(str);
} else if (cs.equals(short.class)||cs.equals(Short.class)) {
value = new Short(str);
} else if (cs.equals(float.class)||cs.equals(Float.class)) {
value = new Float(str);
} else if (cs.equals(double.class)||cs.equals(Double.class)) {
value = new Double(str);
} else if (cs.equals(boolean.class)||cs.equals(Boolean.class)) {
value = new Boolean(str);
}else{
throw new RuntimeException("未能将值:" + str + ",转换类型为:" + cs, null);
}
return (T)value;
}
}
@@ -1,53 +0,0 @@
package cn.dev33.satoken.cookie;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* sa-token 对cookie的相关操作 接口类
* @author kong
*
*/
public interface SaCookieOper {
/**
* 获取指定cookie .
*
* @param request .
* @param cookieName .
* @return .
*/
public Cookie getCookie(HttpServletRequest request, String cookieName);
/**
* 添加cookie
*
* @param response .
* @param name .
* @param value .
* @param path .
* @param timeout .
*/
public void addCookie(HttpServletResponse response, String name, String value, String path, int timeout);
/**
* 删除cookie .
*
* @param request .
* @param response .
* @param name .
*/
public void delCookie(HttpServletRequest request, HttpServletResponse response, String name);
/**
* 修改cookie的value值
*
* @param request .
* @param response .
* @param name .
* @param value .
*/
public void updateCookie(HttpServletRequest request, HttpServletResponse response, String name, String value);
}
@@ -1,70 +0,0 @@
package cn.dev33.satoken.dao;
import cn.dev33.satoken.session.SaSession;
/**
* sa-token持久层的接口
* @author kong
*
*/
public interface SaTokenDao {
/**
* 根据key获取value ,如果没有,则返回空
* @param key 键名称
* @return value
*/
public String getValue(String key);
/**
* 写入指定key-value键值对,并设定过期时间 (单位:秒)
* @param key 键名称
* @param value 值
* @param timeout 过期时间,单位:s
*/
public void setValue(String key, String value, long timeout);
/**
* 修改指定key-value键值对 (过期时间取原来的值)
* @param key 键名称
* @param value 值
*/
public void updateValue(String key, String value);
/**
* 删除一个指定的key
* @param key 键名称
*/
public void delKey(String key);
/**
* 根据指定key的session,如果没有,则返回空
* @param sessionId 键名称
* @return SaSession
*/
public SaSession getSaSession(String sessionId);
/**
* 将指定session持久化
* @param session 要保存的session对象
* @param timeout 过期时间,单位: s
*/
public void saveSaSession(SaSession session, long timeout);
/**
* 更新指定session
* @param session 要更新的session对象
*/
public void updateSaSession(SaSession session);
/**
* 删除一个指定的session
* @param sessionId sessionId
*/
public void deleteSaSession(String sessionId);
}
@@ -1,69 +0,0 @@
package cn.dev33.satoken.dao;
import java.util.HashMap;
import java.util.Map;
import cn.dev33.satoken.session.SaSession;
/**
* sa-token持久层默认的实现类 , 基于内存Map
* @author kong
*
*/
public class SaTokenDaoDefaultImpl implements SaTokenDao {
/**
* 所有数据集合
*/
Map<String, Object> dataMap = new HashMap<String, Object>();
@Override
public String getValue(String key) {
return (String)dataMap.get(key);
}
@Override
public void setValue(String key, String value, long timeout) {
dataMap.put(key, value);
}
@Override
public void updateValue(String key, String value) {
this.setValue(key, value, 0);
}
@Override
public void delKey(String key) {
dataMap.remove(key);
}
@Override
public SaSession getSaSession(String sessionId) {
return (SaSession)dataMap.get(sessionId);
}
@Override
public void saveSaSession(SaSession session, long timeout) {
dataMap.put(session.getId(), session);
}
@Override
public void updateSaSession(SaSession session) {
// 无动作
}
@Override
public void deleteSaSession(String sessionId) {
dataMap.remove(sessionId);
}
}
@@ -1,52 +0,0 @@
package cn.dev33.satoken.exception;
import cn.dev33.satoken.stp.StpUtil;
/**
* 没有指定权限码,抛出的异常
* @author kong
*
*/
public class NotPermissionException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 6806129545290130142L;
/**
* 权限码
*/
private Object code;
/**
* @return 获得权限码
*/
public Object getCode() {
return code;
}
/**
* login_key
*/
private String loginKey;
/**
* 获得login_key
* @return login_key
*/
public String getLoginKey() {
return loginKey;
}
public NotPermissionException(Object code) {
this(code, StpUtil.stpLogic.loginKey);
}
public NotPermissionException(Object code, String loginKey) {
// 这里到底要不要拼接上login_key呢?纠结
super("无此权限:" + code);
this.code = code;
this.loginKey = loginKey;
}
}
@@ -1,151 +0,0 @@
package cn.dev33.satoken.session;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import cn.dev33.satoken.SaTokenManager;
/**
* session会话
* @author kong
*
*/
public class SaSession implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 会话id
*/
private String id;
/**
* 当前会话创建时间
*/
private long createTime;
/**
* 当前会话键值对
*/
private Map<String, Object> dataMap;
/**
* 构建一个 session对象
* @param id sessionId
*/
public SaSession(String id) {
this.id = id;
this.createTime = System.currentTimeMillis();
this.dataMap = new HashMap<String, Object>();
}
/**
* 获取会话id
* @return id
*/
public String getId() {
return id;
}
/**
* 返回当前会话创建时间
* @return 时间戳
*/
public long getCreateTime() {
return createTime;
}
/**
* 写入一个值
* @param key 名称
* @param value 值
*/
public void setAttribute(String key, Object value) {
dataMap.put(key, value);
update();
}
/**
* 取出一个值
* @param key 名称
* @return 值
*/
public Object getAttribute(String key) {
return dataMap.get(key);
}
/**
* 取值,并指定取不到值时的默认值
* @param key 名称
* @param defaultValue 取不到值的时候返回的默认值
* @return value
*/
public Object getAttribute(String key, Object defaultValue) {
Object value = getAttribute(key);
if(value != null) {
return value;
}
return defaultValue;
}
/**
* 移除一个值
* @param key 要移除的值的名字
*/
public void removeAttribute(String key) {
dataMap.remove(key);
update();
}
/**
* 清空所有值
*/
public void clearAttribute() {
dataMap.clear();
update();
}
/**
* 是否含有指定key
* @param key 是否含有指定值
* @return 是否含有
*/
public boolean containsAttribute(String key) {
return dataMap.keySet().contains(key);
}
/**
* 返回当前session会话所有key
* @return 所有值的key列表
*/
public Set<String> getAttributeKeys() {
return dataMap.keySet();
}
/**
* 获取数据集合(如果更新map里的值,请调用session.update()方法避免数据过时
* @return 返回底层储存值的map对象
*/
public Map<String, Object> getDataMap() {
return dataMap;
}
/**
* 将这个session从持久库更新一下
*/
public void update() {
SaTokenManager.getDao().updateSaSession(this);
}
// /** 注销会话(注销后,此session会话将不再存储服务器上) */
// public void logout() {
// SaTokenManager.getDao().delSaSession(this.id);
// }
}
@@ -1,62 +0,0 @@
package cn.dev33.satoken.session;
import cn.dev33.satoken.SaTokenManager;
/**
* 自定义sa-session工具类
* @author kong
*
*/
public class SaSessionCustomUtil {
/**
* 添加上指定前缀,防止恶意伪造session
*/
public static String session_key = "custom";
public static String getSessionKey(String sessionId) {
return SaTokenManager.getConfig().getTokenName() + ":" + session_key + ":session:" + sessionId;
}
/**
* 指定key的session是否存在
* @param sessionId session的id
* @return 是否存在
*/
public boolean isExists(String sessionId) {
return SaTokenManager.getDao().getSaSession(getSessionKey(sessionId)) != null;
}
/**
* 获取指定key的session
* @param sessionId key
* @param isCreate 如果没有,是否新建并返回
* @return SaSession
*/
public static SaSession getSessionById(String sessionId, boolean isCreate) {
SaSession session = SaTokenManager.getDao().getSaSession(getSessionKey(sessionId));
if(session == null && isCreate) {
session = new SaSession(getSessionKey(sessionId));
SaTokenManager.getDao().saveSaSession(session, SaTokenManager.getConfig().getTimeout());
}
return session;
}
/**
* 获取指定key的session, 如果没有则新建并返回
* @param sessionId key
* @return session对象
*/
public static SaSession getSessionById(String sessionId) {
return getSessionById(sessionId, true);
}
/**
* 删除指定key的session
* @param sessionId 删除指定key
*/
public static void deleteSessionById(String sessionId) {
SaTokenManager.getDao().deleteSaSession(getSessionKey(sessionId));
}
}
@@ -1,60 +0,0 @@
package cn.dev33.satoken.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.stp.StpInterface;
/**
* 与SpringBoot集成, 保证此类被扫描,即可完成sa-token与SpringBoot的集成
* @author kong
*
*/
@Component
public class SpringSaToken {
/**
* 获取配置Bean
* @return .
*/
@Bean
@ConfigurationProperties(prefix="spring.sa-token")
public SaTokenConfig getSaTokenConfig() {
return new SaTokenConfig();
}
/**
* 注入配置Bean
* @param saTokenConfig .
*/
@Autowired
public void setConfig(SaTokenConfig saTokenConfig){
SaTokenManager.setConfig(saTokenConfig);
}
/**
* 注入持久化Bean
* @param dao .
*/
@Autowired(required = false)
public void setDao(SaTokenDao dao){
SaTokenManager.setDao(dao);
}
/**
* 注入权限认证Bean
* @param stp .
*/
@Autowired(required = false)
public void setStp(StpInterface stp){
SaTokenManager.setStp(stp);
}
}
@@ -1,20 +0,0 @@
package cn.dev33.satoken.stp;
import java.util.List;
/**
* 开放权限验证接口,方便重写
* @author kong
*/
public interface StpInterface {
/**
* 返回指定login_id所拥有的权限码集合
* @param loginId 账号id
* @param loginKey 具体的stp标识
* @return .
*/
public List<Object> getPermissionCodeList(Object loginId, String loginKey);
}
@@ -1,17 +0,0 @@
package cn.dev33.satoken.stp;
import java.util.ArrayList;
import java.util.List;
/**
* 权限验证接口 ,默认实现
* @author kong
*/
public class StpInterfaceDefaultImpl implements StpInterface {
@Override
public List<Object> getPermissionCodeList(Object loginId, String loginKey) {
return new ArrayList<Object>();
}
}
@@ -1,505 +0,0 @@
package cn.dev33.satoken.stp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
* sa-token 权限验证,逻辑 实现类
* <p>
* (stp = sa-token-permission 的缩写 )
* @author kong
*/
public class StpLogic {
/**
* 持久化的key前缀,多账号体系时以此值区分,比如:login、user、admin
*/
public String loginKey = "";
/**
* 初始化StpLogic, 并制定loginKey
* @param loginKey .
*/
public StpLogic(String loginKey) {
this.loginKey = loginKey;
}
// =================== 获取token 相关 ===================
/**
* 返回token名称
* @return 此StpLogic的token名称
*/
public String getTokenName() {
return getKeyTokenName();
}
/**
* 随机生成一个tokenValue
* @param loginId loginId
* @return 生成的tokenValue
*/
public String randomTokenValue(Object loginId) {
return SaTokenManager.getSta().createToken(loginId, loginKey);
}
/**
* 获取当前tokenValue
* @return 当前tokenValue
*/
public String getTokenValue(){
// 0、获取相应对象
HttpServletRequest request = SaTokenManager.getSta().getCurrRequest();
SaTokenConfig config = SaTokenManager.getConfig();
String keyTokenName = getTokenName();
// 1、尝试从request里读取
if(request.getAttribute(SaTokenInsideUtil.JUST_CREATED_SAVE_KEY) != null) {
return String.valueOf(request.getAttribute(SaTokenInsideUtil.JUST_CREATED_SAVE_KEY));
}
// 2、尝试从请求体里面读取
if(config.getIsReadBody() == true){
String tokenValue = request.getParameter(keyTokenName);
if(tokenValue != null) {
return tokenValue;
}
}
// 3、尝试从header力读取
if(config.getIsReadHead() == true){
String tokenValue = request.getHeader(keyTokenName);
if(tokenValue != null) {
return tokenValue;
}
}
// 4、尝试从cookie里读取
if(config.getIsReadCookie() == true){
Cookie cookie = SaTokenManager.getSaCookieOper().getCookie(request, keyTokenName);
if(cookie != null){
String tokenValue = cookie.getValue();
if(tokenValue != null) {
return tokenValue;
}
}
}
// 5、都读取不到,那算了吧还是
return null;
}
/**
* 获取指定id的tokenValue
* @param loginId .
* @return .
*/
public String getTokenValueByLoginId(Object loginId) {
return SaTokenManager.getDao().getValue(getKeyLoginId(loginId));
}
/**
* 获取当前会话的token信息:tokenName与tokenValue
* @return 一个Map对象
*/
public Map<String, String> getTokenInfo() {
Map<String, String> map = new HashMap<String, String>();
map.put("tokenName", getTokenName());
map.put("tokenValue", getTokenValue());
return map;
}
// =================== 登录相关操作 ===================
/**
* 在当前会话上登录id
* @param loginId 登录id ,建议的类型:(long | int | String
*/
public void setLoginId(Object loginId) {
// 1、获取相应对象
HttpServletRequest request = SaTokenManager.getSta().getCurrRequest();
SaTokenConfig config = SaTokenManager.getConfig();
SaTokenDao dao = SaTokenManager.getDao();
// 2、获取tokenValue
String tokenValue = getTokenValueByLoginId(loginId); // 获取旧tokenValue
if(tokenValue == null){ // 为null则创建一个新的
tokenValue = randomTokenValue(loginId);
} else {
// 不为null, 并且配置不共享,则:将原来的标记为[被顶替]
if(config.getIsShare() == false){
// dao.delKey(getKeyTokenValue(tokenValue));
dao.updateValue(getKeyTokenValue(tokenValue), NotLoginException.BE_REPLACED);
tokenValue = randomTokenValue(loginId);
}
}
// 3、持久化
dao.setValue(getKeyTokenValue(tokenValue), String.valueOf(loginId), config.getTimeout()); // token -> uid
dao.setValue(getKeyLoginId(loginId), tokenValue, config.getTimeout()); // uid -> token
request.setAttribute(SaTokenInsideUtil.JUST_CREATED_SAVE_KEY, tokenValue); // 保存到本次request里
if(config.getIsReadCookie() == true){
SaTokenManager.getSaCookieOper().addCookie(SaTokenManager.getSta().getResponse(), getTokenName(), tokenValue, "/", (int)config.getTimeout()); // cookie注入
}
}
/**
* 当前会话注销登录
*/
public void logout() {
// 如果连token都没有,那么无需执行任何操作
String tokenValue = getTokenValue();
if(tokenValue == null) {
return;
}
// 如果打开了cookie模式,第一步,先把cookie清除掉
if(SaTokenManager.getConfig().getIsReadCookie() == true){
SaTokenManager.getSaCookieOper().delCookie(SaTokenManager.getSta().getCurrRequest(), SaTokenManager.getSta().getResponse(), getTokenName());
}
// 尝试从db中获取loginId值
String loginId = SaTokenManager.getDao().getValue(getKeyTokenValue(tokenValue));
// 如果根本查不到loginId,那么也无需执行任何操作
if(loginId == null) {
return;
}
// 如果已过期或被顶替或被挤下线,那么只删除此token即可
if(loginId.equals(NotLoginException.TOKEN_TIMEOUT) || loginId.equals(NotLoginException.BE_REPLACED) || loginId.equals(NotLoginException.KICK_OUT)) {
return;
}
// 至此,已经是一个正常的loginId,开始三清
logoutByLoginId(loginId);
}
/**
* 指定loginId的会话注销登录(清退下线)
* @param loginId 账号id
*/
public void logoutByLoginId(Object loginId) {
// 获取相应tokenValue
String tokenValue = getTokenValueByLoginId(loginId);
if(tokenValue == null) {
return;
}
// 清除相关数据
SaTokenManager.getDao().delKey(getKeyTokenValue(tokenValue)); // 清除token-id键值对
SaTokenManager.getDao().delKey(getKeyLoginId(loginId)); // 清除id-token键值对
SaTokenManager.getDao().deleteSaSession(getKeySession(loginId)); // 清除其session
}
/**
* 指定loginId的会话注销登录(踢人下线)
* @param loginId 账号id
*/
public void kickoutByLoginId(Object loginId) {
// 获取相应tokenValue
String tokenValue = getTokenValueByLoginId(loginId);
if(tokenValue == null) {
return;
}
// 清除相关数据
SaTokenManager.getDao().updateValue(getKeyTokenValue(tokenValue), NotLoginException.KICK_OUT); // 标记:已被踢下线
SaTokenManager.getDao().delKey(getKeyLoginId(loginId)); // 清除id-token键值对
SaTokenManager.getDao().deleteSaSession(getKeySession(loginId)); // 清除其session
}
// 查询相关
/**
* 获取当前会话是否已经登录
* @return 是否已登录
*/
public boolean isLogin() {
// 判断条件:不为null,并且不在异常项集合里
return getLoginIdDefaultNull() != null;
}
/**
* 检验当前会话是否已经登录,如未登录,则抛出异常
*/
public void checkLogin() {
getLoginId();
}
/**
* 获取当前会话登录id, 如果未登录,则抛出异常
* @return .
*/
public Object getLoginId() {
// 如果获取不到token,则抛出:无token
String tokenValue = getTokenValue();
if(tokenValue == null) {
throw NotLoginException.newInstance(loginKey, NotLoginException.NOT_TOKEN);
}
// 查找此token对应loginId, 则抛出:无效token
String loginId = SaTokenManager.getDao().getValue(getKeyTokenValue(tokenValue));
if(loginId == null) {
throw NotLoginException.newInstance(loginKey, NotLoginException.INVALID_TOKEN);
}
// 如果是已经过期,则抛出已经过期
if(loginId.equals(NotLoginException.TOKEN_TIMEOUT)) {
throw NotLoginException.newInstance(loginKey, NotLoginException.TOKEN_TIMEOUT);
}
// 如果是已经被顶替下去了, 则抛出:已被顶下线
if(loginId.equals(NotLoginException.BE_REPLACED)) {
throw NotLoginException.newInstance(loginKey, NotLoginException.BE_REPLACED);
}
// 如果是已经被踢下线了, 则抛出:已被踢下线
if(loginId.equals(NotLoginException.KICK_OUT)) {
throw NotLoginException.newInstance(loginKey, NotLoginException.KICK_OUT);
}
// 至此,返回loginId
return loginId;
}
/**
* 获取当前会话登录id, 如果未登录,则返回默认值
* @param defaultValue .
* @return .
*/
@SuppressWarnings("unchecked")
public <T>T getLoginId(T defaultValue) {
Object loginId = getLoginIdDefaultNull();
// 如果loginId为null,则返回默认值
if(loginId == null) {
return defaultValue;
}
// 开始尝试类型转换,只尝试三种类型:int、long、String
if(defaultValue instanceof Integer) {
return (T)Integer.valueOf(loginId.toString());
}
if(defaultValue instanceof Long) {
return (T)Long.valueOf(loginId.toString());
}
if(defaultValue instanceof String) {
return (T)loginId.toString();
}
return (T)loginId;
}
/**
* 获取当前会话登录id, 如果未登录,则返回null
* @return .
*/
public Object getLoginIdDefaultNull() {
// 如果连token都是空的,则直接返回
String tokenValue = getTokenValue();
if(tokenValue == null) {
return null;
}
// loginId为null或者在异常项里面,均视为未登录
Object loginId = SaTokenManager.getDao().getValue(getKeyTokenValue(tokenValue));
if(loginId == null || NotLoginException.ABNORMAL_LIST.contains(loginId)) {
return null;
}
// 执行到此,证明loginId已经是个正常的账号id了
return loginId;
}
/**
* 获取当前会话登录id, 并转换为String
* @return
*/
public String getLoginIdAsString() {
return String.valueOf(getLoginId());
}
/**
* 获取当前会话登录id, 并转换为int
* @return .
*/
public int getLoginIdAsInt() {
// Object loginId = getLoginId();
// if(loginId instanceof Integer) {
// return (Integer)loginId;
// }
return Integer.valueOf(String.valueOf(getLoginId()));
}
/**
* 获取当前会话登录id, 并转换为long
* @return .
*/
public long getLoginIdAsLong() {
// Object loginId = getLoginId();
// if(loginId instanceof Long) {
// return (Long)loginId;
// }
return Long.valueOf(String.valueOf(getLoginId()));
}
/**
* 获取指定token对应的登录id,如果未登录,则返回 null
* @return .
*/
public Object getLoginIdByToken(String tokenValue) {
if(tokenValue != null) {
Object loginId = SaTokenManager.getDao().getValue(getKeyTokenValue(tokenValue));
if(loginId != null) {
return loginId;
}
}
return null;
}
// =================== session相关 ===================
/**
* 获取指定key的session, 如果没有,isCreate=是否新建并返回
* @param sessionId .
* @param isCreate .
* @return .
*/
protected SaSession getSessionBySessionId(String sessionId, boolean isCreate) {
SaSession session = SaTokenManager.getDao().getSaSession(sessionId);
if(session == null && isCreate) {
session = new SaSession(sessionId);
SaTokenManager.getDao().saveSaSession(session, SaTokenManager.getConfig().getTimeout());
}
return session;
}
/**
* 获取指定loginId的session, 如果没有,isCreate=是否新建并返回
* @param loginId 登录id
* @param isCreate 是否新建
* @return SaSession
*/
public SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
return getSessionBySessionId(getKeySession(loginId), isCreate);
}
/**
* 获取指定loginId的session
* @param loginId .
* @return .
*/
public SaSession getSessionByLoginId(Object loginId) {
return getSessionByLoginId(loginId, true);
}
/**
* 获取当前会话的session
* @return
*/
public SaSession getSession() {
return getSessionByLoginId(getLoginId());
}
// =================== 权限验证操作 ===================
/**
* 指定loginId是否含有指定权限
* @param loginId .
* @param pcode .
* @return .
*/
public boolean hasPermission(Object loginId, Object pcode) {
List<Object> pcodeList = SaTokenManager.getStp().getPermissionCodeList(loginId, loginKey);
return !(pcodeList == null || pcodeList.contains(pcode) == false);
}
/**
* 当前会话是否含有指定权限
* @param pcode .
* @return .
*/
public boolean hasPermission(Object pcode) {
return hasPermission(getLoginId(), pcode);
}
/**
* 当前账号是否含有指定权限 , 没有就抛出异常
* @param pcode .
*/
public void checkPermission(Object pcode) {
if(hasPermission(pcode) == false) {
throw new NotPermissionException(pcode, this.loginKey);
}
}
/**
* 当前账号是否含有指定权限 , 【指定多个,必须全都有】
* @param pcodeArray .
*/
public void checkPermissionAnd(Object... pcodeArray){
Object loginId = getLoginId();
List<Object> pcodeList = SaTokenManager.getStp().getPermissionCodeList(loginId, loginKey);
for (Object pcode : pcodeArray) {
if(pcodeList.contains(pcode) == false) {
throw new NotPermissionException(pcode, this.loginKey); // 没有权限抛出异常
}
}
}
/**
* 当前账号是否含有指定权限 , 【指定多个,有一个就可以了】
* @param pcodeArray .
*/
public void checkPermissionOr(Object... pcodeArray){
Object loginId = getLoginId();
List<Object> pcodeList = SaTokenManager.getStp().getPermissionCodeList(loginId, loginKey);
for (Object pcode : pcodeArray) {
if(pcodeList.contains(pcode) == true) {
return; // 有的话提前退出
}
}
if(pcodeArray.length > 0) {
throw new NotPermissionException(pcodeArray[0], this.loginKey); // 没有权限抛出异常
}
}
// =================== 返回相应key ===================
/**
* 获取key:客户端 tokenName
* @return
*/
public String getKeyTokenName() {
return SaTokenManager.getConfig().getTokenName();
}
/**
* 获取key tokenValue 持久化
* @param tokenValue .
* @return
*/
public String getKeyTokenValue(String tokenValue) {
return SaTokenManager.getConfig().getTokenName() + ":" + loginKey + ":token:" + tokenValue;
}
/**
* 获取key id 持久化
* @param loginId .
* @return
*/
public String getKeyLoginId(Object loginId) {
return SaTokenManager.getConfig().getTokenName() + ":" + loginKey + ":id:" + loginId;
}
/**
* 获取key session 持久化
* @param loginId .
* @return .
*/
public String getKeySession(Object loginId) {
return SaTokenManager.getConfig().getTokenName() + ":" + loginKey + ":session:" + loginId;
}
}
@@ -1,239 +0,0 @@
package cn.dev33.satoken.stp;
import java.util.Map;
import org.springframework.stereotype.Service;
import cn.dev33.satoken.session.SaSession;
/**
* 一个默认的实现
* @author kong
*/
@Service
public class StpUtil {
/**
* 底层的 StpLogic 对象
*/
public static StpLogic stpLogic = new StpLogic("login");
// =================== 获取token 相关 ===================
/**
* 返回token名称
* @return 此StpLogic的token名称
*/
public static String getTokenName() {
return stpLogic.getTokenName();
}
/**
* 获取当前tokenValue
* @return 当前tokenValue
*/
public static String getTokenValue() {
return stpLogic.getTokenValue();
}
/**
* 获取指定id的tokenValue
* @param loginId .
* @return
*/
public static String getTokenValueByLoginId(Object loginId) {
return stpLogic.getTokenValueByLoginId(loginId);
}
/**
* 获取当前会话的token信息:tokenName与tokenValue
* @return 一个Map对象
*/
public static Map<String, String> getTokenInfo() {
return stpLogic.getTokenInfo();
}
// =================== 登录相关操作 ===================
/**
* 在当前会话上登录id
* @param loginId 登录id ,建议的类型:(long | int | String
*/
public static void setLoginId(Object loginId) {
stpLogic.setLoginId(loginId);
}
/**
* 当前会话注销登录
*/
public static void logout() {
stpLogic.logout();
}
/**
* 指定loginId的会话注销登录(清退下线)
* @param loginId 账号id
*/
public static void logoutByLoginId(Object loginId) {
stpLogic.logoutByLoginId(loginId);
}
/**
* 指定loginId的会话注销登录(踢人下线)
* @param loginId 账号id
*/
public static void kickoutByLoginId(Object loginId) {
stpLogic.kickoutByLoginId(loginId);
}
// 查询相关
/**
* 获取当前会话是否已经登录
* @return 是否已登录
*/
public static boolean isLogin() {
return stpLogic.isLogin();
}
/**
* 检验当前会话是否已经登录,如未登录,则抛出异常
*/
public static void checkLogin() {
getLoginId();
}
/**
* 获取当前会话登录id, 如果未登录,则抛出异常
* @return .
*/
public static Object getLoginId() {
return stpLogic.getLoginId();
}
/**
* 获取当前会话登录id, 如果未登录,则返回默认值
* @param defaultValue .
* @return .
*/
public static <T> T getLoginId(T defaultValue) {
return stpLogic.getLoginId(defaultValue);
}
/**
* 获取当前会话登录id, 如果未登录,则返回null
* @return
*/
public static Object getLoginIdDefaultNull() {
return stpLogic.getLoginIdDefaultNull();
}
/**
* 获取当前会话登录id, 并转换为String
* @return
*/
public static String getLoginIdAsString() {
return stpLogic.getLoginIdAsString();
}
/**
* 获取当前会话登录id, 并转换为int
* @return
*/
public static int getLoginIdAsInt() {
return stpLogic.getLoginIdAsInt();
}
/**
* 获取当前会话登录id, 并转换为long
* @return
*/
public static long getLoginIdAsLong() {
return stpLogic.getLoginIdAsLong();
}
/**
* 获取指定token对应的登录id,如果未登录,则返回 null
* @return .
*/
public static Object getLoginIdByToken(String tokenValue) {
return stpLogic.getLoginIdByToken(tokenValue);
}
// =================== session相关 ===================
/**
* 获取指定loginId的session, 如果没有,isCreate=是否新建并返回
* @param loginId 登录id
* @param isCreate 是否新建
* @return SaSession
*/
public static SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
return stpLogic.getSessionByLoginId(loginId, isCreate);
}
/**
* 获取指定loginId的session
* @param loginId .
* @return .
*/
public static SaSession getSessionByLoginId(Object loginId) {
return stpLogic.getSessionByLoginId(loginId);
}
/**
* 获取当前会话的session
* @return
*/
public static SaSession getSession() {
return stpLogic.getSession();
}
// =================== 权限验证操作 ===================
/**
* 指定loginId是否含有指定权限
* @param loginId .
* @param pcode .
* @return .
*/
public static boolean hasPermission(Object loginId, Object pcode) {
return stpLogic.hasPermission(loginId, pcode);
}
/**
* 当前会话是否含有指定权限
* @param pcode .
* @return .
*/
public static boolean hasPermission(Object pcode) {
return stpLogic.hasPermission(pcode);
}
/**
* 当前账号是否含有指定权限 , 没有就抛出异常
* @param pcode .
*/
public static void checkPermission(Object pcode) {
stpLogic.checkPermission(pcode);
}
/**
* 当前账号是否含有指定权限 , 【指定多个,必须全都有】
* @param pcodeArray .
*/
public static void checkPermissionAnd(Object... pcodeArray) {
stpLogic.checkPermissionAnd(pcodeArray);
}
/**
* 当前账号是否含有指定权限 , 【指定多个,有一个就可以了】
* @param pcodeArray .
*/
public static void checkPermissionOr(Object... pcodeArray) {
stpLogic.checkPermissionOr(pcodeArray);
}
}
@@ -1,40 +0,0 @@
package cn.dev33.satoken.util;
/**
* sa-token 工具类
* @author kong
*
*/
public class SaTokenInsideUtil {
/**
* sa-token 版本号
*/
public static final String VERSION_NO = "v1.5.1";
/**
* sa-token 开源地址
*/
public static final String GITHUB_URL= "https://github.com/click33/sa-token";
/**
* 打印 sa-token
*/
public static void printSaToken() {
String str =
"____ ____ ___ ____ _ _ ____ _ _ \r\n" +
"[__ |__| __ | | | |_/ |___ |\\ | \r\n" +
"___] | | | |__| | \\_ |___ | \\| \r\n" +
"sa-token" + VERSION_NO + " \r\n" +
"GitHub" + GITHUB_URL; // + "\r\n";
System.out.println(str);
}
/**
* 如果token为本次请求新创建的,则以此字符串为key存储在当前request中 JUST_CREATED_SAVE_KEY
*/
public static final String JUST_CREATED_SAVE_KEY = "JUST_CREATED_SAVE_KEY_";
}
View File
+174 -52
View File
@@ -1,11 +1,11 @@
<p align="center">
<img alt="logo" src="https://gitee.com/sz6/sa-token/raw/master/sa-token-doc/doc/logo.png" width="150" height="150" style="margin-bottom: 10px;">
<img alt="logo" src="https://gitee.com/sz6/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.5.1</h1>
<h4 align="center">一个JavaWeb轻量级权限认证框架,功能全面,上手简单</h4>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sa-token v1.13.0</h1>
<h4 align="center">这可能是史上功能最全的Java权限认证框架!</h4>
<h4 align="center">
<a href="https://gitee.com/sz6/sa-token/stargazers"><img src="https://gitee.com/sz6/sa-token/badge/star.svg"></a>
<a href="https://github.com/click33/sa-token"><img src="https://img.shields.io/badge/sa--token-v1.5.1-2B9939"></a>
<a href="https://github.com/click33/sa-token"><img src="https://img.shields.io/badge/sa--token-v1.13.0-2B9939"></a>
<a href="https://github.com/click33/sa-token/stargazers"><img src="https://img.shields.io/github/stars/click33/sa-token"></a>
<a href="https://github.com/click33/sa-token/watchers"><img src="https://img.shields.io/github/watchers/click33/sa-token"></a>
<a href="https://github.com/click33/sa-token/network/members"><img src="https://img.shields.io/github/forks/click33/sa-token"></a>
@@ -16,76 +16,198 @@
---
## 😘 在线资料
- ##### ⚡&nbsp;[官网首页:http://sa-token.dev33.cn/](http://sa-token.dev33.cn/)
- ##### ⚡&nbsp;[在线文档http://sa-token.dev33.cn/doc/index.html](http://sa-token.dev33.cn/doc/index.html)
- ##### ⚡&nbsp;[需求提交:我们深知一个优秀的项目需要海纳百川,点我在线提交需求](http://sa-app.dev33.cn/wall.html?name=sa-token)
- ##### ⚡&nbsp;[开源不易,求鼓励,点个star吧](https://github.com/click33/sa-token)
## 在线资料
- [官网首页http://sa-token.dev33.cn/](http://sa-token.dev33.cn/)
- [在线文档:http://sa-token.dev33.cn/doc/index.html](http://sa-token.dev33.cn/doc/index.html)
- [需求提交:我们深知一个优秀的项目需要海纳百川,点我在线提交需求](http://sa-app.dev33.cn/wall.html?name=sa-token)
- [开源不易,求鼓励,点个star吧](###)
## Sa-Token是什么?
sa-token是一个轻量级Java权限认证框架,主要解决:登录认证、权限认证、Session会话、单点登录 等一系列权限相关问题
近年来,有关权限认证的解决方案层出不穷,例如单点登录、OAuth2.0、分布式Session等等难题,无一不有着各种优秀框架大行其道
然而当我们把视线放低,那些最基础的有如:登录认证、权限认证、Session会话等基础问题却一直被两大上古神兽 `Apache Shiro``Spring Security` 所把持
在此并非专门diss此两大框架,诚然两大框架背景强大,历史悠久,其生态也比较齐全。但是它们毕竟已经是十几年前的产物,那是一个还在用 `jsp` 写页面的时代,两大框架的很多功能都是为jsp那一套量身定做。
在前后台分离已成标配的今天,两大框架的很多设计理念已经比较滞后,已经不能和我们的项目进行无缝适配,很多功能点都需要进行二次封装,甚至找一大堆扩展插件才能集成,已经逐渐不太适合现代化项目的应用
所以,为什么不能有一个自底向上,从最基础的登录、权限做起,以业务需求为核心,做到开箱即用的轻量级权限认证框架?
秉承着这个目的,`sa-token` 诞生了!
## Sa-Token 优点?
sa-token架构设计精简,不引入各种复杂的概念,如丝般顺滑的API调用,大量高级特性统统只需一行代码即可实现
- 在sa-token之前,权限认证业务概念纷飞、代码复杂,在sa-token之后,权限认证将会变成:逻辑清晰,架构简单,人人可写
- 在sa-token之前,你会在百度上频繁搜索: xx框架如何集成Redis?前后台分离下如何鉴权?踢人下线的原理是什么?在sa-token之后,你将轻松知道这一切的答案
- 在sa-token之前,权限认证、分布式会话、单点登录、多账号认证,你需要找不同的框架,在sa-token之后,你只需要这一个框架就足够了
与其它权限认证框架相比,你将会从以下方面感受到 `sa-token` 的优势:
1. **简单** :可零配置启动框架,真正的开箱即用,低成本上手
2. **强大** :目前已集成几十项权限相关特性,涵盖了大部分业务场景的解决方案
3. **易用** :如丝般顺滑的API调用,大量高级特性统统只需一行代码即可实现
4. **高扩展** :几乎所有组件都提供了扩展接口,90%以上的逻辑都可以按需重写
有了sa-token,你所有的权限认证问题,都不再是问题!
## Sa-Token 能做什么?
- **登录验证** —— 轻松登录鉴权,并提供五种细分场景值
- **权限验证** —— 适配RBAC权限模型,不同角色不同授权
- **Session会话** —— 专业的数据缓存中心
- **踢人下线** —— 将违规用户立刻清退下线
- **持久层扩展** —— 可集成Redis、Memcached等专业缓存中间件,重启数据不丢失
- **分布式会话** —— 提供jwt集成和共享数据中心两种分布式会话方案
- **单点登录** —— 一处登录,处处通行
- **模拟他人账号** —— 实时操作任意用户状态数据
- **临时身份切换** —— 将会话身份临时切换为其它账号
- **无Cookie模式** —— APP、小程序等前后台分离场景
- **同端互斥登录** —— 像QQ一样手机电脑同时在线,但是两个手机上互斥登录
- **多账号认证体系** —— 比如一个商城项目的user表和admin表分开鉴权
- **花式token生成** —— 内置六种token风格,还可自定义token生成策略
- **注解式鉴权** —— 优雅的将鉴权与业务代码分离
- **路由拦截式鉴权** —— 设定全局路由拦截,并排除指定路由
- **自动续签** —— 提供两种token过期策略,灵活搭配使用,还可自动续签
- **会话治理** —— 提供方便灵活的会话查询接口
- **组件自动注入** —— 零配置与Spring等框架集成
- **更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流
## 代码示例
sa-token的API调用非常简单,有多简单呢?以登录验证为例,你只需要:
## ⭐ sa-token是什么?
- **sa-token是一个JavaWeb轻量级权限认证框架,其API调用非常简单,有多简单呢?以登录验证为例,你只需要:**
``` java
// 在登录时写入当前会话的账号id
// 在登录时写入当前会话的账号id
StpUtil.setLoginId(10001);
```
- **然后在任意需要验证登录权限的地方:**
``` java
// 检测是否登录 --- 如果当前会话未登录,下面这句代码会抛出 `NotLoginException`异常
// 然后在任意需要校验登录处调用以下API
// 如果当前会话未登录,这句代码会抛出 `NotLoginException`异常
StpUtil.checkLogin();
```
至此,我们已经借助sa-token框架完成登录授权!
此时的你小脑袋可能飘满了问号,就这么简单?自定义Realm呢?全局过滤器呢?我不用写各种配置文件吗?
事实上在此我可以负责的告诉你,在sa-token中,登录授权就是如此的简单,不需要什么全局过滤器,不需要各种乱七八糟的配置!只需要这一行简单的API调用,即可完成会话的登录授权!
当你受够Shiro、Security等框架的三拜九叩之后,你就会明白,相对于这些传统老牌框架,sa-token的API设计是多么的清爽!
权限认证示例 (只有具有`user:add`权限的会话才可以进入请求)
``` java
@SaCheckPermission("user:add")
@RequestMapping("/user/insert")
public String insert(SysUser user) {
return "用户增加";
}
```
将某个账号踢下线 (待到对方再次访问系统时会抛出`NotLoginException`异常)
``` java
// 使账号id为10001的会话注销登录
StpUtil.logoutByLoginId(10001);
```
除了以上的示例,sa-token还可以一行代码完成以下功能:
``` java
StpUtil.setLoginId(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.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值
StpUtil.setLoginId(10001, "PC"); // 指定设备标识登录
StpUtil.logoutByLoginId(10001, "PC"); // 指定设备标识进行强制注销 (不同端不受影响)
StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
```
sa-token的API众多,请恕此处无法为您逐一展示,更多示例请戳官方在线文档
- **没有复杂的封装!不要任何的配置!先写入,后鉴权!只需这两行简单的调用,即可轻松完成系统登录鉴权!**
## 迭代模式
sa-token的功能提案主要来源于社区,这意味着人人都可以参与到sa-token的功能定制,决定框架的未来走向,
如果你有好的想法,可以在issues提出或者加入群一起交流,对于社区的提出的功能要求,主要分为以下几类:
- 对框架新增特性功能且比较简单,会在第一时间进行开发
- 对框架新增特性功能但比较复杂,会延后几个版本制定相应的计划后进行开发
- 与框架设计理念不太相符,或超出权限认证范畴,将会视需求人数决定是否开发
## 🔥 框架设计思想
与其它权限认证框架相比,`sa-token`尽力保证两点:
- 上手简单:能自动化的配置全部自动化,不让你费脑子
- 功能强大:能涵盖的功能全部涵盖,不让你用个框架还要自己给框架打各种补丁
## 参与贡献
众人拾柴火焰高,万丈高楼众人起!
sa-token秉承着开放的思想,欢迎大家贡献代码,为框架添砖加瓦,对框架有卓越贡献者将会出现在贡献者名单里
## 💦️ 涵盖功能
-**登录验证** —— 轻松登录鉴权,并提供五种细分场景值
-**权限验证** —— 拦截违规调用,不同角色不同授权
-**自定义session会话** —— 专业的数据缓存中心
-**踢人下线** —— 将违规用户立刻清退下线
-**模拟他人账号** —— 实时操作任意用户状态数据
-**持久层扩展** —— 可集成redis、MongoDB等专业缓存中间件
-**多账号认证体系** —— 比如一个商城项目的user表和admin表分开鉴权
-**无cookie模式** —— APP、小程序等前后台分离场景
-**注解式鉴权** —— 优雅的将鉴权与业务代码分离
-**组件自动注入** —— 零配置与Spring等框架集成
-**更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流
## 🔨 贡献代码
sa-token欢迎大家贡献代码,为框架添砖加瓦
1. 在github上fork一份到自己的仓库
1. 在gitee或者github上fork一份代码到自己的仓库
2. clone自己的仓库到本地电脑
3. 在本地电脑修改、commit、push
4. 提交pr(点击:New Pull Request
5. 等待合并
## 🌱 建议贡献的地方
- 修复源码现有bug,或增加新的实用功能
- 完善在线文档,或者修复现有错误之处
- 更多demo示例:比如SSM版搭建步骤
- 您可以参考项目issues与需求墙进行贡献
- 如果更新实用功能,可在文档友情链接处留下自己的推广链接
作者寄语:参与贡献不光只有提交代码一个选择,点一个star、提一个issues都是对开源项目的促进,
如果框架帮助到了你,欢迎你把框架推荐给你的朋友、同事使用,为sa-token的推广做一份贡献
## 🚀 友情链接
[**[ okhttps ]** 一个轻量级http通信框架,支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps)
## 建议贡献的地方
目前框架的主要有以下部分需要大家一起参与贡献:
- 核心代码:该部分需要开发者了解整个框架的架构,遵循已有代码规范进行bug修复或提交新功能
- 文档部分:需要以清晰明了的语句书写文档,力求简单易读,授人以鱼同时更授人以渔
- 社区建设:如果框架帮助到了您,希望您可以加入qq群参与交流,对不熟悉框架的新人进行排难解惑
- 框架推广:一个优秀的开源项目不能仅靠闭门造车,它还需要一定的推广方案让更多的人一起参与到项目中
- 其它部分:您可以参考项目issues与需求墙进行贡献
## 😎 交流群
QQ交流群:[1002350610 点击加入](https://jq.qq.com/?_wv=1027&k=45H977HM) ,欢迎你的加入
## 贡献者名单
[省长](https://gitee.com/sz6)、
[RockMan](https://gitee.com/njx33)、
[click33](https://github.com/click33)、
[AppleOfGray](https://gitee.com/appleOfGray)、
[Auster](https://github.com/auster9021)、
[ZhuBJ0510](https://gitee.com/zhubj0510)、
[legg](https://gitee.com/legg321)、
[xiaoshitou](https://gitee.com/smallstoneZ)、
[zhangjiaxiaozhuo](https://gitee.com/zhangjiaxiaozhuo)、
[离你多远](https://gitee.com/liniduoyuan)
## 知乎专栏
- [初识sa-token,一行代码搞定登录授权!](https://zhuanlan.zhihu.com/p/344106099)
- [一个登录功能也能玩出这么多花样?sa-token带你轻松搞定多地登录、单地登录、同端互斥登录](https://zhuanlan.zhihu.com/p/344511415)
- [浅谈踢人下线的设计思路!(附代码实现方案)](https://zhuanlan.zhihu.com/p/345844002)
- 文章已在 [csdn](https://blog.csdn.net/shengzhang_/article/details/112593247)、
[掘金](https://juejin.cn/post/6917250126650015751)、
[开源中国](https://my.oschina.net/u/3503445/blog/4897816)、
[博客园](https://www.cnblogs.com/shengzhang/p/14275558.html)、
[知乎](https://zhuanlan.zhihu.com/p/344106099)
等平台连载中...欢迎投稿
## 使用sa-token的开源项目
[**[ sa-plus]** 一个基于springboot架构的快速开发框架,内置代码生成器](https://gitee.com/sz6/sa-plus)
如果您的项目使用了sa-token,欢迎提交pr
## 友情链接
[**[ okhttps ]** 一个轻量级http通信框架,API设计无比优雅,支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps)
## 交流群
QQ交流群:[1002350610 点击加入](https://jq.qq.com/?_wv=1027&k=45H977HM)
![扫码加群](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/qq-group.png ':size=150')
**微信群**
![微信群](https://images.gitee.com/uploads/images/2021/0204/121632_63ee1287_1766140.png "sa-token-wx.jpg")
<br>
+14 -4
View File
@@ -8,14 +8,22 @@
- **使用**
- [登录验证](/use/login-auth)
- [权限验证](/use/jur-auth)
- [session会话](/use/session)
- [Session会话](/use/session)
- [踢人下线](/use/kick)
- [持久层扩展(集成redis)](/use/dao-extend)
- [cookie模式(前后台分离)](/use/not-cookie)
- [持久层扩展集成Redis](/use/dao-extend)
- [Cookie模式前后台分离](/use/not-cookie)
- [模拟他人](/use/mock-person)
- [多账号验证](/use/many-account)
- [同端互斥登录](/use/mutex-login)
- [注解式鉴权](/use/at-check)
- [路由拦截式鉴权](/use/route-check)
- [花式token](/use/token-style)
- [框架配置](/use/config)
- [会话治理](/use/search-session)
- **进阶**
- [集群、分布式](/senior/dcs)
- [单点登录](/senior/sso)
- [多账号验证](/use/many-account)
- **其它**
- [常见问题](/more/common-questions)
@@ -24,6 +32,8 @@
- **附录**
- [未登录场景值](/fun/not-login-scene)
- [token有效期详解](/fun/token-timeout)
- [TokenInfo参数详解](/fun/token-info)
+13 -2
View File
@@ -5,8 +5,19 @@
## 何为场景值
- 在前面的章节中,我们了解到,在会话未登录的情况下尝试获取`loginId`会使框架抛出`NotLoginException`异常,而同为未登录异常却有五种抛出场景的区分 <br>
- 那么,如何获取场景值呢?废话少说直接上代码:
在前面的章节中,我们了解到,在会话未登录的情况下尝试获取`loginId`会使框架抛出`NotLoginException`异常,而同为未登录异常却有五种抛出场景的区分
| 场景值 | 对应常量 | 含义说明 |
|--- |--- |--- |
| -1 | NotLoginException.NOT_TOKEN | 未能从请求中读取到token |
| -2 | NotLoginException.INVALID_TOKEN| 已读取到token,但是token无效 |
| -3 | NotLoginException.TOKEN_TIMEOUT| 已读取到token,但是token已经过期 |
| -4 | NotLoginException.BE_REPLACED| 已读取到token,但是token已被顶下线 |
| -5 | NotLoginException.KICK_OUT| 已读取到token,但是token已被踢下线 |
那么,如何获取场景值呢?废话少说直接上代码:
``` java
+22
View File
@@ -0,0 +1,22 @@
# SaTokenInfo 参数详解
token信息Model: 用来描述一个token的常用参数
``` js
{
"code": 200,
"msg": "ok",
"data": {
"tokenName": "satoken", // token名称
"tokenValue": "e67b99f1-3d7a-4a8d-bb2f-e888a0805633", // token值
"isLogin": true, // 此token是否已经登录
"loginId": "10001", // 此token对应的LoginId,未登录时为null
"loginKey": "login", // LoginKey账号体系标识
"tokenTimeout": 2591977, // token剩余有效期 (单位: 秒)
"sessionTimeout": 2591977, // User-Session剩余有效时间 (单位: 秒)
"tokenSessionTimeout": -2, // Token-Session剩余有效时间 (单位: 秒)
"tokenActivityTimeout": -1, // token剩余无操作有效时间 (单位: 秒)
"loginDevice": "default-device" // 登录设备标识
},
}
```
+49
View File
@@ -0,0 +1,49 @@
# token有效期详解
<!-- 本篇介绍token有效期的详细用法 -->
`sa-token` 提供两种token自动过期策略,分别是`timeout``activity-timeout`,其详细用法如下:
### timeout
1. `timeout`代表token的长久有效期,单位/秒,例如将其配置为`2592000`(30天),代表在30天后,token必定过期,无法继续使用
2. `timeout`无法续签,想要继续使用必须重新登录
3. `timeout`的值配置为-1后,代表永久有效,不会过期
### activity-timeout
1. `activity-timeout`代表临时有效期,单位/秒,例如将其配置为`1800`(30分钟),代表用户如果30分钟无操作,则此token会立即过期
2. 如果在30分钟内用户有操作,则会再次续签30分钟,用户如果一直操作则会一直续签,直到连续30分钟无操作,token才会过期
3. `activity-timeout`的值配置为-1后,代表永久有效,不会过期,此时也无需频繁续签
### 关于activity-timeout的续签
如果`activity-timeout`配置了大于零的值,`sa-token`会在登录时开始计时,在每次直接或间接调用`getLoginId()`时进行一次过期检查与续签操作。
此时会有两种情况:
1. 一种是会话无操作时间太长,token已经过期,此时框架会抛出`NotLoginException`异常(场景值=-3)
2. 另一种则是会话在`activity-timeout`有效期内通过检查,此时token可以成功续签
### 我可以手动续签吗?
**可以!**
如果框架的自动续签算法无法满足您的业务需求,你可以进行手动续签,`sa-token`提供两个API供你操作:
1. `StpUtil.checkActivityTimeout()`: 检查当前token 是否已经[临时过期],如果已经过期则抛出异常
2. `StpUtil.updateLastActivityToNow()`: 续签当前token:(将 [最后操作时间] 更新为当前时间戳)
注意:在手动续签时,即时token已经 [临时过期] 也可续签成功,如果此场景下需要提示续签失败,可采用先检查再续签的形式保证token有效性
例如以下代码:
``` java
// 先检查是否已过期
StpUtil.checkActivityTimeout();
// 检查通过后继续续签
StpUtil.updateLastActivityToNow();
```
同时,你还可以关闭框架的自动续签(在配置文件中配置 `autoRenew=false` ),此时续签操作完全由开发者控制,框架不再自动进行任何续签操作
### timeout与activity-timeout可以同时使用吗?
**可以同时使用!**
两者的认证逻辑彼此独立,互不干扰,可以同时使用。
-32
View File
@@ -1,32 +0,0 @@
.logo-box {position: absolute;left: 30px;top: 10px;cursor: pointer;color: #000;}
.logo-box img {width: 50px;height: 50px;vertical-align: middle;}
.logo-box .logo-text {display: inline-block;vertical-align: middle;font-size: 24px;font-weight: 400;}
#main {padding-bottom: 100px;}
#main h2 {font-size: 1.6rem;}
#main h3 {font-size: 1.25rem;}
.main-box .markdown-section{ padding: 30px 20px;}
@media screen and (max-width: 800px) {
.logo-box {display: none;}
.main-box .markdown-section{max-width: 1000px;}
}
/* ============== 样式优化 ================ */
/* 背景变黑 */
.main-box [data-lang]{padding: 0px !important; border-radius: 10px; overflow: hidden;}
.main-box [class^="lang-"]{/* color: red !important; */ border: 0px red solid; padding: 1.2em; background-color: #222; color: #FFF;}
.main-box [class^="lang-api"]{color: #FFF;}
.token.string{color: #65B042;}
.token.number{color: #2487CC;}
.token.punctuation{color: #FFF;}
.token.comment{color: #FFF;}
/* .main-box [class="lang-api"]{color: red;} */
#main table{margin-left: 25px;}
@media screen and (min-width: 800px) {
#main table tr th{min-width: 150px;}
}
+65 -15
View File
@@ -4,13 +4,12 @@
<meta charset="UTF-8">
<title>sa-token</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="description" content="sa-token是一个java权限认证框架,功能全面,上手简单,登录验证、权限验证、Session会话、踢人下线、集成Redis、分布式会话、单点登录、前后台分离、模拟他人账号、临时身份切换、多账号体系、注解式鉴权、路由拦截式鉴权、花式token、自动续签、同端互斥登录、会话治理、Spring集成...,零配置开箱即用,覆盖所有应用场景,你所需要的功能,这里都有">
<meta name="keywords" content="sa-token,sa-token框架,sa-token文档,java权限认证">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta name="keywords" content="sa-token|sa-token框架|sa-token文档|sa-token在线文档|权限认证框架">
<meta name="description" content="sa-token是一个JavaWeb权限认证框架,强大、简单、好用,登录验证、权限验证、自定义session会话、踢人下线、持久层扩展、无cookie模式、模拟他人账号、多账号体系、注解式鉴权、Spring集成...,零配置开箱即用,覆盖所有应用场景,你所需要的功能,这里都有">
<link rel="stylesheet" href="https://unpkg.com/docsify@4.11.3/lib/themes/vue.css">
<link rel="stylesheet" href="./index.css">
<link rel="shortcut icon" type="image/x-icon" href="logo.png">
<link rel="stylesheet" href="./lib/index.css">
<link rel="stylesheet" href="https://unpkg.zhimg.com/docsify@4.11.3/lib/themes/vue.css">
</head>
<body>
<a href="/">
@@ -20,6 +19,19 @@
</div>
</a>
<nav>
<select onchange="location.href=this.value">
<option value="http://sa-token.dev33.cn/doc/index.html">最新版</option>
<option value="http://sa-token.dev33.cn/v/v1.12.1/doc/index.html">v1.12.1</option>
<option value="http://sa-token.dev33.cn/v/v1.12.0/doc/index.html">v1.12.0</option>
<option value="http://sa-token.dev33.cn/v/v1.11.0/doc/index.html">v1.11.0</option>
<option value="http://sa-token.dev33.cn/v/v1.10.0/doc/index.html">v1.10.0</option>
<option value="http://sa-token.dev33.cn/v/v1.9.0/doc/index.html">v1.9.0</option>
<option value="http://sa-token.dev33.cn/v/v1.8.0/doc/index.html">v1.8.0</option>
<option value="http://sa-token.dev33.cn/v/v1.7.0/doc/index.html">v1.7.0</option>
<option value="http://sa-token.dev33.cn/v/v1.6.0/doc/index.html">v1.6.0</option>
<option value="http://sa-token.dev33.cn/v/v1.5.1/doc/index.html">v1.5.1</option>
<option value="http://sa-token.dev33.cn/v/v1.4.0/doc/index.html">v1.4.0</option>
</select>
<a href="/">首页</a>
<a href="/doc/">文档</a>
<a href="http://sa-app.dev33.cn/wall.html?name=sa-token" target="_blank">需求墙</a>
@@ -30,12 +42,12 @@
</div>
<script>
var name = '<img style="width: 50px; height: 50px; vertical-align: middle;" src="logo.png" alt="logo" /> ';
name += '<b style="font-size: 24px; vertical-align: middle;">sa-token</b> <sub>v1.5.1</sub>'
name += '<b style="font-size: 24px; vertical-align: middle;">sa-token</b> <sub>v1.13.0</sub>'
window.$docsify = {
name: name, // 名字
repo: 'https://github.com/click33/sa-token', // github地址
// themeColor: '#06A3D7', // 主题颜色
basePath: location.pathname.substr(0, location.pathname.lastIndexOf('/') + 1), // 自动计算项目名字
basePath: location.pathname.substr(0, location.pathname.lastIndexOf('/') + 1), // 自动计算项目名字
// basePath: '/sa-token-doc/', // 设置文件加载的父路径, 这在一些带项目名部署的文件中非常有效
auto2top: true, // 是否在切换页面后回到顶部
// coverpage: true, // 开启封面
@@ -50,28 +62,31 @@
alias: {
'/.*/_sidebar.md': '/_sidebar.md'
},
plugins: [ // 自定义插件
plugins: [ // 自定义插件
function(hook, vm) {
// 解析之后执行
hook.afterEach(function(html) {
var url = 'https://github.com/click33/sa-token/tree/master/sa-token-doc/doc/' + vm.route.file;
var url = 'https://gitee.com/sz6/sa-token/tree/dev/sa-token-doc/doc/' + vm.route.file;
var url2 = 'https://github.com/click33/sa-token/tree/dev/sa-token-doc/doc/' + vm.route.file;
var footer = [
'<br/><br/><br/><br/><br/><br/><br/><hr/>',
'<footer>',
'&emsp;<span>发现错误?想参与编辑? <a href="' + url + '" target="_blank">GitHub 上编辑此页!</a> </span>',
'<span>发现错误?想参与编辑? <a href="' + url + '" target="_blank">Gitee</a> 或 <a href="' + url2 +
'" target="_blank">GitHub</a> 上编辑此页!</span>',
'</footer>'
].join('');
return html + footer;
});
}
]
]
}
</script>
<script src="https://unpkg.com/docsify@4.9.4/lib/docsify.min.js"></script>
<script src="https://unpkg.com/docsify-copy-code@2.1.0/dist/docsify-copy-code.min.js"></script>
<script src="https://unpkg.com/prismjs@1.19.0/components/prism-java.min.js"></script>
<script src="https://unpkg.zhimg.com/docsify@4.9.4/lib/docsify.min.js"></script>
<script src="https://unpkg.zhimg.com/docsify-copy-code@2.1.0/dist/docsify-copy-code.min.js"></script>
<script src="https://unpkg.zhimg.com/prismjs@1.19.0/components/prism-java.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<!-- 搜索引擎自动提交 -->
<script>
(function() {
var bp = document.createElement('script');
@@ -85,9 +100,44 @@
s.parentNode.insertBefore(bp, s);
})();
</script>
<!-- 友盟 -->
<div style="height: 0px; overflow: hidden;">
<script type="text/javascript" src="https://v1.cnzz.com/z_stat.php?id=1279021391&web_id=1279021391"></script>
<script type="text/javascript" src="https://s4.cnzz.com/z_stat.php?id=1279646043&web_id=1279646043"></script>
</div>
<!-- 百度统计 -->
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?77d7418dd845f98ba1cfee8596eeee3f";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<!-- Gitalk评论 -->
<link rel="stylesheet" href="https://unpkg.zhimg.com/gitalk@1.7.0/dist/gitalk.css">
<script src="https://unpkg.zhimg.com/docsify@4.11.6/lib/plugins/gitalk.min.js"></script>
<script src="https://unpkg.zhimg.com/gitalk@1.7.0/dist/gitalk.min.js"></script>
<script>
function f5Gitalk() {
window.gitalk = new Gitalk({
id: location.hash.replace('#', ''),
clientID: '19939399448841f818a1',
clientSecret: 'af67e0cc14a0f36e171895771c330471cfe36c23',
repo: 'sa-token', // 仓库名称
owner: 'click33',
admin: ['click33'], // 管理员列表
// facebook-like distraction free mode
distractionFreeMode: false
})
}
f5Gitalk();
window.onhashchange = function() {
f5Gitalk();
}
</script>
</body>
</html>
+56
View File
@@ -0,0 +1,56 @@
body{font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;}
.logo-box {position: absolute;left: 30px;top: 10px;cursor: pointer;color: #000;}
.logo-box img {width: 50px;height: 50px;vertical-align: middle;}
.logo-box .logo-text {display: inline-block;vertical-align: middle;font-size: 24px;font-weight: 400;}
#main {padding-bottom: 100px;}
#main h2 {font-size: 1.6rem;}
#main h3 {font-size: 1.25rem;}
.main-box .markdown-section{ padding: 30px 20px;}
@media screen and (max-width: 800px) {
.logo-box {display: none;}
.main-box .markdown-section{max-width: 1000px;}
}
/* 左侧树优化 */
.sidebar .sidebar-nav>ul>li>p{font-size: 1.2em; margin-top: 10px;}
.sidebar ul li a{color: #222;}
.sidebar .sidebar-nav>ul>li>ul>li>a{/* color: #222; */font-size: 16px; /* font-weight: 700; */}
/* ============== 样式优化 ================ */
/* 背景变黑 */
.main-box [data-lang]{padding: 0px !important; border-radius: 8px; overflow: hidden;}
.main-box [class^="lang-"]{border: 0px red solid; padding: 1.5em 1.2em;/* background-color: #282828; */ background-color: #090300; color: #FFF;}
.main-box [data-lang]{overflow: auto;}
/* .main-box h2{margin-top: 70px;} */
/* xml语言样式优化 */
.lang-xml .token.comment{color: #CDAB53;}
.lang-xml .token.tag *{color: #db2d20;}
.lang-xml .token.attr-value{color: #A6E22E;}
/* java语言样式优化 */
.main-box .lang-java{color: #01a252; opacity: 1;}
.lang-java .token.keyword{color: #db2d20;}
.lang-java .token.namespace,.lang-java .token.namespace *{color: #01A252; opacity: 1;}
.lang-java .token.class-name,.lang-java .cm-variable{color: #55b5db; opacity: 1;}
.lang-java .token.comment{color: #CDAB53;}
.lang-java .token.annotation.punctuation{color: #ddd;}
.lang-java .token.punctuation{color: #ddd;}
/* js语言样式优化 */
.main-box .lang-js{color: #01a252;}
.lang-js .token.comment{color: #CDAB53;}
/* .lang-js .token.string{color: #fded02;} */
.lang-js .token.string{color: #ddd;}
.lang-js .token.punctuation{color: #ddd;}
.gt-container{padding: 1.5em; padding-bottom: 100px;}
/* 调整表格的响应式 */
#main table{margin-left: 25px;}
@media screen and (min-width: 800px) {
#main table tr th{min-width: 150px;}
}

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