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

Compare commits

...

113 Commits

Author SHA1 Message Date
click33 be499e011b v.1.30.0 release 2022-05-09 19:05:46 +08:00
click33 3d75eca892 新增动态演示图. 2022-05-09 18:05:56 +08:00
click33 0975070508 增加动态演示图 2022-05-09 17:35:29 +08:00
click33 af2d851ddb 将整合redis的demo独立出来 2022-05-06 10:32:55 +08:00
click33 7e399fb63a 完善 v1.30.0 的文档 2022-05-06 10:31:46 +08:00
孔明 a30b85fc9f !132 sa-token-solon-plugin:升级 solon 到 1.7.5
Merge pull request !132 from 西东/dev
2022-05-02 11:57:03 +00:00
noear df6ea7ec4e sa-token-solon-plugin:升级 solon 到 1.7.5 2022-05-02 19:53:57 +08:00
noear 1d620fa119 sa-token-solon-plugin:升级 solon 到 1.7.5 2022-05-02 19:51:13 +08:00
click33 abba41b6f7 v1.30.0.RC up 2022-05-01 13:34:46 +08:00
click33 21147043f2 v1.30.0.RC 2022-05-01 12:18:36 +08:00
click33 a7ef71737e 重构 sa-token-jwt 模块 2022-05-01 04:46:28 +08:00
click33 d45d404fa0 重构 sa-token-jwt 插件 2022-04-30 08:21:04 +08:00
click33 7f93c2086d 修复部分不准确的注释 2022-04-30 01:26:38 +08:00
click33 443a61d404 SSO 模式三的无 sdk 对接demo. 2022-04-30 01:23:43 +08:00
click33 ed18af72b8 SSO 模式三的无 sdk 对接demo 2022-04-30 01:20:49 +08:00
click33 de39d91b71 将SSO模式三的接口调用改为签名式校验 2022-04-29 03:23:46 +08:00
click33 5e3795e29e 更换单点注销接口响应格式 2022-04-27 01:53:20 +08:00
click33 e320a8e45b json 转换器增加 toJsonString 方法 2022-04-26 19:11:48 +08:00
click33 26393d17dc 新建 json 转换器模块 2022-04-26 18:59:55 +08:00
click33 30758464ac 将 sso 模块从 core 包下拆分出来,并细分 sso 模块异常. 2022-04-26 00:48:40 +08:00
click33 a3c8b2ade2 将 sso 模块从 core 包下拆分出来,并细分 sso 模块异常 2022-04-26 00:47:31 +08:00
click33 f5cbe0616e 统一版本 2022-04-25 17:56:06 +08:00
click33 969deb9470 新增 maxLoginCount 配置,指定同一账号可同时在线的最大数量 2022-04-24 19:19:20 +08:00
click33 cfc11d0ba8 单元测试改为 junit5 2022-04-23 19:12:03 +08:00
click33 210b92f3dc 优化源码注释,更符合语义 2022-04-23 17:26:48 +08:00
click33 2c39fc7209 文档优化 2022-04-23 17:08:09 +08:00
click33 b62e2dae24 解决不必要的黄线警告 2022-04-23 16:24:15 +08:00
click33 0c0b08c6c3 修复 quick-login 插件循环依赖问题 2022-04-23 16:20:04 +08:00
click33 7302a34de0 统一版本号管理、文档部分依赖本地化 2022-04-23 14:14:49 +08:00
click33 8e3bb4db6b Merge branch 'dev' of github.com:dromara/sa-token into dev 2022-04-23 14:00:13 +08:00
click33 4af9b4a9be Merge pull request #229 from ruansheng8/dev
版本号统一管理
2022-04-23 13:56:49 +08:00
省长 042f383256 !131 修复Jboot使用redis时注册失败的BUG
Merge pull request !131 from nextStar/dev
2022-04-22 07:45:23 +00:00
星痕 2d022516c5 增加使用默认redis配置方法 2022-04-22 14:56:54 +08:00
星痕 2e82fc0592 修复jboot使用独立Redis库时创建连接错误的BUG 2022-04-22 14:47:25 +08:00
省长 1beb5afede !130 update sa-token-doc/doc/up/basic-auth.md.
Merge pull request !130 from AppleOfGray/N/A
2022-04-22 05:21:13 +00:00
AppleOfGray bcce8b4b68 update sa-token-doc/doc/up/basic-auth.md. 2022-04-22 05:19:19 +00:00
click33 bfab1b655c 更换logo地址 2022-04-21 13:54:19 +08:00
click33 2b2ef97dfb 完善赞赏名单 2022-04-21 13:35:08 +08:00
click33 2b3a558325 Merge branch 'dev' of github.com:dromara/sa-token into dev 2022-04-21 13:34:10 +08:00
省长 ca07fbdfc9 !129 升级Jboot版本到3.14.4,优化Redis缓存使用spi注册
Merge pull request !129 from nextStar/dev
2022-04-21 02:27:55 +00:00
星痕 4de21e7391 增加test配置示例 2022-04-21 10:18:29 +08:00
click33 d4ee232b9b Merge pull request #246 from dromara/dependabot/maven/sa-token-plugin/sa-token-dao-redis-jackson/com.fasterxml.jackson.core-jackson-databind-2.12.6.1
Bump jackson-databind from 2.11.2 to 2.12.6.1 in /sa-token-plugin/sa-token-dao-redis-jackson
2022-04-21 02:04:22 +08:00
click33 7199e81f6f Merge pull request #237 from BATTLEHAWK00/patch-1
增加parstToken未配置jwt密钥时的异常提示
2022-04-21 02:03:43 +08:00
dependabot[bot] 651d0c37c6 Bump jackson-databind in /sa-token-plugin/sa-token-dao-redis-jackson
Bumps [jackson-databind](https://github.com/FasterXML/jackson) from 2.11.2 to 2.12.6.1.
- [Release notes](https://github.com/FasterXML/jackson/releases)
- [Commits](https://github.com/FasterXML/jackson/commits)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-20 18:03:03 +00:00
click33 63fa7b3eed Merge pull request #241 from dromara/dependabot/maven/sa-token-starter/sa-token-spring-boot-starter/org.springframework.boot-spring-boot-starter-web-2.5.12
Bump spring-boot-starter-web from 2.0.0.RELEASE to 2.5.12 in /sa-token-starter/sa-token-spring-boot-starter
2022-04-21 02:02:39 +08:00
省长 4a78c8d74f !124 sso,oauth2插件中调用配置类使用getter方法
Merge pull request !124 from Naah/config-getter-invoke
2022-04-20 17:46:39 +00:00
省长 6a3a2c20ad !125 update sa-token-doc/doc/fun/sa-token-context.md.
Merge pull request !125 from AppleOfGray/N/A
2022-04-20 17:35:51 +00:00
省长 7853c1bea0 !128 sa-token-solon-plugin:升级 solon 到 1.7.2
Merge pull request !128 from 西东/dev
2022-04-20 17:28:24 +00:00
noear faa63fe327 sa-token-solon-plugin:升级 solon 到 1.7.2 2022-04-18 14:04:41 +08:00
AppleOfGray 5f341d97ba update sa-token-doc/doc/fun/sa-token-context.md.
多了一个 "都" 字
2022-04-14 06:35:34 +00:00
naah69 20063fbd41 还原yml 2022-04-14 13:33:17 +08:00
naah69 9de31236c6 sso,oauth2插件中调用配置类使用getter方法 2022-04-14 11:24:03 +08:00
星痕 f5881f2d1c 升级Jboot版本到3.14.4,使用Jboot框架默认的缓存存取token的方法 2022-04-13 10:28:34 +08:00
省长 d30509f6cc !123 新增使用 Sa-Token 的开源项目链接。
Merge pull request !123 from 拾壹/dev
2022-04-02 08:10:07 +00:00
quequnlong 0900b0d06f 新增sa-token开源项目链接 2022-04-02 16:04:40 +08:00
省长 897ed026e7 !122 update sa-token-doc/doc/more/link.md.
Merge pull request !122 from AppleOfGray/N/A
2022-04-02 06:36:02 +00:00
AppleOfGray 9b2c4cf127 update sa-token-doc/doc/more/link.md.
添加一个使用 Sa-Token 的开源项目链接
2022-04-02 06:31:05 +00:00
dependabot[bot] 7f416d9e29 Bump spring-boot-starter-web
Bumps [spring-boot-starter-web](https://github.com/spring-projects/spring-boot) from 2.0.0.RELEASE to 2.5.12.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.0.0.RELEASE...v2.5.12)

---
updated-dependencies:
- dependency-name: org.springframework.boot:spring-boot-starter-web
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-31 19:54:31 +00:00
省长 53af6a3c20 !120 更新token有效期文档
Merge pull request !120 from AppleOfGray/N/A
2022-03-31 07:31:28 +00:00
省长 5892a3907c !121 update sa-token-doc/doc/use/at-check.md.
Merge pull request !121 from AppleOfGray/N/A
2022-03-31 07:31:12 +00:00
AppleOfGray d6cad032ac update sa-token-doc/doc/use/at-check.md.
spring boot2.6.5版本
satoken 版本 1.29.0 (没有redis集成)
```
@RequestMapping("/x")
    public String get() {
        return "你好";
}
```

注解拦截配置类加入 @EnableWebMvc 后返回中文字符串乱码
注解拦截配置类不加 @EnableWebMvc 则正常返回中文字符串
2022-03-31 07:30:10 +00:00
省长 072e3a5217 !119 添加 sa-token-dao-redisx 插件(无 Spring 依赖)
Merge pull request !119 from 西东/dev
2022-03-31 03:56:10 +00:00
AppleOfGray 8c3cc23be7 更新token有效期文档
添加支持自动续签的注解说明表
2022-03-31 03:09:00 +00:00
noear 10845ee849 sa-token-solon-plugin:添加 SaTokenDao 主入支持 2022-03-30 23:04:01 +08:00
noear aa252f831c 添加 sa-token-dao-redisx 插件,用于适配redisx框架(无 spring 依赖) 2022-03-30 22:59:00 +08:00
noear c44ad67117 sa-token-solon-plugin: 升级到 solon 1.6.34 2022-03-30 22:58:05 +08:00
省长 d115d04319 !116 提示顺序修复
Merge pull request !116 from AppleOfGray/N/A
2022-03-26 13:38:17 +00:00
AppleOfGray 9500c0bd56 提示顺序修复 2022-03-25 08:47:13 +00:00
BATTLEHAWK 563c6b7334 Update SaJwtUtil.java 2022-03-22 20:34:06 +08:00
省长 ee5729e607 !115 升级Jboot版本到3.14.2,使用jBoot框架默认的缓存存取token的方法
Merge pull request !115 from nextStar/dev
2022-03-21 14:15:07 +00:00
星痕 914d9c4d1e 升级Jboot版本到3.14.2,使用jBoot框架默认的缓存存取token的方法 2022-03-21 14:13:12 +08:00
省长 81daf2c300 !114 项目链接被文档重构干掉了
Merge pull request !114 from 疯狂的狮子Li/N/A
2022-03-20 15:56:40 +00:00
疯狂的狮子Li b5991a71ca 项目链接被文档重构干掉了 2022-03-20 15:09:48 +00:00
click33 88d88198f1 文档优化 2022-03-16 16:51:14 +08:00
省长 b4dcf5f02d !111 update sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java.
Merge pull request !111 from AppleOfGray/N/A
2022-03-11 04:21:59 +00:00
省长 2abc4bab3c !112 update sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java.
Merge pull request !112 from AppleOfGray/N/A
2022-03-11 04:21:52 +00:00
省长 7fafb070ff !110 update sa-token-doc/doc/fun/token-timeout.md.
Merge pull request !110 from AppleOfGray/N/A
2022-03-11 04:21:38 +00:00
省长 4109dc35e2 !113 update sa-token-doc/doc/sso/sso-h5.md.
Merge pull request !113 from AppleOfGray/N/A
2022-03-11 04:19:25 +00:00
AppleOfGray 357aab49d6 update sa-token-doc/doc/sso/sso-h5.md.
这个名字的demo项目不存在, 是否是启动sa-token-demo-sso-server项目?
2022-03-11 04:14:44 +00:00
AppleOfGray 22a3620de5 update sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java.
即时->即使
2022-03-11 02:14:19 +00:00
AppleOfGray 7292ea5fca update sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java.
即时->即使
2022-03-11 02:14:03 +00:00
AppleOfGray 4ef67ee282 update sa-token-doc/doc/fun/token-timeout.md.
添加支持自动续期的方法
2022-03-11 02:06:52 +00:00
省长 fee291180b !107 sa-token-solon-plugin: 升级到 solon 1.6.29
Merge pull request !107 from 西东/dev
2022-03-08 08:36:22 +00:00
省长 98bdea8e23 !108 update sa-token-doc/doc/use/route-check.md.
Merge pull request !108 from AppleOfGray/N/A
2022-03-08 08:36:13 +00:00
AppleOfGray b3dc39d5fd update sa-token-doc/doc/use/route-check.md.
原本的示例感觉有歧义
2022-03-08 08:34:46 +00:00
noear e444b88521 sa-token-solon-plugin: 升级到 solon 1.6.29 2022-03-07 16:59:09 +08:00
ruansheng 54dcbe2a88 版本号统一管理 2022-03-06 17:36:13 +08:00
省长 9de70bc8be !106 update sa-token-doc/doc/more/link.md.
Merge pull request !106 from lijiaxing_boy/N/A
2022-02-28 15:06:42 +00:00
lijiaxing_boy c9a1b369f2 update sa-token-doc/doc/more/link.md. 2022-02-28 14:46:05 +00:00
省长 5a1e1be85a !105 update sa-token-doc/doc/plugin/jwt-extend.md.
Merge pull request !105 from AppleOfGray/N/A
2022-02-21 08:51:08 +00:00
AppleOfGray de68b9365b update sa-token-doc/doc/plugin/jwt-extend.md. 2022-02-21 08:50:30 +00:00
省长 249ceceae3 !104 update sa-token-doc/doc/more/link.md.
Merge pull request !104 from Jwss/N/A
2022-02-18 10:41:49 +00:00
Jwss b97e199e92 update sa-token-doc/doc/more/link.md.
使用了Sa-Token的开源项目
2022-02-18 08:21:05 +00:00
省长 b0e92a252a !103 【轻量级 PR】:投递 RuoYi-Cloud-Plus 新框架 修改老框架地址
Merge pull request !103 from 疯狂的狮子Li/N/A
2022-02-18 03:46:56 +00:00
疯狂的狮子Li ca39ced1b1 投递 RuoYi-Cloud-Plus 新框架 修改老框架地址 2022-02-18 03:45:23 +00:00
省长 f596a5ea5d !102 fix 修复内网鉴权报错 测试通过
Merge pull request !102 from 疯狂的狮子Li/N/A
2022-02-15 06:41:45 +00:00
疯狂的狮子Li 42acb14ea7 fix 修复内网鉴权报错 测试通过 2022-02-15 06:37:01 +00:00
click33 a1e995e800 1.29.1.trial 2022-02-14 08:49:36 +08:00
click33 31c1c42cc4 修复整合 jwt-Style 模式时,StpUtil.getExtra("") 无效的bug 2022-02-12 00:34:50 +08:00
click33 e12077a1a2 Merge pull request #217 from dromara/dependabot/maven/sa-token-plugin/sa-token-context-dubbo/org.apache.dubbo-dubbo-2.7.15
Bump dubbo from 2.7.11 to 2.7.15 in /sa-token-plugin/sa-token-context-dubbo
2022-02-12 00:07:33 +08:00
click33 1d60239d22 取出警告线 2022-02-11 23:11:45 +08:00
省长 8a00231000 !101 update sa-token-doc/doc/use/jur-auth.md.
Merge pull request !101 from haappy/N/A
2022-02-11 14:08:38 +00:00
省长 4d7dd1ab3e !100 Jboot,jFinal插件更新,优化解决缓存序列化出现乱码的问题
Merge pull request !100 from nextStar/dev
2022-02-11 14:08:05 +00:00
haappy 3f5dd24ae2 update sa-token-doc/doc/use/jur-auth.md.
多了个字
2022-02-11 07:47:42 +00:00
zhaofeng\zf cce907fd20 处理因jfinal,jboot默认序列化方法不同导致的乱码问题,统一使用SaJdkSerializer进行缓存序列化 2022-02-11 15:27:25 +08:00
zhaofeng\zf f05818d0ea Merge branch 'dev' of https://gitee.com/nxstv/sa-token into dev 2022-02-11 15:12:40 +08:00
click33 40a5cdc6dc Web-Socket 鉴权示例 2022-02-11 02:57:11 +08:00
click33 fbc139b430 更改导航栏样式 2022-02-11 02:53:23 +08:00
dependabot[bot] 7e366326e4 Bump dubbo in /sa-token-plugin/sa-token-context-dubbo
Bumps [dubbo](https://github.com/apache/dubbo) from 2.7.11 to 2.7.15.
- [Release notes](https://github.com/apache/dubbo/releases)
- [Changelog](https://github.com/apache/dubbo/blob/3.0/CHANGES.md)
- [Commits](https://github.com/apache/dubbo/compare/dubbo-2.7.11...dubbo-2.7.15)

---
updated-dependencies:
- dependency-name: org.apache.dubbo:dubbo
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-09 12:30:50 +00:00
赵锋 6363db248a 修复jFinal缓存处理类BUG 2021-12-20 17:15:38 +08:00
赵锋 9c852da49b 修复jFinal,jboot插件Redis缓存方法与通用方法不一致的问题
增加缓存序列化方法
2021-12-17 12:04:44 +08:00
赵锋 4784cbd103 修复jFinal,jboot插件Redis缓存方法与通用方法不一致的问题
增加缓存序列化方法
2021-12-17 11:15:04 +08:00
赵锋 40a6da10fe 新增 jboot,jfinal项目的运行插件 2021-11-23 16:29:52 +08:00
261 changed files with 9757 additions and 2436 deletions
+1 -1
View File
@@ -13,4 +13,4 @@ unpackage/
.idea/
.flattened-pom.xml
+23 -90
View File
@@ -1,7 +1,7 @@
<p align="center">
<img alt="logo" src="https://gitee.com/dromara/sa-token/raw/master/sa-token-doc/doc/logo.png" width="150" height="150">
<img alt="logo" src="https://sa-token.dev33.cn/doc/logo.png" width="150" height="150">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.29.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.30.0</h1>
<h4 align="center">一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!</h4>
<p align="center">
<a href="https://gitee.com/dromara/sa-token/stargazers"><img src="https://gitee.com/dromara/sa-token/badge/star.svg?theme=gvp"></a>
@@ -28,64 +28,6 @@
**Sa-Token** 是一个轻量级 Java 权限认证框架,主要解决:**`登录认证`**、**`权限认证`**、**`Session会话`**、**`单点登录`**、**`OAuth2.0`**、**`微服务网关鉴权`**
等一系列权限相关问题。
Sa-Token 的 API 设计非常简单,有多简单呢?以登录认证为例,你只需要:
``` java
// 在登录时写入当前会话的账号id
StpUtil.login(10001);
// 然后在需要校验登录处调用以下方法:
// 如果当前会话未登录,这句代码会抛出 `NotLoginException` 异常
StpUtil.checkLogin();
```
至此,我们已经借助 Sa-Token 完成登录认证!
此时的你小脑袋可能飘满了问号,就这么简单?自定义 Realm 呢?全局过滤器呢?我不用写各种配置文件吗?
没错,在 Sa-Token 中,登录认证就是如此简单,不需要任何的复杂前置工作,只需这一行简单的API调用,就可以完成会话登录认证!
当你受够 Shiro、SpringSecurity 等框架的三拜九叩之后,你就会明白,相对于这些传统老牌框架,Sa-Token 的 API 设计是多么的简单、优雅!
权限认证示例(只有具备 `user:add` 权限的会话才可以进入请求)
``` java
@SaCheckPermission("user:add")
@RequestMapping("/user/insert")
public String insert(SysUser user) {
// ...
return "用户增加";
}
```
将某个账号踢下线(待到对方再次访问系统时会抛出`NotLoginException`异常)
``` java
// 将账号id为 10001 的会话踢下线
StpUtil.kickout(10001);
```
在 Sa-Token 中,绝大多数功能都可以 **一行代码** 完成:
``` java
StpUtil.login(10001); // 标记当前会话登录的账号id
StpUtil.getLoginId(); // 获取当前会话登录的账号id
StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false
StpUtil.logout(); // 当前会话注销登录
StpUtil.kickout(10001); // 将账号为10001的会话踢下线
StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识, 返回true或false
StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false
StpUtil.getSession(); // 获取当前账号id的Session
StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session
StpUtil.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值
StpUtil.login(10001, "PC"); // 指定设备标识登录,常用于“同端互斥登录”
StpUtil.kickout(10001, "PC"); // 指定账号指定设备标识踢下线 (不同端不受影响)
StpUtil.openSafe(120); // 在当前会话开启二级认证,有效期为120秒
StpUtil.checkSafe(); // 校验当前会话是否处于二级认证有效期内,校验失败会抛出异常
StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
```
即使不运行测试,相信您也能意会到绝大多数 API 的用法。
## Sa-Token 功能一览
- **登录认证** —— 单端登录、多端登录、同端互斥登录、七天内免登录
- **权限认证** —— 权限认证、角色认证、会话二级认证
@@ -119,7 +61,7 @@ StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
## Sa-Token-SSO 单点登录
网上的单点登录教程大多以CAS流程为主,其实对于不同的系统架构,实现单点登录的步骤也大为不同,Sa-Token由简入难将其划分为三种模式:
Sa-Token-SSO 由简入难划分为三种模式,解决不同架构下的 SSO 接入问题
| 系统架构 | 采用模式 | 简介 | 文档链接 |
| :-------- | :-------- | :-------- | :-------- |
@@ -131,8 +73,6 @@ StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
1. 前端同域:就是指多个系统可以部署在同一个主域名之下,比如:`c1.domain.com``c2.domain.com``c3.domain.com`
2. 后端同Redis:就是指多个系统可以连接同一个Redis。PS:这里并不需要把所有项目的数据都放在同一个Redis中,Sa-Token提供了 **`[权限缓存与业务缓存分离]`** 的解决方案,详情戳:[Alone独立Redis插件](http://sa-token.dev33.cn/doc/index.html#/plugin/alone-redis)
3. 如果既无法做到前端同域,也无法做到后端同Redis,那么只能走模式三,Http请求获取会话(Sa-Token对SSO提供了完整的封装,你只需要按照示例从文档上复制几段代码便可以轻松集成)
4. 技术选型一定要根据系统架构对症下药,切不可胡乱选择
## Sa-Token-OAuth2.0 授权登录
Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749) 编写,通过Sa-OAuth2你可以非常轻松的实现系统的OAuth2.0授权认证
@@ -154,54 +94,47 @@ Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749)
![sa-token-rz](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/x/sa-token-rz2.png 's-w')
## Star 趋势
[![giteye-chart](https://chart.giteye.net/gitee/dromara/sa-token/77YQZ6UK.png 'Gitee')](https://giteye.net/chart/77YQZ6UK)
[![github-chart](https://starchart.cc/dromara/sa-token.svg 'GitHub')](https://starchart.cc/dromara/sa-token)
## 使用Sa-Token的开源项目
- **[ sa-plus ]**[一个基于 SpringBoot 架构的快速开发框架,内置代码生成器](https://gitee.com/click33/sa-plus)
- [[ sa-plus ]](https://gitee.com/click33/sa-plus):一个基于 SpringBoot 架构的快速开发框架,内置代码生成器
- **[ jthink ]** [一个基于 SpringBoot + Sa-Token + Thymeleaf 的博客系统](https://gitee.com/wtsoftware/jthink)
- [[ jthink ]](https://gitee.com/wtsoftware/jthink) 一个基于 SpringBoot + Sa-Token + Thymeleaf 的博客系统
- **[ dcy-fast ]**[ 一个基于 SpringBoot + Sa-Token + Mybatis-Plus 的后台管理系统,前端vue-element-admin,并且内置代码生成器](https://gitee.com/dcy421/dcy-fast)
- [[ dcy-fast ]](https://gitee.com/dcy421/dcy-fast) 一个基于 SpringBoot + Sa-Token + Mybatis-Plus 的后台管理系统,前端vue-element-admin,并且内置代码生成器
- **[ helio-starters ]**[ 单体 Boot 版脚手架 + 微服务 Cloud 版脚手架,带有配套后台管理前端模板及代码生成器](https://gitee.com/uncarbon97/helio-starters)
- [[ helio-starters ]](https://gitee.com/uncarbon97/helio-starters) 单体 Boot 版脚手架 + 微服务 Cloud 版脚手架,带有配套后台管理前端模板及代码生成器
- **[ sa-token-plugin ]**[Sa-Token第三方插件实现,基于Sa-Token-Core,提供一些与官方不同实现机制的的插件集合,作为Sa-Token开源生态的补充](https://gitee.com/bootx/sa-token-plugin)
- [[ sa-token-plugin ]](https://gitee.com/bootx/sa-token-plugin)Sa-Token第三方插件实现,基于Sa-Token-Core,提供一些与官方不同实现机制的的插件集合,作为Sa-Token开源生态的补充
- **[ easy-admin ]**[一个基于SpringBoot2 + Sa-Token + Mybatis-Plus + Snakerflow + Layui 的后台管理系统,灵活多变可前后端分离,也可单体,内置代码生成器、权限管理、工作流引擎等](https://gitee.com/lakernote/easy-admin)
- [[ easy-admin ]](https://gitee.com/lakernote/easy-admin)一个基于SpringBoot2 + Sa-Token + Mybatis-Plus + Snakerflow + Layui 的后台管理系统,灵活多变可前后端分离,也可单体,内置代码生成器、权限管理、工作流引擎等
- **[ RuoYi-Vue-Plus ]**[基于 RuoYi-Vue 集成 SaToken + Lombok + Mybatis-Plus + Undertow + knife4j + Hutool + Feign 重写所有原生业务 定期与 RuoYi-Vue 同步](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/satoken/)
- [[ RuoYi-Vue-Plus ]](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus):重写RuoYi-Vue所有功能 集成 Sa-Token+Mybatis-Plus+Jackson+Xxl-Job+knife4j+Hutool+OSS 定期同步
- **[ falser-cloud ]**: [基于 SpringCloud Alibaba + SpringCloud gateway + SpringBoot + Sa-Token + vue-admin-template + Nacos + Rabbit MQ + Redis 的一个后台管理系统,前后端分离,权限管理,菜单管理,数据字典,停车场系统管理等功能](https://gitee.com/falser/falser-cloud)
- [[ RuoYi-Cloud-Plus ]](https://gitee.com/JavaLionLi/RuoYi-Cloud-Plus):重写RuoYi-Cloud所有功能 整合 SpringCloudAlibaba Dubbo3.0 Sa-Token Mybatis-Plus MQ OSS ES Xxl-Job Docker 全方位升级 定期同步
- **[ bootx-platform ]**[集成sa-token和sa-token-plugin并深度定制认证模块,包含多级别数据范围权限、数据自动加解密、数据脱敏、超级查询器、以及支付收单、消息通知等准商用功能的开源免费开发脚手架项目](https://gitee.com/bootx/bootx-platform)
- [[ falser-cloud ]](https://gitee.com/falser/falser-cloud): 基于 SpringCloud Alibaba + SpringCloud gateway + SpringBoot + Sa-Token + vue-admin-template + Nacos + Rabbit MQ + Redis 的一个后台管理系统,前后端分离,权限管理,菜单管理,数据字典,停车场系统管理等功能
- **[ QForum-Core ]**[QForum 论坛系统官方核心,可拓展性强、轻量级、高性能、前后端分离,基于 SpringBoot2 + Sa-Token + Mybatis-Plus](https://github.com/Project-QForum/QForum-Core/)
- [[ bootx-platform ]](https://gitee.com/bootx/bootx-platform):集成sa-token和sa-token-plugin并深度定制认证模块,包含多级别数据范围权限、数据自动加解密、数据脱敏、超级查询器、以及支付收单、消息通知等准商用功能的开源免费开发脚手架项目
- **[ ExciteCMS-Layui ]**[ExciteCMS 快速开发脚手架:一款后端基于 SpringBoot2 + Sa-Token + Mybatis-Plus,前端基于 Layuimini 的内容管理系统,具备RBAC、日志管理、代码生成等功能,并集成常用的支付、OSS等第三方服务,拥有详细的开发文档](https://gitee.com/ExciteTeam/ExciteCMS-SpringBoot-Layui)
- [[ QForum-Core ]](https://github.com/Project-QForum/QForum-Core/)QForum 论坛系统官方核心,可拓展性强、轻量级、高性能、前后端分离,基于 SpringBoot2 + Sa-Token + Mybatis-Plus
- [[ ExciteCMS-Layui ]](https://gitee.com/ExciteTeam/ExciteCMS-SpringBoot-Layui)ExciteCMS 快速开发脚手架:一款后端基于 SpringBoot2 + Sa-Token + Mybatis-Plus,前端基于 Layuimini 的内容管理系统,具备RBAC、日志管理、代码生成等功能,并集成常用的支付、OSS等第三方服务,拥有详细的开发文档
- [[ 拾壹博客 ]](https://gitee.com/quequnlong/shiyi-blog):一款vue+springboot前后端分离的博客系统,博客后台管理系统使用了vue+elmentui开发,后端使用Sa-Token进行权限管理,支持动态菜单权限,动态定时任务,文件支持本地和七牛云上传,使用ElasticSearch作为全文检索服务,支持QQ、微博、码云登录。
如果您的项目使用了Sa-Token,欢迎提交pr
## 友情链接
- **[ OkHttps ]**[ 一个轻量级http通信框架,API设计无比优雅,支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps)
- [[ OkHttps ]](https://gitee.com/ejlchina-zhxu/okhttps)一个轻量级http通信框架,API设计无比优雅,支持 WebSocket 以及 Stomp 协议
- **[ Bean Searcher ]**[ 比 MyBatis 效率快 100 倍的条件检索引擎,天生支持联表,使一行代码实现复杂列表检索成为可能!](https://github.com/ejlchina/bean-searcher)
- [[ Bean Searcher ]](https://github.com/ejlchina/bean-searcher)比 MyBatis 效率快 100 倍的条件检索引擎,天生支持联表,使一行代码实现复杂列表检索成为可能!
- **[ 小诺快速开发平台 ]**[ 基于SpringBoot2 + AntDesignVue全新快速开发平台,同时拥有三个版本](https://xiaonuo.vip/index#pricing)
- [[ 小诺快速开发平台 ]](https://xiaonuo.vip/index#pricing)基于SpringBoot2 + AntDesignVue全新快速开发平台,同时拥有三个版本
- **[ Jpom ]**[ 简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件](https://gitee.com/dromara/Jpom)
- [[ Jpom ]](https://gitee.com/dromara/Jpom):简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件
- **[ TLog ]**[ 一个轻量级的分布式日志标记追踪神器](https://gitee.com/dromara/TLog)
- [[ TLog ]](https://gitee.com/dromara/TLog):一个轻量级的分布式日志标记追踪神器
## 贡献者名单
感谢每一个为 Sa-Token 贡献代码的小伙伴
[![Giteye chart](https://chart.giteye.net/gitee/dromara/sa-token/CGZ7GT8E.png)](https://giteye.net/chart/CGZ7GT8E)
## 交流群
QQ交流群:1群:1002350610 (已满) 、
+18 -69
View File
@@ -5,75 +5,24 @@ call mvn clean
:: demo模块clean
cd sa-token-demo
cd sa-token-demo-jwt
call mvn clean
cd ..
cd sa-token-demo-springboot
call mvn clean
cd ..
cd sa-token-demo-webflux
call mvn clean
cd ..
cd sa-token-demo-solon
call mvn clean
cd ..
cd sa-token-demo-oauth2-client
call mvn clean
cd ..
cd sa-token-demo-oauth2-server
call mvn clean
cd ..
cd sa-token-demo-quick-login
call mvn clean
cd ..
cd sa-token-demo-alone-redis
call mvn clean
cd ..
cd sa-token-demo-thymeleaf
call mvn clean
cd ..
cd sa-token-demo-sso-server
call mvn clean
cd ..
cd sa-token-demo-sso1-client
call mvn clean
cd ..
cd sa-token-demo-sso2-client
call mvn clean
cd ..
cd sa-token-demo-sso3-client
call mvn clean
cd ..
cd sa-token-demo-jwt
call mvn clean
cd ..
cd sa-token-demo-jwt
call mvn clean
cd ..
cd sa-token-demo-dubbo-provider
call mvn clean
cd ..
cd sa-token-demo-dubbo-consumer
call mvn clean
cd ..
cd sa-token-demo-springboot & call mvn clean & cd ..
cd sa-token-demo-webflux & call mvn clean & cd ..
cd sa-token-demo-solon & call mvn clean & cd ..
cd sa-token-demo-oauth2-client & call mvn clean & cd ..
cd sa-token-demo-oauth2-server & call mvn clean & cd ..
cd sa-token-demo-quick-login & call mvn clean & cd ..
cd sa-token-demo-alone-redis & call mvn clean & cd ..
cd sa-token-demo-thymeleaf & call mvn clean & cd ..
cd sa-token-demo-sso-server & call mvn clean & cd ..
cd sa-token-demo-sso1-client & call mvn clean & cd ..
cd sa-token-demo-sso2-client & call mvn clean & cd ..
cd sa-token-demo-sso3-client & call mvn clean & cd ..
cd sa-token-demo-sso3-client-nosdk & call mvn clean & cd ..
cd sa-token-demo-jwt & call mvn clean & cd ..
cd sa-token-demo-dubbo-provider & call mvn clean & cd ..
cd sa-token-demo-dubbo-consumer & call mvn clean & cd ..
cd sa-token-demo-websocket & call mvn clean & cd ..
cd sa-token-demo-websocket-spring & call mvn clean & cd ..
cd ..
+30 -2
View File
@@ -8,7 +8,7 @@
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<packaging>pom</packaging>
<version>1.29.0</version>
<version>${revision}</version>
<!-- 项目介绍 -->
<name>sa-token</name>
@@ -37,7 +37,7 @@
<!-- 一些属性 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
<revision>1.30.0</revision>
<jdk.version>1.8</jdk.version>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
@@ -86,6 +86,34 @@
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 统一版本号管理 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.2.7</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
+2 -1
View File
@@ -7,7 +7,8 @@
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.29.0</version>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
@@ -3,8 +3,6 @@ package cn.dev33.satoken;
import java.util.HashMap;
import java.util.Map;
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.context.SaTokenContext;
@@ -13,8 +11,12 @@ import cn.dev33.satoken.context.second.SaTokenSecondContext;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.SaTokenDaoDefaultImpl;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.json.SaJsonTemplate;
import cn.dev33.satoken.json.SaJsonTemplateDefaultImpl;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.listener.SaTokenListenerDefaultImpl;
import cn.dev33.satoken.sign.SaSignTemplate;
import cn.dev33.satoken.sign.SaSignTemplateDefaultImpl;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
import cn.dev33.satoken.stp.StpLogic;
@@ -28,7 +30,6 @@ import cn.dev33.satoken.util.SaFoxUtil;
* @author kong
*
*/
@SuppressWarnings("deprecation")
public class SaManager {
/**
@@ -93,24 +94,6 @@ public class SaManager {
return stpInterface;
}
/**
* 框架行为 Bean
*/
private volatile static SaTokenAction saTokenAction;
public static void setSaTokenAction(SaTokenAction saTokenAction) {
SaManager.saTokenAction = saTokenAction;
}
public static SaTokenAction getSaTokenAction() {
if (saTokenAction == null) {
synchronized (SaManager.class) {
if (saTokenAction == null) {
setSaTokenAction(new SaTokenActionDefaultImpl());
}
}
}
return saTokenAction;
}
/**
* 上下文Context Bean
*/
@@ -192,6 +175,42 @@ public class SaManager {
}
return saTemp;
}
/**
* JSON 转换器 Bean
*/
private volatile static SaJsonTemplate saJsonTemplate;
public static void setSaJsonTemplate(SaJsonTemplate saJsonTemplate) {
SaManager.saJsonTemplate = saJsonTemplate;
}
public static SaJsonTemplate getSaJsonTemplate() {
if (saJsonTemplate == null) {
synchronized (SaManager.class) {
if (saJsonTemplate == null) {
setSaJsonTemplate(new SaJsonTemplateDefaultImpl());
}
}
}
return saJsonTemplate;
}
/**
* 参数签名 Bean
*/
private volatile static SaSignTemplate saSignTemplate;
public static void setSaSignTemplate(SaSignTemplate saSignTemplate) {
SaManager.saSignTemplate = saSignTemplate;
}
public static SaSignTemplate getSaSignTemplate() {
if (saSignTemplate == null) {
synchronized (SaManager.class) {
if (saSignTemplate == null) {
setSaSignTemplate(new SaSignTemplateDefaultImpl());
}
}
}
return saSignTemplate;
}
/**
* StpLogic集合, 记录框架所有成功初始化的StpLogic
@@ -1,54 +0,0 @@
package cn.dev33.satoken.action;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
import cn.dev33.satoken.session.SaSession;
/**
* <h1> v1.27+ 此接口已废弃,目前版本暂时向下兼容,请及时更换为 SaStrategy </h1>
* <p>Sa-Token 逻辑代理接口 </p>
* <p>此接口将会代理框架内部的一些关键性逻辑,方便开发者进行按需重写</p>
* @author kong
*
*/
@Deprecated
public interface SaTokenAction {
/**
* 创建一个Token
* @param loginId 账号id
* @param loginType 账号类型
* @return token
*/
public String createToken(Object loginId, String loginType);
/**
* 创建一个Session
* @param sessionId Session的Id
* @return 创建后的Session
*/
public SaSession createSession(String sessionId);
/**
* 判断:集合中是否包含指定元素(模糊匹配)
* @param list 集合
* @param element 元素
* @return 是否包含
*/
public boolean hasElement(List<String> list, String element);
/**
* 对一个Method对象进行注解检查(注解鉴权内部实现)
* @param method Method对象
*/
public void checkMethodAnnotation(Method method);
/**
* 从指定元素校验注解
* @param target /
*/
public void validateAnnotation(AnnotatedElement target);
}
@@ -1,150 +0,0 @@
package cn.dev33.satoken.action;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
import java.util.UUID;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckBasic;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.basic.SaBasicUtil;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaTokenConsts;
/**
* <h1> v1.27+ 此接口已废弃,目前版本暂时向下兼容,请及时更换为 SaStrategy </h1>
* <p> Sa-Token 逻辑代理接口 [默认实现类] </p>
* @author kong
*
*/
@Deprecated
public class SaTokenActionDefaultImpl implements SaTokenAction {
/**
* 创建一个Token
*/
@Override
public String createToken(Object loginId, String loginType) {
// 根据配置的tokenStyle生成不同风格的token
String tokenStyle = SaManager.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 SaFoxUtil.getRandomString(32);
}
// 64位随机字符串
if(SaTokenConsts.TOKEN_STYLE_RANDOM_64.equals(tokenStyle)) {
return SaFoxUtil.getRandomString(64);
}
// 128位随机字符串
if(SaTokenConsts.TOKEN_STYLE_RANDOM_128.equals(tokenStyle)) {
return SaFoxUtil.getRandomString(128);
}
// tik风格 (2_14_16)
if(SaTokenConsts.TOKEN_STYLE_TIK.equals(tokenStyle)) {
return SaFoxUtil.getRandomString(2) + "_" + SaFoxUtil.getRandomString(14) + "_" + SaFoxUtil.getRandomString(16) + "__";
}
// 默认,还是uuid
return UUID.randomUUID().toString();
}
/**
* 创建一个Session
*/
@Override
public SaSession createSession(String sessionId) {
return new SaSession(sessionId);
}
/**
* 判断:集合中是否包含指定元素(模糊匹配)
*/
@Override
public boolean hasElement(List<String> list, String element) {
// 空集合直接返回false
if(list == null || list.size() == 0) {
return false;
}
// 先尝试一下简单匹配,如果可以匹配成功则无需继续模糊匹配
if (list.contains(element)) {
return true;
}
// 开始模糊匹配
for (String patt : list) {
if(SaFoxUtil.vagueMatch(patt, element)) {
return true;
}
}
// 走出for循环说明没有一个元素可以匹配成功
return false;
}
/**
* 对一个Method对象进行注解检查(注解鉴权内部实现)
*/
@Override
public void checkMethodAnnotation(Method method) {
// 先校验 Method 所属 Class 上的注解
validateAnnotation(method.getDeclaringClass());
// 再校验 Method 上的注解
validateAnnotation(method);
}
/**
* 从指定元素校验注解
* @param target see note
*/
public void validateAnnotation(AnnotatedElement target) {
// 校验 @SaCheckLogin 注解
SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.me.getAnnotation.apply(target, SaCheckLogin.class);
if(checkLogin != null) {
SaManager.getStpLogic(checkLogin.type()).checkByAnnotation(checkLogin);
}
// 校验 @SaCheckRole 注解
SaCheckRole checkRole = (SaCheckRole) SaStrategy.me.getAnnotation.apply(target, SaCheckRole.class);
if(checkRole != null) {
SaManager.getStpLogic(checkRole.type()).checkByAnnotation(checkRole);
}
// 校验 @SaCheckPermission 注解
SaCheckPermission checkPermission = (SaCheckPermission) SaStrategy.me.getAnnotation.apply(target, SaCheckPermission.class);
if(checkPermission != null) {
SaManager.getStpLogic(checkPermission.type()).checkByAnnotation(checkPermission);
}
// 校验 @SaCheckSafe 注解
SaCheckSafe checkSafe = (SaCheckSafe) SaStrategy.me.getAnnotation.apply(target, SaCheckSafe.class);
if(checkSafe != null) {
SaManager.getStpLogic(checkSafe.type()).checkByAnnotation(checkSafe);
}
// 校验 @SaCheckBasic 注解
SaCheckBasic checkBasic = (SaCheckBasic) SaStrategy.me.getAnnotation.apply(target, SaCheckBasic.class);
if(checkBasic != null) {
SaBasicUtil.check(checkBasic.realm(), checkBasic.account());
}
}
}
@@ -3,7 +3,7 @@ package cn.dev33.satoken.config;
import java.io.Serializable;
/**
* Sa-Token 配置类 Model
* Sa-Token 配置类 Model
* <p>
* 你可以通过yml、properties、java代码等形式配置本类参数,具体请查阅官方文档: http://sa-token.dev33.cn/
*
@@ -32,6 +32,11 @@ public class SaTokenConfig implements Serializable {
/** 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) */
private Boolean isShare = true;
/**
* 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置才有效)
*/
private int maxLoginCount = 12;
/** 是否尝试从请求体里读取token */
private Boolean isReadBody = true;
@@ -88,11 +93,6 @@ public class SaTokenConfig implements Serializable {
*/
public SaCookieConfig cookie = new SaCookieConfig();
/**
* SSO单点登录配置对象
*/
public SaSsoConfig sso = new SaSsoConfig();
/**
* @return token名称 (同时也是cookie名称)
@@ -176,6 +176,22 @@ public class SaTokenConfig implements Serializable {
return this;
}
/**
* @return 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置才有效)
*/
public int getMaxLoginCount() {
return maxLoginCount;
}
/**
* @param maxLoginCount 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置才有效)
* @return 对象自身
*/
public SaTokenConfig setMaxLoginCount(int maxLoginCount) {
this.maxLoginCount = maxLoginCount;
return this;
}
/**
* @return 是否尝试从请求体里读取token
*/
@@ -418,22 +434,6 @@ public class SaTokenConfig implements Serializable {
return this;
}
/**
* @return SSO单点登录配置对象
*/
public SaSsoConfig getSso() {
return sso;
}
/**
* @param sso SSO单点登录配置对象
* @return 对象自身
*/
public SaTokenConfig setSso(SaSsoConfig sso) {
this.sso = sso;
return this;
}
/**
* @return Cookie 全局配置对象
*/
@@ -458,6 +458,7 @@ public class SaTokenConfig implements Serializable {
+ ", activityTimeout=" + activityTimeout
+ ", isConcurrent=" + isConcurrent
+ ", isShare=" + isShare
+ ", maxLoginCount=" + maxLoginCount
+ ", isReadBody=" + isReadBody
+ ", isReadHead=" + isReadHead
+ ", isReadCookie=" + isReadCookie
@@ -473,7 +474,6 @@ public class SaTokenConfig implements Serializable {
+ ", basic=" + basic
+ ", currDomain=" + currDomain
+ ", checkIdToken=" + checkIdToken
+ ", sso=" + sso
+ ", cookie=" + cookie
+ "]";
}
@@ -12,7 +12,7 @@ public class ApiDisabledException extends SaTokenException {
private static final long serialVersionUID = 6806129545290130133L;
/** 异常提示语 */
public static final String BE_MESSAGE = "This API is disabled";
public static final String BE_MESSAGE = "this api is disabled";
/**
* 一个异常:代表 API 已被禁用
@@ -26,7 +26,7 @@ public class NotLoginException extends SaTokenException {
/** 表示未提供token */
public static final String NOT_TOKEN = "-1";
public static final String NOT_TOKEN_MESSAGE = "提供Token";
public static final String NOT_TOKEN_MESSAGE = "能读取到有效Token";
/** 表示token无效 */
public static final String INVALID_TOKEN = "-2";
@@ -16,13 +16,13 @@ public class NotPermissionException extends SaTokenException {
private static final long serialVersionUID = 6806129545290130141L;
/** 权限码 */
private String code;
private String permission;
/**
* @return 获得权限码
* @return 获得具体缺少的权限码
*/
public String getCode() {
return code;
public String getPermission() {
return permission;
}
/**
@@ -39,14 +39,23 @@ public class NotPermissionException extends SaTokenException {
return loginType;
}
public NotPermissionException(String code) {
this(code, StpUtil.stpLogic.loginType);
public NotPermissionException(String permission) {
this(permission, StpUtil.stpLogic.loginType);
}
public NotPermissionException(String code, String loginType) {
super("无此权限:" + code);
this.code = code;
public NotPermissionException(String permission, String loginType) {
super("无此权限:" + permission);
this.permission = permission;
this.loginType = loginType;
}
/**
* <h1> 警告:自 v1.30+ 版本起,获取异常权限码由 getCode() 更改为 getPermission(),请及时更换! </h1>
* @return 获得权限码
*/
@Deprecated
public int getCode() {
return super.getCode();
}
}
@@ -0,0 +1,14 @@
package cn.dev33.satoken.exception;
/**
* 定义所有异常细分状态码
*
* @author kong
* @since: 2022-4-25
*/
public class SaExceptionCode {
/** 代表这个异常在抛出时未指定异常细分状态码 */
public static final int CODE_UNDEFINED = -1;
}
@@ -0,0 +1,23 @@
package cn.dev33.satoken.exception;
/**
* 一个异常:代表 JSON 转换失败
*
* @author kong
*/
public class SaJsonConvertException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290134144L;
/**
* 一个异常:代表 JSON 转换失败
* @param cause 异常对象
*/
public SaJsonConvertException(Throwable cause) {
super(cause);
}
}
@@ -16,6 +16,22 @@ public class SaTokenException extends RuntimeException {
*/
private static final long serialVersionUID = 6806129545290130132L;
/**
* 异常细分状态码
*/
private int code = SaExceptionCode.CODE_UNDEFINED;
/**
* 构建一个异常
*
* @param code 异常细分状态码
*/
public SaTokenException(int code) {
super();
this.code = code;
}
/**
* 构建一个异常
*
@@ -25,6 +41,17 @@ public class SaTokenException extends RuntimeException {
super(message);
}
/**
* 构建一个异常
*
* @param code 异常细分状态码
* @param message 异常信息
*/
public SaTokenException(int code, String message) {
super(message);
this.code = code;
}
/**
* 构建一个异常
*
@@ -44,6 +71,24 @@ public class SaTokenException extends RuntimeException {
super(message, cause);
}
/**
* 获取异常细分状态码
* @return 异常细分状态码
*/
public int getCode() {
return code;
}
/**
* 写入异常细分状态码
* @param code 异常细分状态码
* @return 对象自身
*/
public SaTokenException setCode(int code) {
this.code = code;
return this;
}
/**
* 如果flag==true,则抛出message异常
* @param flag 标记
@@ -0,0 +1,28 @@
package cn.dev33.satoken.json;
import java.util.Map;
/**
* JSON 转换器
*
* @author kong
*
*/
public interface SaJsonTemplate {
/**
* 将任意对象转换为 json 字符串
*
* @param obj 对象
* @return 转换后的 json 字符串
*/
public String toJsonString(Object obj);
/**
* 解析 json 字符串为map对象
* @param jsonStr json字符串
* @return map对象
*/
public Map<String, Object> parseJsonToMap(String jsonStr);
}
@@ -0,0 +1,33 @@
package cn.dev33.satoken.json;
import java.util.Map;
import cn.dev33.satoken.exception.ApiDisabledException;
/**
* JSON 相关操作接口
*
* @author kong
*
*/
public class SaJsonTemplateDefaultImpl implements SaJsonTemplate {
public static final String ERROR_MESSAGE = "未实现具体的 json 转换器";
/**
* 将任意对象转换为 json 字符串
*/
@Override
public String toJsonString(Object obj) {
throw new ApiDisabledException(ERROR_MESSAGE);
}
/**
* 将 json 字符串解析为 Map
*/
@Override
public Map<String, Object> parseJsonToMap(String jsonStr) {
throw new ApiDisabledException(ERROR_MESSAGE);
};
}
@@ -14,9 +14,10 @@ public interface SaTokenListener {
* 每次登录时触发
* @param loginType 账号类别
* @param loginId 账号id
* @param tokenValue 本次登录产生的 token 值
* @param loginModel 登录参数
*/
public void doLogin(String loginType, Object loginId, SaLoginModel loginModel);
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel);
/**
* 每次注销时触发
@@ -17,7 +17,7 @@ public class SaTokenListenerDefaultImpl implements SaTokenListener {
* 每次登录时触发
*/
@Override
public void doLogin(String loginType, Object loginId, SaLoginModel loginModel) {
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
println("账号[" + loginId + "]登录成功");
}
@@ -1,6 +1,7 @@
package cn.dev33.satoken.session;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -110,12 +111,37 @@ public class SaSession implements Serializable {
private final List<TokenSign> tokenSignList = new Vector<>();
/**
* 返回token签名列表的拷贝副本
* 此Session绑定的token签名列表
*
* @return token签名列表
*/
public List<TokenSign> getTokenSignList() {
return new Vector<>(tokenSignList);
return tokenSignList;
}
/**
* 返回token签名列表的拷贝副本
*
* @return token签名列表
*/
public List<TokenSign> tokenSignListCopy() {
return new ArrayList<>(tokenSignList);
}
/**
* 返回token签名列表的拷贝副本,根据 device 筛选
*
* @param device 设备类型,填 null 代表不限设备类型
* @return token签名列表
*/
public List<TokenSign> tokenSignListCopyByDevice(String device) {
List<TokenSign> list = new ArrayList<>();
for (TokenSign tokenSign : tokenSignListCopy()) {
if(device == null || tokenSign.getDevice().equals(device)) {
list.add(tokenSign);
}
}
return list;
}
/**
@@ -125,7 +151,7 @@ public class SaSession implements Serializable {
* @return 查找到的tokenSign
*/
public TokenSign getTokenSign(String tokenValue) {
for (TokenSign tokenSign : getTokenSignList()) {
for (TokenSign tokenSign : tokenSignListCopy()) {
if (tokenSign.getValue().equals(tokenValue)) {
return tokenSign;
}
@@ -140,7 +166,7 @@ public class SaSession implements Serializable {
*/
public void addTokenSign(TokenSign tokenSign) {
// 如果已经存在于列表中,则无需再次添加
for (TokenSign tokenSign2 : getTokenSignList()) {
for (TokenSign tokenSign2 : tokenSignListCopy()) {
if (tokenSign2.getValue().equals(tokenSign.getValue())) {
return;
}
@@ -154,7 +180,7 @@ public class SaSession implements Serializable {
* 添加一个token签名
*
* @param tokenValue token值
* @param device 设备标识
* @param device 设备类型
*/
public void addTokenSign(String tokenValue, String device) {
addTokenSign(new TokenSign(tokenValue, device));
@@ -23,7 +23,7 @@ public class TokenSign implements Serializable {
private String value;
/**
* 所设备标识
* 所设备类型
*/
private String device;
@@ -35,7 +35,7 @@ public class TokenSign implements Serializable {
* 构建一个
*
* @param value token值
* @param device 所设备标识
* @param device 所设备类型
*/
public TokenSign(String value, String device) {
this.value = value;
@@ -50,7 +50,7 @@ public class TokenSign implements Serializable {
}
/**
* @return token登录设备
* @return 所属设备类型
*/
public String getDevice() {
return device;
@@ -0,0 +1,59 @@
package cn.dev33.satoken.sign;
import java.util.Map;
import java.util.TreeMap;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* 参数签名算法
*
* @author kong
* @since: 2022-4-27
*/
public interface SaSignTemplate {
/**
* 将所有参数连接成一个字符串
* @param paramsMap 参数列表
* @return 字符串
*/
public default String joinParams(Map<String, Object> paramsMap) {
// 保证字段按照字典顺序排列
if(paramsMap instanceof TreeMap == false) {
paramsMap = new TreeMap<>(paramsMap);
}
// 按照 k1=v1&k2=v2&k3=v3 排列
StringBuilder sb = new StringBuilder();
for (String key : paramsMap.keySet()) {
Object value = paramsMap.get(key);
if(SaFoxUtil.isEmpty(value) == false) {
sb.append(key).append("=").append(value).append("&");
}
}
// 删除最后一位 &
if(sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
// .
return sb.toString();
}
/**
* 创建签名:md5(paramsStr + keyStr)
* @param paramsMap 参数列表
* @param key 秘钥
* @return 签名
*/
public default String createSign(Map<String, Object> paramsMap, String key) {
String paramsStr = joinParams(paramsMap);
String fullStr = paramsStr + "&key=" + key;
return SaSecureUtil.md5(fullStr);
}
}
@@ -0,0 +1,11 @@
package cn.dev33.satoken.sign;
/**
* 参数签名算法 [默认实现类]
*
* @author kong
* @since: 2022-4-27
*/
public class SaSignTemplateDefaultImpl implements SaSignTemplate {
}
@@ -12,7 +12,7 @@ import java.util.Map;
public class SaLoginConfig {
/**
* @param device 此次登录的客户端设备标识
* @param device 此次登录的客户端设备类型
* @return SaLoginModel配置对象
*/
public static SaLoginModel setDevice(String device) {
@@ -16,7 +16,7 @@ import cn.dev33.satoken.util.SaTokenConsts;
public class SaLoginModel {
/**
* 此次登录的客户端设备标识
* 此次登录的客户端设备类型
*/
public String device;
@@ -42,14 +42,14 @@ public class SaLoginModel {
/**
* @return 此次登录的客户端设备标识
* @return 此次登录的客户端设备类型
*/
public String getDevice() {
return device;
}
/**
* @param device 此次登录的客户端设备标识
* @param device 此次登录的客户端设备类型
* @return 对象自身
*/
public SaLoginModel setDevice(String device) {
@@ -35,7 +35,7 @@ public class SaTokenInfo {
/** token剩余无操作有效时间 (单位: 秒) */
public long tokenActivityTimeout;
/** 登录设备标识 */
/** 登录设备类型 */
public String loginDevice;
/** 自定义数据 */
@@ -170,14 +170,14 @@ public class SaTokenInfo {
}
/**
* @return 登录设备标识
* @return 登录设备类型
*/
public String getLoginDevice() {
return loginDevice;
}
/**
* @param loginDevice 登录设备标识
* @param loginDevice 登录设备类型
*/
public void setLoginDevice(String loginDevice) {
this.loginDevice = loginDevice;
@@ -1,7 +1,10 @@
package cn.dev33.satoken.stp;
import java.util.*;
import java.util.function.Consumer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckLogin;
@@ -84,7 +87,7 @@ public class StpLogic {
/**
* 创建一个TokenValue
* @param loginId loginId
* @param device 设备标识
* @param device 设备类型
* @param timeout 过期时间
* @param extraData 扩展信息
* @return 生成的tokenValue
@@ -251,9 +254,9 @@ public class StpLogic {
}
/**
* 会话登录,并指定登录设备
* 会话登录,并指定登录设备类型
* @param id 账号id,建议的类型:(long | int | String
* @param device 设备标识
* @param device 设备类型
*/
public void login(Object id, String device) {
login(id, new SaLoginModel().setDevice(device));
@@ -315,7 +318,12 @@ public class StpLogic {
if(config.getIsConcurrent()) {
// 如果配置为共享token, 则尝试从Session签名记录里取出token
if(getConfigOfIsShare()) {
tokenValue = getTokenValueByLoginId(id, loginModel.getDeviceOrDefault());
// 为确保 jwt-simple 模式的 token Extra 数据生成不受旧token影响,这里必须确保 is-share 配置项在 ExtraData 为空时才可以生效
if(loginModel.getExtraData() == null || loginModel.getExtraData().size() == 0) {
tokenValue = getTokenValueByLoginId(id, loginModel.getDeviceOrDefault());
}
} else {
//
}
} else {
// --- 如果不允许并发登录,则将这个账号的历史登录标记为:被顶下线
@@ -345,7 +353,12 @@ public class StpLogic {
setLastActivityToNow(tokenValue);
// $$ 通知监听器,账号xxx 登录成功
SaManager.getSaTokenListener().doLogin(loginType, id, loginModel);
SaManager.getSaTokenListener().doLogin(loginType, id, tokenValue, loginModel);
// 检查此账号会话数量是否超出最大值
if(config.getMaxLoginCount() != -1) {
logoutByMaxLoginCount(id, session, null, config.getMaxLoginCount());
}
// 返回Token
return tokenValue;
@@ -383,20 +396,64 @@ public class StpLogic {
public void logout(Object loginId) {
logout(loginId, null);
}
/**
* 会话注销,根据账号id 和 设备标识
* 会话注销,根据账号id 和 设备类型
*
* @param loginId 账号id
* @param device 设备标识 (填null代表所有注销设备)
* @param device 设备类型 (填null代表注销所有设备类型)
*/
public void logout(Object loginId, String device) {
clearTokenCommonMethod(loginId, device, tokenValue -> {
SaSession session = getSessionByLoginId(loginId, false);
if(session != null) {
for (TokenSign tokenSign: session.tokenSignListCopyByDevice(device)) {
// 清理: token签名、token最后活跃时间
String tokenValue = tokenSign.getValue();
session.removeTokenSign(tokenValue);
clearLastActivity(tokenValue);
// 删除Token-Id映射 & 清除Token-Session
deleteTokenToIdMapping(tokenValue);
deleteTokenSession(tokenValue);
SaManager.getSaTokenListener().doLogout(loginType, loginId, tokenValue);
}
// 注销 Session
session.logoutByTokenSignCountToZero();
}
}
/**
* 会话注销,根据账号id 和 设备类型 和 最大同时在线数量
*
* @param loginId 账号id
* @param session 此账号的 Session 对象,可填写null,框架将自动获取
* @param device 设备类型 (填null代表注销所有设备类型)
* @param maxLoginCount 保留最近的几次登录
*/
public void logoutByMaxLoginCount(Object loginId, SaSession session, String device, int maxLoginCount) {
if(session == null) {
session = getSessionByLoginId(loginId, false);
if(session == null) {
return;
}
}
List<TokenSign> list = session.tokenSignListCopyByDevice(device);
// 遍历操作
for (int i = 0; i < list.size(); i++) {
// 只操作前n条
if(i >= list.size() - maxLoginCount) {
continue;
}
// 清理: token签名、token最后活跃时间
String tokenValue = list.get(i).getValue();
session.removeTokenSign(tokenValue);
clearLastActivity(tokenValue);
// 删除Token-Id映射 & 清除Token-Session
deleteTokenToIdMapping(tokenValue);
deleteTokenSession(tokenValue);
SaManager.getSaTokenListener().doLogout(loginType, loginId, tokenValue);
}, true);
}
// 注销 Session
session.logoutByTokenSignCountToZero();
}
/**
@@ -444,18 +501,27 @@ public class StpLogic {
}
/**
* 踢人下线,根据账号id 和 设备标识
* 踢人下线,根据账号id 和 设备类型
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
*
* @param loginId 账号id
* @param device 设备标识 (填null代表踢出所有设备)
* @param device 设备类型 (填null代表踢出所有设备类型)
*/
public void kickout(Object loginId, String device) {
clearTokenCommonMethod(loginId, device, tokenValue -> {
// 将此 token 标记为已被踢下线
updateTokenToIdMapping(tokenValue, NotLoginException.KICK_OUT);
SaManager.getSaTokenListener().doKickout(loginType, loginId, tokenValue);
}, true);
SaSession session = getSessionByLoginId(loginId, false);
if(session != null) {
for (TokenSign tokenSign: session.tokenSignListCopyByDevice(device)) {
// 清理: token签名、token最后活跃时间
String tokenValue = tokenSign.getValue();
session.removeTokenSign(tokenValue);
clearLastActivity(tokenValue);
// 将此 token 标记为已被踢下线
updateTokenToIdMapping(tokenValue, NotLoginException.KICK_OUT);
SaManager.getSaTokenListener().doKickout(loginType, loginId, tokenValue);
}
// 注销 Session
session.logoutByTokenSignCountToZero();
}
}
/**
@@ -491,51 +557,25 @@ public class StpLogic {
}
/**
* 顶人下线,根据账号id 和 设备标识
* 顶人下线,根据账号id 和 设备类型
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-4 </p>
*
* @param loginId 账号id
* @param device 设备标识 (填null代表顶替所有设备)
* @param device 设备类型 (填null代表顶替所有设备类型)
*/
public void replaced(Object loginId, String device) {
clearTokenCommonMethod(loginId, device, tokenValue -> {
// 将此 token 标记为已被顶替
updateTokenToIdMapping(tokenValue, NotLoginException.BE_REPLACED);
SaManager.getSaTokenListener().doReplaced(loginType, loginId, tokenValue);
}, false);
}
/**
* 封装 注销、踢人、顶人 三个动作的相同代码(无API含义方法)
* @param loginId 账号id
* @param device 设备标识
* @param appendFun 追加操作
* @param isLogoutSession 是否注销 User-Session
*/
protected void clearTokenCommonMethod(Object loginId, String device, Consumer<String> appendFun, boolean isLogoutSession) {
// 1. 如果此账号尚未登录,则不执行任何操作
SaSession session = getSessionByLoginId(loginId, false);
if(session == null) {
return;
}
// 2. 循环token签名列表,开始删除相关信息
for (TokenSign tokenSign : session.getTokenSignList()) {
if(device == null || tokenSign.getDevice().equals(device)) {
// -------- 共有操作
// s1. 获取token
if(session != null) {
for (TokenSign tokenSign: session.tokenSignListCopyByDevice(device)) {
// 清理: token签名、token最后活跃时间
String tokenValue = tokenSign.getValue();
// s2. 清理掉[token-last-activity]
session.removeTokenSign(tokenValue);
clearLastActivity(tokenValue);
// s3. 从token签名列表移除
session.removeTokenSign(tokenValue);
// -------- 追加操作
appendFun.accept(tokenValue);
// 将此 token 标记为已被顶替
updateTokenToIdMapping(tokenValue, NotLoginException.BE_REPLACED);
SaManager.getSaTokenListener().doReplaced(loginType, loginId, tokenValue);
}
}
// 3. 尝试注销session
if(isLogoutSession) {
session.logoutByTokenSignCountToZero();
}
}
// ---- 会话查询
@@ -957,7 +997,7 @@ public class StpLogic {
/**
* 续签当前token:(将 [最后操作时间] 更新为当前时间戳)
* <h1>请注意: 即token已经 [临时过期] 也可续签成功,
* <h1>请注意: 即使token已经 [临时过期] 也可续签成功,
* 如果此场景下需要提示续签失败,可在此之前调用 checkActivityTimeout() 强制检查是否过期即可 </h1>
*/
public void updateLastActivityToNow() {
@@ -1347,11 +1387,11 @@ public class StpLogic {
}
/**
* 获取指定账号id指定设备端的tokenValue
* 获取指定账号id指定设备类型端的tokenValue
* <p> 在配置为允许并发登录时,此方法只会返回队列的最后一个token,
* 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
* @param loginId 账号id
* @param device 设备标识,填null代表不限设备
* @param device 设备类型,填null代表不限设备类型
* @return token值
*/
public String getTokenValueByLoginId(Object loginId, String device) {
@@ -1369,9 +1409,9 @@ public class StpLogic {
}
/**
* 获取指定账号id指定设备端的tokenValue 集合
* 获取指定账号id指定设备类型端的tokenValue 集合
* @param loginId 账号id
* @param device 设备标识,填null代表不限设备
* @param device 设备类型,填null代表不限设备类型
* @return 此loginId的所有相关token
*/
public List<String> getTokenValueListByLoginId(Object loginId, String device) {
@@ -1381,7 +1421,7 @@ public class StpLogic {
return Collections.emptyList();
}
// 遍历解析
List<TokenSign> tokenSignList = session.getTokenSignList();
List<TokenSign> tokenSignList = session.tokenSignListCopy();
List<String> tokenValueList = new ArrayList<>();
for (TokenSign tokenSign : tokenSignList) {
if(device == null || tokenSign.getDevice().equals(device)) {
@@ -1392,8 +1432,8 @@ public class StpLogic {
}
/**
* 返回当前会话的登录设备
* @return 当前令牌的登录设备
* 返回当前会话的登录设备类型
* @return 当前令牌的登录设备类型
*/
public String getLoginDevice() {
// 如果没有token,直接返回 null
@@ -1411,7 +1451,7 @@ public class StpLogic {
return null;
}
// 遍历解析
List<TokenSign> tokenSignList = session.getTokenSignList();
List<TokenSign> tokenSignList = session.tokenSignListCopy();
for (TokenSign tokenSign : tokenSignList) {
if(tokenSign.getValue().equals(tokenValue)) {
return tokenSign.getDevice();
@@ -1799,10 +1839,10 @@ public class StpLogic {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout(id) ,使用方式保持不变 </h1>
*
* 会话注销,根据账号id and 设备标识 (踢人下线)
* 会话注销,根据账号id and 设备类型 (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 </p>
* @param loginId 账号id
* @param device 设备标识 (填null代表所有注销设备)
* @param device 设备类型 (填null代表注销所有设备类型)
*/
@Deprecated
public void logoutByLoginId(Object loginId, String device) {
@@ -1812,7 +1852,7 @@ public class StpLogic {
/**
* 创建一个TokenValue
* @param loginId loginId
* @param device 设备标识
* @param device 设备类型
* @param timeout 过期时间
* @return 生成的tokenValue
*/
@@ -106,9 +106,9 @@ public class StpUtil {
}
/**
* 会话登录,并指定登录设备
* 会话登录,并指定登录设备类型
* @param id 账号id,建议的类型:(long | int | String
* @param device 设备标识
* @param device 设备类型
*/
public static void login(Object id, String device) {
stpLogic.login(id, device);
@@ -169,10 +169,10 @@ public class StpUtil {
}
/**
* 会话注销,根据账号id 和 设备标识
* 会话注销,根据账号id 和 设备类型
*
* @param loginId 账号id
* @param device 设备标识 (填null代表所有注销设备)
* @param device 设备类型 (填null代表注销所有设备类型)
*/
public static void logout(Object loginId, String device) {
stpLogic.logout(loginId, device);
@@ -198,11 +198,11 @@ public class StpUtil {
}
/**
* 踢人下线,根据账号id 和 设备标识
* 踢人下线,根据账号id 和 设备类型
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
*
* @param loginId 账号id
* @param device 设备标识 (填null代表踢出所有设备)
* @param device 设备类型 (填null代表踢出所有设备类型)
*/
public static void kickout(Object loginId, String device) {
stpLogic.kickout(loginId, device);
@@ -219,11 +219,11 @@ public class StpUtil {
}
/**
* 顶人下线,根据账号id 和 设备标识
* 顶人下线,根据账号id 和 设备类型
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-4 </p>
*
* @param loginId 账号id
* @param device 设备标识 (填null代表顶替所有设备)
* @param device 设备类型 (填null代表顶替所有设备类型)
*/
public static void replaced(Object loginId, String device) {
stpLogic.replaced(loginId, device);
@@ -395,7 +395,7 @@ public class StpUtil {
/**
* 续签当前token:(将 [最后操作时间] 更新为当前时间戳)
* <h1>请注意: 即token已经 [临时过期] 也可续签成功,
* <h1>请注意: 即使token已经 [临时过期] 也可续签成功,
* 如果此场景下需要提示续签失败,可在此之前调用 checkActivityTimeout() 强制检查是否过期即可 </h1>
*/
public static void updateLastActivityToNow() {
@@ -630,11 +630,11 @@ public class StpUtil {
}
/**
* 获取指定账号id指定设备端的tokenValue
* 获取指定账号id指定设备类型端的tokenValue
* <p> 在配置为允许并发登录时,此方法只会返回队列的最后一个token,
* 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
* @param loginId 账号id
* @param device 设备标识
* @param device 设备类型
* @return token值
*/
public static String getTokenValueByLoginId(Object loginId, String device) {
@@ -651,9 +651,9 @@ public class StpUtil {
}
/**
* 获取指定账号id指定设备端的tokenValue 集合
* 获取指定账号id指定设备类型端的tokenValue 集合
* @param loginId 账号id
* @param device 设备标识
* @param device 设备类型
* @return 此loginId的所有相关token
*/
public static List<String> getTokenValueListByLoginId(Object loginId, String device) {
@@ -661,8 +661,8 @@ public class StpUtil {
}
/**
* 返回当前会话的登录设备
* @return 当前令牌的登录设备
* 返回当前会话的登录设备类型
* @return 当前令牌的登录设备类型
*/
public static String getLoginDevice() {
return stpLogic.getLoginDevice();
@@ -847,9 +847,9 @@ public class StpUtil {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
*
* 在当前会话上登录id, 并指定登录设备
* 在当前会话上登录id, 并指定登录设备类型
* @param loginId 登录id,建议的类型:(long | int | String
* @param device 设备标识
* @param device 设备类型
*/
@Deprecated
public static void setLoginId(Object loginId, String device) {
@@ -859,7 +859,7 @@ public class StpUtil {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
*
* 在当前会话上登录id, 并指定登录设备
* 在当前会话上登录id, 并指定登录设备类型
* @param loginId 登录id,建议的类型:(long | int | String
* @param isLastingCookie 是否为持久Cookie
*/
@@ -895,10 +895,10 @@ public class StpUtil {
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
*
* 会话注销,根据账号id and 设备标识 (踢人下线)
* 会话注销,根据账号id and 设备类型 (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 </p>
* @param loginId 账号id
* @param device 设备标识 (填null代表所有注销设备)
* @param device 设备类型 (填null代表注销所有设备类型)
*/
@Deprecated
public static void logoutByLoginId(Object loginId, String device) {
@@ -4,12 +4,21 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckBasic;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.basic.SaBasicUtil;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaTokenConsts;
/**
* Sa-Token 策略对象
@@ -27,7 +36,6 @@ import cn.dev33.satoken.session.SaSession;
* @author kong
*
*/
@SuppressWarnings("deprecation")
public final class SaStrategy {
private SaStrategy() {
@@ -47,7 +55,34 @@ public final class SaStrategy {
* <p> 参数 [账号id, 账号类型]
*/
public BiFunction<Object, String, String> createToken = (loginId, loginType) -> {
return SaManager.getSaTokenAction().createToken(loginId, loginType);
// 根据配置的tokenStyle生成不同风格的token
String tokenStyle = SaManager.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 SaFoxUtil.getRandomString(32);
}
// 64位随机字符串
if(SaTokenConsts.TOKEN_STYLE_RANDOM_64.equals(tokenStyle)) {
return SaFoxUtil.getRandomString(64);
}
// 128位随机字符串
if(SaTokenConsts.TOKEN_STYLE_RANDOM_128.equals(tokenStyle)) {
return SaFoxUtil.getRandomString(128);
}
// tik风格 (2_14_16)
if(SaTokenConsts.TOKEN_STYLE_TIK.equals(tokenStyle)) {
return SaFoxUtil.getRandomString(2) + "_" + SaFoxUtil.getRandomString(14) + "_" + SaFoxUtil.getRandomString(16) + "__";
}
// 默认,还是uuid
return UUID.randomUUID().toString();
};
/**
@@ -55,7 +90,7 @@ public final class SaStrategy {
* <p> 参数 [SessionId]
*/
public Function<String, SaSession> createSession = (sessionId) -> {
return SaManager.getSaTokenAction().createSession(sessionId);
return new SaSession(sessionId);
};
/**
@@ -63,7 +98,26 @@ public final class SaStrategy {
* <p> 参数 [集合, 元素]
*/
public BiFunction<List<String>, String, Boolean> hasElement = (list, element) -> {
return SaManager.getSaTokenAction().hasElement(list, element);
// 空集合直接返回false
if(list == null || list.size() == 0) {
return false;
}
// 先尝试一下简单匹配,如果可以匹配成功则无需继续模糊匹配
if (list.contains(element)) {
return true;
}
// 开始模糊匹配
for (String patt : list) {
if(SaFoxUtil.vagueMatch(patt, element)) {
return true;
}
}
// 走出for循环说明没有一个元素可以匹配成功
return false;
};
/**
@@ -83,9 +137,37 @@ public final class SaStrategy {
* 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
* <p> 参数 [element元素]
*/
public Consumer<AnnotatedElement> checkElementAnnotation = (element) -> {
// 为了兼容旧版本
SaManager.getSaTokenAction().validateAnnotation(element);
public Consumer<AnnotatedElement> checkElementAnnotation = (target) -> {
// 校验 @SaCheckLogin 注解
SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.me.getAnnotation.apply(target, SaCheckLogin.class);
if(checkLogin != null) {
SaManager.getStpLogic(checkLogin.type()).checkByAnnotation(checkLogin);
}
// 校验 @SaCheckRole 注解
SaCheckRole checkRole = (SaCheckRole) SaStrategy.me.getAnnotation.apply(target, SaCheckRole.class);
if(checkRole != null) {
SaManager.getStpLogic(checkRole.type()).checkByAnnotation(checkRole);
}
// 校验 @SaCheckPermission 注解
SaCheckPermission checkPermission = (SaCheckPermission) SaStrategy.me.getAnnotation.apply(target, SaCheckPermission.class);
if(checkPermission != null) {
SaManager.getStpLogic(checkPermission.type()).checkByAnnotation(checkPermission);
}
// 校验 @SaCheckSafe 注解
SaCheckSafe checkSafe = (SaCheckSafe) SaStrategy.me.getAnnotation.apply(target, SaCheckSafe.class);
if(checkSafe != null) {
SaManager.getStpLogic(checkSafe.type()).checkByAnnotation(checkSafe);
}
// 校验 @SaCheckBasic 注解
SaCheckBasic checkBasic = (SaCheckBasic) SaStrategy.me.getAnnotation.apply(target, SaCheckBasic.class);
if(checkBasic != null) {
SaBasicUtil.check(checkBasic.realm(), checkBasic.account());
}
};
/**
@@ -26,12 +26,16 @@ public class SaFoxUtil {
* 打印 Sa-Token 版本字符画
*/
public static void printSaToken() {
String str = "____ ____ ___ ____ _ _ ____ _ _ \r\n" + "[__ |__| __ | | | |_/ |___ |\\ | \r\n"
String str = ""
+ "____ ____ ___ ____ _ _ ____ _ _ \r\n"
+ "[__ |__| __ | | | |_/ |___ |\\ | \r\n"
+ "___] | | | |__| | \\_ |___ | \\| "
// + SaTokenConsts.VERSION_NO
// + "sa-token"
+ "\r\n" + "DevDoc" + SaTokenConsts.DEV_DOC_URL // + "\r\n";
// + "\r\n" + "DevDoc" + SaTokenConsts.DEV_DOC_URL // + "\r\n";
+ "\r\n" + SaTokenConsts.DEV_DOC_URL // + "\r\n";
+ " (" + SaTokenConsts.VERSION_NO + ")"
+ "\r\n" + "GitHub" + SaTokenConsts.GITHUB_URL // + "\r\n";
// + "\r\n" + "GitHub" + SaTokenConsts.GITHUB_URL // + "\r\n";
;
System.out.println(str);
}
@@ -21,11 +21,33 @@ public class SaResult extends LinkedHashMap<String, Object> implements Serializa
public static final int CODE_SUCCESS = 200;
public static final int CODE_ERROR = 500;
/**
* 构建
*/
public SaResult() {
}
/**
* 构建
* @param code 状态码
* @param msg 信息
* @param data 数据
*/
public SaResult(int code, String msg, Object data) {
this.setCode(code);
this.setMsg(msg);
this.setData(data);
}
/**
* 根据 Map 快速构建
* @param map /
*/
public SaResult(Map<String, Object> map) {
for (String key: map.keySet()) {
this.set(key, map.get(key));
}
}
/**
* 获取code
@@ -138,9 +160,16 @@ public class SaResult extends LinkedHashMap<String, Object> implements Serializa
public String toString() {
return "{"
+ "\"code\": " + this.getCode()
+ ", \"msg\": \"" + this.getMsg() + "\""
+ ", \"data\": \"" + this.getData() + "\""
+ ", \"msg\": " + transValue(this.getMsg())
+ ", \"data\": " + transValue(this.getData())
+ "}";
}
private String transValue(Object value) {
if(value instanceof String) {
return "\"" + value + "\"";
}
return String.valueOf(value);
}
}
@@ -13,10 +13,15 @@ public class SaTokenConsts {
/**
* Sa-Token 当前版本号
*/
public static final String VERSION_NO = "v1.29.0";
public static final String VERSION_NO = "v1.30.0";
/**
* Sa-Token 开源地址
* Sa-Token 开源地址 Gitee
*/
public static final String GITEE_URL = "https://gitee.com/dromara/sa-token";
/**
* Sa-Token 开源地址 GitHub
*/
public static final String GITHUB_URL = "https://github.com/dromara/sa-token";
@@ -43,7 +48,7 @@ public class SaTokenConsts {
public static final String TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY = "TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY_";
/**
* 常量key标记: 在登录时,默认使用的设备名称
* 常量key标记: 在登录时,默认使用的设备类型
*/
public static final String DEFAULT_LOGIN_DEVICE = "default-device";
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<version>2.5.12</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
+2 -2
View File
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<version>2.5.12</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
@@ -10,7 +10,7 @@ public class SaTokenJwtDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenJwtDemoApplication.class, args);
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}
@@ -6,7 +6,7 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import cn.dev33.satoken.jwt.StpLogicJwtForStyle;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.stp.StpLogic;
@@ -32,7 +32,7 @@ public class SaTokenConfigure implements WebMvcConfigurer {
*/
@Bean
public StpLogic getStpLogicJwt() {
return new StpLogicJwtForStyle();
return new StpLogicJwtForSimple();
}
}
@@ -36,7 +36,7 @@ public class GlobalException {
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
} else if(e instanceof NotPermissionException) { // 如果是权限异常
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此权限:" + ee.getCode());
aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
} else { // 普通异常, 输出:500 + 异常信息
aj = AjaxJson.getError(e.getMessage());
}
@@ -17,7 +17,7 @@
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<!-- 定义sa-token版本号 -->
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
@@ -17,7 +17,7 @@
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<!-- 定义sa-token版本号 -->
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
@@ -37,5 +37,4 @@ spring:
min-idle: 0
@@ -10,12 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<version>2.5.12</version>
<!-- <version>2.6.0</version> -->
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
+2 -2
View File
@@ -9,7 +9,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
@@ -18,7 +18,7 @@
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-web</artifactId>
<version>1.6.1</version>
<version>1.7.5</version>
</dependency>
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
@@ -31,7 +31,7 @@ public class GlobalException implements EventListener<Throwable> {
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
} else if (e instanceof NotPermissionException) { // 如果是权限异常
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此权限:" + ee.getCode());
aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
} else if (e instanceof DisableLoginException) { // 如果是被封禁异常
DisableLoginException ee = (DisableLoginException) e;
aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封");
@@ -192,7 +192,7 @@ public class TestController {
return AjaxJson.getSuccess();
}
// 测试登录接口, 按照设备登录, 浏览器访问: http://localhost:8081/test/login2
// 测试登录接口, 按照设备类型登录, 浏览器访问: http://localhost:8081/test/login2
@Mapping("login2")
public AjaxJson login2(@Param(defaultValue="10001") String id, @Param(defaultValue="PC") String device) {
StpUtil.login(id, device);
@@ -226,7 +226,7 @@ public class TestController {
return AjaxJson.getSuccess();
}
// 测试指定设备登录 浏览器访问: http://localhost:8081/test/loginByDevice
// 测试指定设备类型登录 浏览器访问: http://localhost:8081/test/loginByDevice
@Mapping("loginByDevice")
public AjaxJson loginByDevice() {
System.out.println("--------------");
@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath
@@ -0,0 +1,72 @@
<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-springboot-redis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.30.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>
<!-- 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>
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,21 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaManager;
/**
* Sa-Token整合SpringBoot 示例,整合redis
* @author kong
*
*/
@SpringBootApplication
public class SaTokenDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenDemoApplication.class, args);
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}
@@ -0,0 +1,57 @@
package com.pj.current;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.exception.DisableLoginException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
/**
* 全局异常处理
*/
@RestControllerAdvice
public class GlobalException {
// 全局异常拦截(拦截项目中的所有异常)
@ExceptionHandler
public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
throws Exception {
// 打印堆栈,以供调试
System.out.println("全局异常---------------");
e.printStackTrace();
// 不同异常返回不同状态码
AjaxJson aj = null;
if (e instanceof NotLoginException) { // 如果是未登录异常
NotLoginException ee = (NotLoginException) e;
aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
}
else if(e instanceof NotRoleException) { // 如果是角色异常
NotRoleException ee = (NotRoleException) e;
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
}
else if(e instanceof NotPermissionException) { // 如果是权限异常
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
}
else if(e instanceof DisableLoginException) { // 如果是被封禁异常
DisableLoginException ee = (DisableLoginException) e;
aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封");
}
else { // 普通异常, 输出:500 + 异常信息
aj = AjaxJson.getError(e.getMessage());
}
// 返回给前端
return aj;
}
}
@@ -0,0 +1,27 @@
package com.pj.current;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.util.SaResult;
/**
* 处理 404
* @author kong
*/
@RestController
public class NotFoundHandle implements ErrorController {
@RequestMapping("/error")
public Object error(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setStatus(200);
return SaResult.get(404, "not found", null);
}
}
@@ -0,0 +1,85 @@
package com.pj.satoken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import cn.dev33.satoken.strategy.SaStrategy;
/**
* [Sa-Token 权限认证] 配置类
* @author kong
*
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/**
* 注册Sa-Token 的拦截器,打开注解式鉴权功能
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册注解拦截器
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
}
/**
* 注册 [Sa-Token 全局过滤器]
*/
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
// 指定 [拦截路由] 与 [放行路由]
.addInclude("/**")// .addExclude("/favicon.ico")
// 认证函数: 每次请求执行
.setAuth(obj -> {
// System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath());
})
// 异常处理函数:每次认证函数发生异常时执行此函数
.setError(e -> {
System.out.println("---------- sa全局异常 ");
return AjaxJson.getError(e.getMessage());
})
// 前置函数:在每次认证函数之前执行
.setBeforeAuth(r -> {
// ---------- 设置一些安全响应头 ----------
SaHolder.getResponse()
// 服务器名称
.setServer("sa-server")
// 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
.setHeader("X-Frame-Options", "SAMEORIGIN")
// 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
.setHeader("X-XSS-Protection", "1; mode=block")
// 禁用浏览器内容嗅探
.setHeader("X-Content-Type-Options", "nosniff")
;
})
;
}
/**
* 重写 Sa-Token 框架内部算法策略
*/
@Autowired
public void rewriteSaStrategy() {
// 重写Sa-Token的注解处理器,增加注解合并功能
SaStrategy.me.getAnnotation = (element, annotationClass) -> {
return AnnotatedElementUtils.getMergedAnnotation(element, annotationClass);
};
}
}
@@ -0,0 +1,44 @@
package com.pj.satoken;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.stp.StpInterface;
/**
* 自定义权限验证接口扩展
*/
@Component // 打开此注解保证此类被springboot扫描即可完成sa-token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 本list仅做模拟实际项目中要根据具体业务逻辑来查询权限
List<String> list = new ArrayList<String>();
list.add("101");
list.add("user-add");
list.add("user-delete");
list.add("user-update");
list.add("user-get");
list.add("article-get");
return list;
}
/**
* 返回一个账号所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 本list仅做模拟实际项目中要根据具体业务逻辑来查询角色
List<String> list = new ArrayList<String>();
list.add("admin");
list.add("super-admin");
return list;
}
}
@@ -0,0 +1,21 @@
package com.pj.satoken.at;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import cn.dev33.satoken.annotation.SaCheckLogin;
/**
* 登录认证(User版)只有登录之后才能进入该方法
* <p> 可标注在函数类上效果等同于标注在此类的所有方法上
* @author kong
*
*/
@SaCheckLogin(type = StpUserUtil.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface SaUserCheckLogin {
}
@@ -0,0 +1,38 @@
package com.pj.satoken.at;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
/**
* 权限认证(User版)必须具有指定权限才能进入该方法
* <p> 可标注在函数类上效果等同于标注在此类的所有方法上
* @author kong
*
*/
@SaCheckPermission(type = StpUserUtil.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface SaUserCheckPermission {
/**
* 需要校验的权限码
* @return 需要校验的权限码
*/
@AliasFor(annotation = SaCheckPermission.class)
String [] value() default {};
/**
* 验证模式AND | OR默认AND
* @return 验证模式
*/
@AliasFor(annotation = SaCheckPermission.class)
SaMode mode() default SaMode.AND;
}
@@ -0,0 +1,38 @@
package com.pj.satoken.at;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
/**
* 角色认证(User版)必须具有指定角色标识才能进入该方法
* <p> 可标注在函数类上效果等同于标注在此类的所有方法上
* @author kong
*
*/
@SaCheckRole(type = StpUserUtil.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface SaUserCheckRole {
/**
* 需要校验的角色标识
* @return 需要校验的角色标识
*/
@AliasFor(annotation = SaCheckRole.class)
String [] value() default {};
/**
* 验证模式AND | OR默认AND
* @return 验证模式
*/
@AliasFor(annotation = SaCheckRole.class)
SaMode mode() default SaMode.AND;
}
@@ -0,0 +1,912 @@
package com.pj.satoken.at;
import java.util.List;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
/**
* Sa-Token 权限认证工具类 (user版)
* @author kong
*/
public class StpUserUtil {
/**
* 账号类型标识
*/
public static final String TYPE = "user";
/**
* 底层的 StpLogic 对象
*/
public static StpLogic stpLogic = new StpLogic(TYPE);
/**
* 获取当前 StpLogic 的账号类型
* @return See Note
*/
public static String getLoginType(){
return stpLogic.getLoginType();
}
/**
* 重置 StpLogic 对象
* @param stpLogic /
*/
public static void setStpLogic(StpLogic stpLogic) {
StpUtil.stpLogic = stpLogic;
// 防止自定义 stpLogic 被覆盖
SaManager.putStpLogic(stpLogic);
}
// =================== 获取token 相关 ===================
/**
* 返回token名称
* @return 此StpLogic的token名称
*/
public static String getTokenName() {
return stpLogic.getTokenName();
}
/**
* 在当前会话写入当前TokenValue
* @param tokenValue token值
*/
public static void setTokenValue(String tokenValue){
stpLogic.setTokenValue(tokenValue);
}
/**
* 在当前会话写入当前TokenValue
* @param tokenValue token值
* @param cookieTimeout Cookie存活时间()
*/
public static void setTokenValue(String tokenValue, int cookieTimeout){
stpLogic.setTokenValue(tokenValue, cookieTimeout);
}
/**
* 获取当前TokenValue
* @return 当前tokenValue
*/
public static String getTokenValue() {
return stpLogic.getTokenValue();
}
/**
* 获取当前TokenValue (不裁剪前缀)
* @return /
*/
public static String getTokenValueNotCut(){
return stpLogic.getTokenValueNotCut();
}
/**
* 获取当前会话的Token信息
* @return token信息
*/
public static SaTokenInfo getTokenInfo() {
return stpLogic.getTokenInfo();
}
// =================== 登录相关操作 ===================
// --- 登录
/**
* 会话登录
* @param id 账号id建议的类型long | int | String
*/
public static void login(Object id) {
stpLogic.login(id);
}
/**
* 会话登录并指定登录设备类型
* @param id 账号id建议的类型long | int | String
* @param device 设备类型
*/
public static void login(Object id, String device) {
stpLogic.login(id, device);
}
/**
* 会话登录并指定是否 [记住我]
* @param id 账号id建议的类型long | int | String
* @param isLastingCookie 是否为持久Cookie
*/
public static void login(Object id, boolean isLastingCookie) {
stpLogic.login(id, isLastingCookie);
}
/**
* 会话登录并指定所有登录参数Model
* @param id 登录id建议的类型long | int | String
* @param loginModel 此次登录的参数Model
*/
public static void login(Object id, SaLoginModel loginModel) {
stpLogic.login(id, loginModel);
}
/**
* 创建指定账号id的登录会话
* @param id 登录id建议的类型long | int | String
* @return 返回会话令牌
*/
public static String createLoginSession(Object id) {
return stpLogic.createLoginSession(id);
}
/**
* 创建指定账号id的登录会话
* @param id 登录id建议的类型long | int | String
* @param loginModel 此次登录的参数Model
* @return 返回会话令牌
*/
public static String createLoginSession(Object id, SaLoginModel loginModel) {
return stpLogic.createLoginSession(id, loginModel);
}
// --- 注销
/**
* 会话注销
*/
public static void logout() {
stpLogic.logout();
}
/**
* 会话注销根据账号id
* @param loginId 账号id
*/
public static void logout(Object loginId) {
stpLogic.logout(loginId);
}
/**
* 会话注销根据账号id 设备类型
*
* @param loginId 账号id
* @param device 设备类型 (填null代表注销所有设备类型)
*/
public static void logout(Object loginId, String device) {
stpLogic.logout(loginId, device);
}
/**
* 会话注销根据指定 Token
*
* @param tokenValue 指定token
*/
public static void logoutByTokenValue(String tokenValue) {
stpLogic.logoutByTokenValue(tokenValue);
}
/**
* 踢人下线根据账号id
* <p> 当对方再次访问系统时会抛出NotLoginException异常场景值=-5 </p>
*
* @param loginId 账号id
*/
public static void kickout(Object loginId) {
stpLogic.kickout(loginId);
}
/**
* 踢人下线根据账号id 设备类型
* <p> 当对方再次访问系统时会抛出NotLoginException异常场景值=-5 </p>
*
* @param loginId 账号id
* @param device 设备类型 (填null代表踢出所有设备类型)
*/
public static void kickout(Object loginId, String device) {
stpLogic.kickout(loginId, device);
}
/**
* 踢人下线根据指定 Token
* <p> 当对方再次访问系统时会抛出NotLoginException异常场景值=-5 </p>
*
* @param tokenValue 指定token
*/
public static void kickoutByTokenValue(String tokenValue) {
stpLogic.kickoutByTokenValue(tokenValue);
}
/**
* 顶人下线根据账号id 设备类型
* <p> 当对方再次访问系统时会抛出NotLoginException异常场景值=-4 </p>
*
* @param loginId 账号id
* @param device 设备类型 (填null代表顶替所有设备类型)
*/
public static void replaced(Object loginId, String device) {
stpLogic.replaced(loginId, device);
}
// 查询相关
/**
* 当前会话是否已经登录
* @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);
}
/**
* 获取Token扩展信息只在jwt模式下有效
* @param key 键值
* @return 对应的扩展数据
*/
public static Object getExtra(String key) {
return stpLogic.getExtra(key);
}
// =================== User-Session 相关 ===================
/**
* 获取指定账号id的Session, 如果Session尚未创建isCreate=是否新建并返回
* @param loginId 账号id
* @param isCreate 是否新建
* @return Session对象
*/
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);
}
/**
* 获取指定账号id的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();
}
/**
* 获取当前登录者的 User-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();
}
/**
* 对当前 Token timeout 值进行续期
* @param timeout 要修改成为的有效时间 (单位: )
*/
public static void renewTimeout(long timeout) {
stpLogic.renewTimeout(timeout);
}
/**
* 对指定 Token timeout 值进行续期
* @param tokenValue 指定token
* @param timeout 要修改成为的有效时间 (单位: )
*/
public static void renewTimeout(String tokenValue, long timeout) {
stpLogic.renewTimeout(tokenValue, timeout);
}
// =================== 角色验证操作 ===================
/**
* 获取当前账号的角色集合
* @return /
*/
public static List<String> getRoleList() {
return stpLogic.getRoleList();
}
/**
* 获取指定账号的角色集合
* @param loginId 指定账号id
* @return /
*/
public static List<String> getRoleList(Object loginId) {
return stpLogic.getRoleList(loginId);
}
/**
* 判断当前账号是否拥有指定角色, 返回true或false
* @param role 角色标识
* @return 是否含有指定角色标识
*/
public static boolean hasRole(String role) {
return stpLogic.hasRole(role);
}
/**
* 判断指定账号是否含有指定角色标识, 返回true或false
* @param loginId 账号id
* @param role 角色标识
* @return 是否含有指定角色标识
*/
public static boolean hasRole(Object loginId, String role) {
return stpLogic.hasRole(loginId, role);
}
/**
* 判断当前账号是否含有指定角色标识 [指定多个必须全部验证通过]
* @param roleArray 角色标识数组
* @return true或false
*/
public static boolean hasRoleAnd(String... roleArray){
return stpLogic.hasRoleAnd(roleArray);
}
/**
* 判断当前账号是否含有指定角色标识 [指定多个只要其一验证通过即可]
* @param roleArray 角色标识数组
* @return true或false
*/
public static boolean hasRoleOr(String... roleArray){
return stpLogic.hasRoleOr(roleArray);
}
/**
* 校验当前账号是否含有指定角色标识, 如果验证未通过则抛出异常: 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);
}
// =================== 权限验证操作 ===================
/**
* 获取当前账号的权限码集合
* @return /
*/
public static List<String> getPermissionList() {
return stpLogic.getPermissionList();
}
/**
* 获取指定账号的权限码集合
* @param loginId 指定账号id
* @return /
*/
public static List<String> getPermissionList(Object loginId) {
return stpLogic.getPermissionList(loginId);
}
/**
* 判断当前账号是否含有指定权限, 返回true或false
* @param permission 权限码
* @return 是否含有指定权限
*/
public static boolean hasPermission(String permission) {
return stpLogic.hasPermission(permission);
}
/**
* 判断指定账号id是否含有指定权限, 返回true或false
* @param loginId 账号id
* @param permission 权限码
* @return 是否含有指定权限
*/
public static boolean hasPermission(Object loginId, String permission) {
return stpLogic.hasPermission(loginId, permission);
}
/**
* 判断当前账号是否含有指定权限, [指定多个必须全部具有]
* @param permissionArray 权限码数组
* @return true false
*/
public static boolean hasPermissionAnd(String... permissionArray){
return stpLogic.hasPermissionAnd(permissionArray);
}
/**
* 判断当前账号是否含有指定权限 [指定多个只要其一验证通过即可]
* @param permissionArray 权限码数组
* @return true false
*/
public static boolean hasPermissionOr(String... permissionArray){
return stpLogic.hasPermissionOr(permissionArray);
}
/**
* 校验当前账号是否含有指定权限, 如果验证未通过则抛出异常: 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 相关操作 ===================
/**
* 获取指定账号id的tokenValue
* <p> 在配置为允许并发登录时此方法只会返回队列的最后一个token
* 如果你需要返回此账号id的所有token请调用 getTokenValueListByLoginId
* @param loginId 账号id
* @return token值
*/
public static String getTokenValueByLoginId(Object loginId) {
return stpLogic.getTokenValueByLoginId(loginId);
}
/**
* 获取指定账号id指定设备类型端的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);
}
/**
* 获取指定账号id的tokenValue集合
* @param loginId 账号id
* @return 此loginId的所有相关token
*/
public static List<String> getTokenValueListByLoginId(Object loginId) {
return stpLogic.getTokenValueListByLoginId(loginId);
}
/**
* 获取指定账号id指定设备类型端的tokenValue 集合
* @param loginId 账号id
* @param device 设备类型
* @return 此loginId的所有相关token
*/
public static List<String> getTokenValueListByLoginId(Object loginId, String device) {
return stpLogic.getTokenValueListByLoginId(loginId, device);
}
/**
* 返回当前会话的登录设备类型
* @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);
}
// ------------------- 账号封禁 -------------------
/**
* 封禁指定账号
* <p> 此方法不会直接将此账号id踢下线而是在对方再次登录时抛出`DisableLoginException`异常
* @param loginId 指定账号id
* @param disableTime 封禁时间, 单位: -1=永久封禁
*/
public static void disable(Object loginId, long disableTime) {
stpLogic.disable(loginId, disableTime);
}
/**
* 指定账号是否已被封禁 (true=已被封禁, false=未被封禁)
* @param loginId 账号id
* @return see note
*/
public static boolean isDisable(Object loginId) {
return stpLogic.isDisable(loginId);
}
/**
* 获取指定账号剩余封禁时间单位-1=永久封禁-2=未被封禁
* @param loginId 账号id
* @return see note
*/
public static long getDisableTime(Object loginId) {
return stpLogic.getDisableTime(loginId);
}
/**
* 解封指定账号
* @param loginId 账号id
*/
public static void untieDisable(Object loginId) {
stpLogic.untieDisable(loginId);
}
// =================== 身份切换 ===================
/**
* 临时切换身份为指定账号id
* @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();
}
/**
* 在一个代码段里方法内临时切换身份为指定账号id
* @param loginId 指定账号id
* @param function 要执行的方法
*/
public static void switchTo(Object loginId, SaFunction function) {
stpLogic.switchTo(loginId, function);
}
// ------------------- 二级认证 -------------------
/**
* 在当前会话 开启二级认证
* @param safeTime 维持时间 (单位: )
*/
public static void openSafe(long safeTime) {
stpLogic.openSafe(safeTime);
}
/**
* 当前会话 是否处于二级认证时间内
* @return true=二级认证已通过, false=尚未进行二级认证或认证已超时
*/
public static boolean isSafe() {
return stpLogic.isSafe();
}
/**
* 检查当前会话是否已通过二级认证如未通过则抛出异常
*/
public static void checkSafe() {
stpLogic.checkSafe();
}
/**
* 获取当前会话的二级认证剩余有效时间 (单位: , 返回-2代表尚未通过二级认证)
* @return 剩余有效时间
*/
public static long getSafeTime() {
return stpLogic.getSafeTime();
}
/**
* 在当前会话 结束二级认证
*/
public static void closeSafe() {
stpLogic.closeSafe();
}
// =================== 历史API兼容旧版本 ===================
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 StpUtil.getLoginType() 使用方式保持不变 </h1>
*
* 获取当前StpLogin的loginKey
* @return 当前StpLogin的loginKey
*/
@Deprecated
public static String getLoginKey(){
return stpLogic.getLoginType();
}
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 StpUtil.login() 使用方式保持不变 </h1>
*
* 在当前会话上登录id
* @param loginId 登录id建议的类型long | int | String
*/
@Deprecated
public static void setLoginId(Object loginId) {
stpLogic.login(loginId);
}
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 StpUtil.login() 使用方式保持不变 </h1>
*
* 在当前会话上登录id, 并指定登录设备类型
* @param loginId 登录id建议的类型long | int | String
* @param device 设备类型
*/
@Deprecated
public static void setLoginId(Object loginId, String device) {
stpLogic.login(loginId, device);
}
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 StpUtil.login() 使用方式保持不变 </h1>
*
* 在当前会话上登录id, 并指定登录设备类型
* @param loginId 登录id建议的类型long | int | String
* @param isLastingCookie 是否为持久Cookie
*/
@Deprecated
public static void setLoginId(Object loginId, boolean isLastingCookie) {
stpLogic.login(loginId, isLastingCookie);
}
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 StpUtil.login() 使用方式保持不变 </h1>
*
* 在当前会话上登录id, 并指定所有登录参数Model
* @param loginId 登录id建议的类型long | int | String
* @param loginModel 此次登录的参数Model
*/
@Deprecated
public static void setLoginId(Object loginId, SaLoginModel loginModel) {
stpLogic.login(loginId, loginModel);
}
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 StpUtil.kickout() 使用方式保持不变 </h1>
*
* 会话注销根据账号id 踢人下线
* <p> 当对方再次访问系统时会抛出NotLoginException异常场景值=-2
* @param loginId 账号id
*/
@Deprecated
public static void logoutByLoginId(Object loginId) {
stpLogic.kickout(loginId);
}
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 StpUtil.kickout() 使用方式保持不变 </h1>
*
* 会话注销根据账号id and 设备类型 踢人下线
* <p> 当对方再次访问系统时会抛出NotLoginException异常场景值=-2 </p>
* @param loginId 账号id
* @param device 设备类型 (填null代表注销所有设备类型)
*/
@Deprecated
public static void logoutByLoginId(Object loginId, String device) {
stpLogic.kickout(loginId, device);
}
}
@@ -0,0 +1,80 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.annotation.SaCheckBasic;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 注解鉴权测试
* @author kong
*
*/
@RestController
@RequestMapping("/at/")
public class AtController {
// 登录认证登录之后才可以进入方法 ---- http://localhost:8081/at/checkLogin
@SaCheckLogin
@RequestMapping("checkLogin")
public SaResult checkLogin() {
return SaResult.ok();
}
// 权限认证具备user-add权限才可以进入方法 ---- http://localhost:8081/at/checkPermission
@SaCheckPermission("user-add")
@RequestMapping("checkPermission")
public SaResult checkPermission() {
return SaResult.ok();
}
// 权限认证同时具备所有权限才可以进入 ---- http://localhost:8081/at/checkPermissionAnd
@SaCheckPermission({"user-add", "user-delete", "user-update"})
@RequestMapping("checkPermissionAnd")
public SaResult checkPermissionAnd() {
return SaResult.ok();
}
// 权限认证只要具备其中一个就可以进入 ---- http://localhost:8081/at/checkPermissionOr
@SaCheckPermission(value = {"user-add", "user-delete", "user-update"}, mode = SaMode.OR)
@RequestMapping("checkPermissionOr")
public SaResult checkPermissionOr() {
return SaResult.ok();
}
// 角色认证只有具备admin角色才可以进入 ---- http://localhost:8081/at/checkRole
@SaCheckRole("admin")
@RequestMapping("checkRole")
public SaResult checkRole() {
return SaResult.ok();
}
// 完成二级认证 ---- http://localhost:8081/at/openSafe
@RequestMapping("openSafe")
public SaResult openSafe() {
StpUtil.openSafe(200); // 打开二级认证有效期为200秒
return SaResult.ok();
}
// 通过二级认证后才可以进入 ---- http://localhost:8081/at/checkSafe
@SaCheckSafe
@RequestMapping("checkSafe")
public SaResult checkSafe() {
return SaResult.ok();
}
// 通过Basic认证后才可以进入 ---- http://localhost:8081/at/checkBasic
@SaCheckBasic(account = "sa:123456")
@RequestMapping("checkBasic")
public SaResult checkBasic() {
return SaResult.ok();
}
}
@@ -0,0 +1,48 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 登录测试
* @author kong
*
*/
@RestController
@RequestMapping("/acc/")
public class LoginController {
// 测试登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
// 此处仅作模拟示例真实项目需要从数据库中查询数据进行比对
if("zhang".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功");
}
return SaResult.error("登录失败");
}
// 查询登录状态 ---- http://localhost:8081/acc/isLogin
@RequestMapping("isLogin")
public SaResult isLogin() {
return SaResult.ok("是否登录:" + StpUtil.isLogin());
}
// 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
@RequestMapping("tokenInfo")
public SaResult tokenInfo() {
return SaResult.data(StpUtil.getTokenInfo());
}
// 测试注销 ---- http://localhost:8081/acc/logout
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}
@@ -0,0 +1,61 @@
package com.pj.test;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.pj.util.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.login("1000" + j, "PC-" + j);
if(j % 1000 == 0) {
System.out.println("已登录:" + j);
}
}
t.end();
list.add((t.returnMs() + 0.0) / 1000);
System.out.println("" + i + "" + "用时:" + t.toString());
}
// System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size());
System.out.println("\n---------------------测试结果---------------------");
System.out.println(list.size() + "次测试: " + list);
double ss = 0;
for (int i = 0; i < list.size(); i++) {
ss += list.get(i);
}
System.out.println("平均用时: " + ss / list.size());
return AjaxJson.getSuccess();
}
}
@@ -0,0 +1,251 @@
package com.pj.test;
import java.util.Date;
import java.util.List;
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;
/**
* 测试专用Controller
* @author kong
*
*/
@RestController
@RequestMapping("/test/")
public class TestController {
// 测试登录接口 浏览器访问 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.login(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();
}
// 测试退出登录 浏览器访问 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("是否具有权限user-add" + StpUtil.hasPermission("user-add"));
System.out.println("是否具有权限article-get" + StpUtil.hasPermission("article-get"));
System.out.println("没有user-add权限就抛出异常");
StpUtil.checkPermission("user-add");
System.out.println("在【101、102】中只要拥有一个就不会抛出异常");
StpUtil.checkPermissionOr("101", "102");
System.out.println("在【101、102】中必须全部拥有才不会抛出异常");
StpUtil.checkPermissionAnd("101", "102");
System.out.println("权限测试通过");
return AjaxJson.getSuccess();
}
// 测试会话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().get("name"));
StpUtil.getSession().set("name", new Date()); // 写入一个值
System.out.println("测试取值name" + StpUtil.getSession().get("name"));
System.out.println( new ObjectMapper().writeValueAsString(StpUtil.getSession()));
return AjaxJson.getSuccess();
}
// 测试自定义session接口 浏览器访问 http://localhost:8081/test/session2
@RequestMapping("session2")
public AjaxJson session2() {
System.out.println("======================= 进入方法,测试自定义session接口 ========================= ");
// 自定义session就是无需登录也可以使用 的session 比如拿用户的手机号当做 key 来获取 session
System.out.println("自定义 session的id为:" + SaSessionCustomUtil.getSessionById("1895544896").getId());
System.out.println("测试取值name" + SaSessionCustomUtil.getSessionById("1895544896").get("name"));
SaSessionCustomUtil.getSessionById("1895544896").set("name", "张三"); // 写入值
System.out.println("测试取值name" + SaSessionCustomUtil.getSessionById("1895544896").get("name"));
System.out.println("测试取值name" + SaSessionCustomUtil.getSessionById("1895544896").get("name"));
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().get("name"));
StpUtil.getTokenSession().set("name", "张三"); // 写入一个值
System.out.println("测试取值name" + StpUtil.getTokenSession().get("name"));
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);
}
// 测试注解式鉴权 浏览器访问 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/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.login(10001);
// 踢下线
StpUtil.kickout(10001);
// 再尝试获取
StpUtil.getLoginId();
// 返回
return AjaxJson.getSuccess();
}
// 测试登录接口, 按照设备登录 浏览器访问 http://localhost:8081/test/login2
@RequestMapping("login2")
public AjaxJson login2(@RequestParam(defaultValue="10001") String id, @RequestParam(defaultValue="PC") String device) {
StpUtil.login(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();
}
// 测试指定设备登录 浏览器访问 http://localhost:8081/test/loginByDevice
@RequestMapping("loginByDevice")
public AjaxJson loginByDevice() {
System.out.println("--------------");
StpUtil.login(10001, "PC");
return AjaxJson.getSuccessData("登录成功");
}
// 测试 浏览器访问 http://localhost:8081/test/test
@RequestMapping("test")
public AjaxJson test() {
System.out.println("------------进来了");
return AjaxJson.getSuccess();
}
// 测试 浏览器访问 http://localhost:8081/test/test2
@RequestMapping("test2")
public AjaxJson test2() {
return AjaxJson.getSuccess();
}
}
@@ -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
}
}
@@ -0,0 +1,49 @@
# 端口
server:
port: 8081
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
spring:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
+2 -29
View File
@@ -10,14 +10,14 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<version>2.5.12</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
@@ -39,26 +39,6 @@
<version>${sa-token-version}</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>
@@ -73,13 +53,6 @@
<optional>true</optional>
</dependency>
<!-- hutool工具类,用来生成雪花算法唯一id -->
<!-- <dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.4</version>
</dependency> -->
</dependencies>
@@ -42,7 +42,7 @@ public class GlobalException {
}
else if(e instanceof NotPermissionException) { // 如果是权限异常
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此权限:" + ee.getCode());
aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
}
else if(e instanceof DisableLoginException) { // 如果是被封禁异常
DisableLoginException ee = (DisableLoginException) e;
@@ -18,11 +18,6 @@ import cn.dev33.satoken.util.SaResult;
@RestController
public class NotFoundHandle implements ErrorController {
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping("/error")
public Object error(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setStatus(200);
@@ -11,7 +11,7 @@ import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
/**
* Sa-Token 权限认证工具类 (User版)
* Sa-Token 权限认证工具类 (user版)
* @author kong
*/
public class StpUserUtil {
@@ -110,9 +110,9 @@ public class StpUserUtil {
}
/**
* 会话登录并指定登录设备
* 会话登录并指定登录设备类型
* @param id 账号id建议的类型long | int | String
* @param device 设备标识
* @param device 设备类型
*/
public static void login(Object id, String device) {
stpLogic.login(id, device);
@@ -173,10 +173,10 @@ public class StpUserUtil {
}
/**
* 会话注销根据账号id 设备标识
* 会话注销根据账号id 设备类型
*
* @param loginId 账号id
* @param device 设备标识 (填null代表所有注销设备)
* @param device 设备类型 (填null代表注销所有设备类型)
*/
public static void logout(Object loginId, String device) {
stpLogic.logout(loginId, device);
@@ -202,11 +202,11 @@ public class StpUserUtil {
}
/**
* 踢人下线根据账号id 设备标识
* 踢人下线根据账号id 设备类型
* <p> 当对方再次访问系统时会抛出NotLoginException异常场景值=-5 </p>
*
* @param loginId 账号id
* @param device 设备标识 (填null代表踢出所有设备)
* @param device 设备类型 (填null代表踢出所有设备类型)
*/
public static void kickout(Object loginId, String device) {
stpLogic.kickout(loginId, device);
@@ -223,11 +223,11 @@ public class StpUserUtil {
}
/**
* 顶人下线根据账号id 设备标识
* 顶人下线根据账号id 设备类型
* <p> 当对方再次访问系统时会抛出NotLoginException异常场景值=-4 </p>
*
* @param loginId 账号id
* @param device 设备标识 (填null代表顶替所有设备)
* @param device 设备类型 (填null代表顶替所有设备类型)
*/
public static void replaced(Object loginId, String device) {
stpLogic.replaced(loginId, device);
@@ -309,7 +309,16 @@ public class StpUserUtil {
public static Object getLoginIdByToken(String tokenValue) {
return stpLogic.getLoginIdByToken(tokenValue);
}
/**
* 获取Token扩展信息只在jwt模式下有效
* @param key 键值
* @return 对应的扩展数据
*/
public static Object getExtra(String key) {
return stpLogic.getExtra(key);
}
// =================== User-Session 相关 ===================
@@ -390,7 +399,7 @@ public class StpUserUtil {
/**
* 续签当前token( [最后操作时间] 更新为当前时间戳)
* <h1>请注意: token已经 [临时过期] 也可续签成功
* <h1>请注意: 使token已经 [临时过期] 也可续签成功
* 如果此场景下需要提示续签失败可在此之前调用 checkActivityTimeout() 强制检查是否过期即可 </h1>
*/
public static void updateLastActivityToNow() {
@@ -431,8 +440,23 @@ public class StpUserUtil {
public static long getTokenActivityTimeout() {
return stpLogic.getTokenActivityTimeout();
}
/**
* 对当前 Token timeout 值进行续期
* @param timeout 要修改成为的有效时间 (单位: )
*/
public static void renewTimeout(long timeout) {
stpLogic.renewTimeout(timeout);
}
/**
* 对指定 Token timeout 值进行续期
* @param tokenValue 指定token
* @param timeout 要修改成为的有效时间 (单位: )
*/
public static void renewTimeout(String tokenValue, long timeout) {
stpLogic.renewTimeout(tokenValue, timeout);
}
// =================== 角色验证操作 ===================
@@ -610,11 +634,11 @@ public class StpUserUtil {
}
/**
* 获取指定账号id指定设备端的tokenValue
* 获取指定账号id指定设备类型端的tokenValue
* <p> 在配置为允许并发登录时此方法只会返回队列的最后一个token
* 如果你需要返回此账号id的所有token请调用 getTokenValueListByLoginId
* @param loginId 账号id
* @param device 设备标识
* @param device 设备类型
* @return token值
*/
public static String getTokenValueByLoginId(Object loginId, String device) {
@@ -631,9 +655,9 @@ public class StpUserUtil {
}
/**
* 获取指定账号id指定设备端的tokenValue 集合
* 获取指定账号id指定设备类型端的tokenValue 集合
* @param loginId 账号id
* @param device 设备标识
* @param device 设备类型
* @return 此loginId的所有相关token
*/
public static List<String> getTokenValueListByLoginId(Object loginId, String device) {
@@ -641,8 +665,8 @@ public class StpUserUtil {
}
/**
* 返回当前会话的登录设备
* @return 当前令牌的登录设备
* 返回当前会话的登录设备类型
* @return 当前令牌的登录设备类型
*/
public static String getLoginDevice() {
return stpLogic.getLoginDevice();
@@ -827,9 +851,9 @@ public class StpUserUtil {
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 StpUtil.login() 使用方式保持不变 </h1>
*
* 在当前会话上登录id, 并指定登录设备
* 在当前会话上登录id, 并指定登录设备类型
* @param loginId 登录id建议的类型long | int | String
* @param device 设备标识
* @param device 设备类型
*/
@Deprecated
public static void setLoginId(Object loginId, String device) {
@@ -839,7 +863,7 @@ public class StpUserUtil {
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 StpUtil.login() 使用方式保持不变 </h1>
*
* 在当前会话上登录id, 并指定登录设备
* 在当前会话上登录id, 并指定登录设备类型
* @param loginId 登录id建议的类型long | int | String
* @param isLastingCookie 是否为持久Cookie
*/
@@ -875,10 +899,10 @@ public class StpUserUtil {
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 StpUtil.kickout() 使用方式保持不变 </h1>
*
* 会话注销根据账号id and 设备标识 踢人下线
* 会话注销根据账号id and 设备类型 踢人下线
* <p> 当对方再次访问系统时会抛出NotLoginException异常场景值=-2 </p>
* @param loginId 账号id
* @param device 设备标识 (填null代表所有注销设备)
* @param device 设备类型 (填null代表注销所有设备类型)
*/
@Deprecated
public static void logoutByLoginId(Object loginId, String device) {
@@ -43,6 +43,8 @@
if(res.code == 200) {
localStorage.setItem('satoken', res.data);
location.href = decodeURIComponent(back);
} else {
alert(res.msg);
}
})
}
+10 -3
View File
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<version>2.5.12</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
@@ -34,7 +34,14 @@
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token整合redis (使用jackson序列化方式) -->
<!-- Sa-Token 插件:整合SSO -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token 插件:整合redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
@@ -7,7 +7,7 @@ import org.springframework.web.servlet.ModelAndView;
import com.ejlchina.okhttps.OkHttps;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.SaSsoHandle;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
@@ -34,15 +34,15 @@ public class SsoServerController {
// 配置SSO相关参数
@Autowired
private void configSso(SaTokenConfig cfg) {
private void configSso(SaSsoConfig sso) {
// 配置未登录时返回的View
cfg.sso.setNotLoginView(() -> {
sso.setNotLoginView(() -> {
return new ModelAndView("sa-login.html");
});
// 配置登录处理函数
cfg.sso.setDoLoginHandle((name, pwd) -> {
sso.setDoLoginHandle((name, pwd) -> {
// 此处仅做模拟登录真实环境应该查询数据进行登录
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
@@ -52,9 +52,16 @@ public class SsoServerController {
});
// 配置 Http 请求处理器 在模式三的单点注销功能下用到如不需要可以注释掉
cfg.sso.setSendHttp(url -> {
return OkHttps.sync(url).get().getBody().toString();
sso.setSendHttp(url -> {
try {
// 发起 http 请求
System.out.println("发起请求:" + url);
return OkHttps.sync(url).get().getBody().toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
});
}
}
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<version>2.5.12</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
@@ -34,6 +34,13 @@
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token 插件:整合SSO -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token 整合redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
@@ -4,7 +4,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
@@ -18,8 +18,8 @@ public class SsoClientController {
// SSO-Client端首页
@RequestMapping("/")
public String index() {
String authUrl = SaManager.getConfig().getSso().getAuthUrl();
String solUrl = SaManager.getConfig().getSso().getSloUrl();
String authUrl = SaSsoManager.getConfig().getAuthUrl();
String solUrl = SaSsoManager.getConfig().getSloUrl();
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
"<p><a href=\"javascript:location.href='" + authUrl + "?mode=simple&redirect=' + encodeURIComponent(location.href);\">登录</a> " +
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<version>2.5.12</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
@@ -34,6 +34,13 @@
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token 插件:整合SSO -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token 整合redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath
@@ -0,0 +1,35 @@
<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-sso3-client-nosdk</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<relativePath/>
</parent>
<dependencies>
<!-- SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Http 请求工具 -->
<dependency>
<groupId>com.dtflys.forest</groupId>
<artifactId>forest-spring-boot-starter</artifactId>
<version>1.5.19</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,14 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SaSsoClientApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoClientApplication.class, args);
System.out.println("\nSa-Token SSO模式三 Client端 (无SDK版本) 启动成功");
}
}
@@ -0,0 +1,177 @@
package com.pj.sso;
import java.io.IOException;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.pj.sso.util.AjaxJson;
import com.pj.sso.util.MyHttpSessionHolder;
/**
* SSO Client端 Controller
* @author kong
*/
@RestController
public class SsoClientController {
// SSO-Client端首页
@RequestMapping("/")
public String index(HttpSession session) {
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话登录账号:" + session.getAttribute("userId") + "</p>" +
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a>" +
" <a href='/sso/logout?back=' + + encodeURIComponent(location.href);>注销</a>" +
" <a href='/sso/myinfo' target=\"_blank\">获取资料</a></p>";
return str;
}
// SSO-Client端单点登录地址
@RequestMapping("/sso/login")
public Object ssoLogin(String ticket, @RequestParam(defaultValue = "/") String back,
HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException {
// 如果已经登录则直接返回
if(session.getAttribute("userId") != null) {
response.sendRedirect(back);
return null;
}
/*
* 此时有两种情况:
* 情况1ticket无值说明此请求是Client端访问需要重定向至SSO认证中心
* 情况2ticket有值说明此请求从SSO认证中心重定向而来需要根据ticket进行登录
*/
if(ticket == null) {
String currUrl = request.getRequestURL().toString();
String clientLoginUrl = currUrl + "?back=" + SsoRequestUtil.encodeUrl(back);
String serverAuthUrl = SsoRequestUtil.authUrl + "?redirect=" + clientLoginUrl;
response.sendRedirect(serverAuthUrl);
return null;
} else {
// 获取当前 client 端的单点注销回调地址
String ssoLogoutCall = "";
if(SsoRequestUtil.isSlo) {
ssoLogoutCall = request.getRequestURL().toString().replace("/sso/login", "/sso/logoutCall");
}
// 校验 ticket
String checkUrl = SsoRequestUtil.checkTicketUrl + "?ticket=" + ticket + "&ssoLogoutCall=" + ssoLogoutCall;
AjaxJson result = SsoRequestUtil.request(checkUrl);
// 200 代表校验成功
if(result.getCode() == 200 && SsoRequestUtil.isEmpty(result.getData()) == false) {
// 登录上
Object loginId = result.getData();
session.setAttribute("userId", loginId);
// 返回 back 地址
response.sendRedirect(back);
return null;
} else {
// sso-server 回应的消息作为异常抛出
throw new RuntimeException(result.getMsg());
}
}
}
// SSO-Client端单点注销地址
@RequestMapping("/sso/logout")
public Object ssoLogout(@RequestParam(defaultValue = "/") String back,
HttpServletResponse response, HttpSession session) throws IOException {
// 如果未登录则无需注销
if(session.getAttribute("userId") == null) {
response.sendRedirect(back);
return null;
}
// 调用 sso-server 认证中心单点注销API
Object loginId = session.getAttribute("userId"); // 账号id
String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳
String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串
String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce, SsoRequestUtil.secretkey); // 参数签名
String url = SsoRequestUtil.sloUrl +
"?loginId=" + loginId +
"&timestamp=" + timestamp +
"&nonce=" + nonce +
"&sign=" + sign;
AjaxJson result = SsoRequestUtil.request(url);
// 校验响应状态码200 代表成功
if(result.getCode() == 200) {
// 极端场景下sso-server 中心的单点注销可能并不会通知到此 client 所以这里需要再补一刀
session.removeAttribute("userId");
// 返回 back 地址
response.sendRedirect(back);
return null;
} else {
// sso-server 回应的消息作为异常抛出
throw new RuntimeException(result.getMsg());
}
}
// SSO-Client端单点注销回调地址
@RequestMapping("/sso/logoutCall")
public Object ssoLogoutCall(String loginId, String timestamp, String nonce, String sign) {
// 校验签名
String calcSign = SsoRequestUtil.getSign(loginId, timestamp, nonce, SsoRequestUtil.secretkey);
if(calcSign.equals(sign) == false) {
return AjaxJson.getError("无效签名,拒绝应答");
}
// 注销这个账号id
for (HttpSession session: MyHttpSessionHolder.sessionList) {
Object userId = session.getAttribute("userId");
if(Objects.equals(String.valueOf(userId), loginId)) {
session.removeAttribute("userId");
}
}
return AjaxJson.getSuccess("账号id=" + loginId + " 注销成功");
}
// 查询我的账号信息 (调用此接口的前提是 sso-server 端开放了 /sso/userinfo 路由)
@RequestMapping("/sso/myinfo")
public Object myinfo(HttpSession session) {
// 如果尚未登录
if(session.getAttribute("userId") == null) {
return "尚未登录,无法获取";
}
// 组织 url 参数
Object loginId = session.getAttribute("userId"); // 账号id
String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳
String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串
String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce, SsoRequestUtil.secretkey); // 参数签名
String url = SsoRequestUtil.userinfoUrl +
"?loginId=" + loginId +
"&timestamp=" + timestamp +
"&nonce=" + nonce +
"&sign=" + sign;
AjaxJson result = SsoRequestUtil.request(url);
// 返回给前端
return result;
}
// 全局异常拦截
@ExceptionHandler
public AjaxJson handlerException(Exception e) {
e.printStackTrace();
return AjaxJson.getError(e.getMessage());
}
}
@@ -0,0 +1,145 @@
package com.pj.sso;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.Map;
import java.util.Random;
import com.dtflys.forest.Forest;
import com.pj.sso.util.AjaxJson;
/**
* 封装一些 sso 共用方法
*
* @author kong
* @since: 2022-4-30
*/
public class SsoRequestUtil {
/**
* SSO-Server端 统一认证地址
*/
public static String authUrl = "http://sa-sso-server.com:9000/sso/auth";
/**
* 使用 Http 请求校验ticket
*/
// public static boolean isHttp = true;
/**
* SSO-Server端 ticket校验地址
*/
public static String checkTicketUrl = "http://sa-sso-server.com:9000/sso/checkTicket";
/**
* 打开单点注销功能
*/
public static boolean isSlo = true;
/**
* 单点注销地址
*/
public static String sloUrl = "http://sa-sso-server.com:9000/sso/logout";
/**
* 接口调用秘钥
*/
public static String secretkey = "kQwIOrYvnXmSDkwEiFngrKidMcdrgKor";
/**
* SSO-Server端 查询userinfo地址
*/
public static String userinfoUrl = "http://sa-sso-server.com:9000/sso/userinfo";
// -------------------------- 工具方法
/**
* 发出请求并返回 SaResult 结果
* @param url 请求地址
* @return 返回的结果
*/
public static AjaxJson request(String url) {
Map<String, Object> map = Forest.post(url).executeAsMap();
return new AjaxJson(map);
}
/**
* 根据参数计算签名
* @param loginId 账号id
* @param timestamp 当前时间戳13位
* @param nonce 随机字符串
* @param secretkey 账号id
* @return 签名
*/
public static String getSign(Object loginId, String timestamp, String nonce, String secretkey) {
return md5("loginId=" + loginId + "&nonce=" + nonce + "&timestamp=" + timestamp + "&key=" + secretkey);
}
/**
* 指定元素是否为null或者空字符串
* @param str 指定元素
* @return 是否为null或者空字符串
*/
public static boolean isEmpty(Object str) {
return str == null || "".equals(str);
}
/**
* md5加密
* @param str 指定字符串
* @return 加密后的字符串
*/
public static String md5(String str) {
str = (str == null ? "" : str);
char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
try {
byte[] btInput = str.getBytes();
MessageDigest mdInst = MessageDigest.getInstance("MD5");
mdInst.update(btInput);
byte[] md = mdInst.digest();
int j = md.length;
char[] strA = new char[j * 2];
int k = 0;
for (byte byte0 : md) {
strA[k++] = hexDigits[byte0 >>> 4 & 0xf];
strA[k++] = hexDigits[byte0 & 0xf];
}
return new String(strA);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 生成指定长度的随机字符串
*
* @param length 字符串的长度
* @return 一个随机字符串
*/
public static String getRandomString(int length) {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
/**
* URL编码
* @param url see note
* @return see note
*/
public static String encodeUrl(String url) {
try {
return URLEncoder.encode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,231 @@
package com.pj.sso.util;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* ajax请求返回Json格式数据的封装 <br>
* 所有预留字段<br>
* code=状态码 <br>
* msg=描述信息 <br>
* data=携带对象 <br>
* pageNo=当前页 <br>
* pageSize=页大小 <br>
* startIndex=起始索引 <br>
* dataCount=数据总数 <br>
* pageCount=分页总数 <br>
* <p> 返回范例</p>
* <pre>
{
"code": 200, // 成功时=200, 失败时=500 msg=失败原因
"msg": "ok",
"data": {}
}
</pre>
*/
public class AjaxJson extends LinkedHashMap<String, Object> 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; // 无效请求状态码
// ============================ 写值取值 ==================================
/** 给code赋值,连缀风格 */
public AjaxJson setCode(int code) {
this.put("code", code);
return this;
}
/** 返回code */
public Integer getCode() {
return (Integer)this.get("code");
}
/** 给msg赋值,连缀风格 */
public AjaxJson setMsg(String msg) {
this.put("msg", msg);
return this;
}
/** 获取msg */
public String getMsg() {
return (String)this.get("msg");
}
/** 给data赋值,连缀风格 */
public AjaxJson setData(Object data) {
this.put("data", data);
return this;
}
/** 获取data */
public Object getData() {
return this.get("data");
}
/** 将data还原为指定类型并返回 */
@SuppressWarnings("unchecked")
public <T> T getData(Class<T> cs) {
return (T) this.getData();
}
/** 给dataCount(数据总数)赋值,连缀风格 */
public AjaxJson setDataCount(Long dataCount) {
this.put("dataCount", dataCount);
// 如果提供了数据总数则尝试计算page信息
if(dataCount != null && dataCount >= 0) {
// 如果已有page信息
if(get("pageNo") != null) {
this.initPageInfo();
}
// // 或者是JavaWeb环境
// else if(SoMap.isJavaWeb()) {
// SoMap so = SoMap.getRequestSoMap();
// this.setPageNoAndSize(so.getKeyPageNo(), so.getKeyPageSize());
// this.initPageInfo();
// }
}
return this;
}
/** 获取dataCount(数据总数) */
public Long getDataCount() {
return (Long)this.get("dataCount");
}
/** 设置pageNo 和 pageSize,并计算出startIndex于pageCount */
public AjaxJson setPageNoAndSize(long pageNo, long pageSize) {
this.put("pageNo", pageNo);
this.put("pageSize", pageSize);
return this;
}
/** 根据 pageSize dataCount,计算startIndex 与 pageCount */
public AjaxJson initPageInfo() {
long pageNo = (long)this.get("pageNo");
long pageSize = (long)this.get("pageSize");
long dataCount = (long)this.get("dataCount");
this.set("startIndex", (pageNo - 1) * pageSize);
long pc = dataCount / pageSize;
this.set("pageCount", (dataCount % pageSize == 0 ? pc : pc + 1));
return this;
}
/** 写入一个值 自定义key, 连缀风格 */
public AjaxJson set(String key, Object data) {
this.put(key, data);
return this;
}
/** 写入一个Map, 连缀风格 */
public AjaxJson setMap(Map<String, ?> map) {
for (String key : map.keySet()) {
this.put(key, map.get(key));
}
return this;
}
// ============================ 构建 ==================================
public AjaxJson(int code, String msg, Object data, Long dataCount) {
this.setCode(code);
this.setMsg(msg);
this.setData(data);
if(dataCount != null) {
this.setDataCount(dataCount);
}
}
public AjaxJson(Map<String, Object> map) {
for (String key: map.keySet()) {
this.set(key, map.get(key));
}
}
/** 返回成功 */
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 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");
}
// // 历史版本遗留代码
// public int code; // 状态码
// public String msg; // 描述信息
// public Object data; // 携带对象
// public Long dataCount; // 数据总数用于分页
}
@@ -0,0 +1,34 @@
package com.pj.sso.util;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.springframework.stereotype.Component;
/**
* 记录所有已创建的 HttpSession 对象
*
* <b> 此种方式有性能问题仅做demo示例真实项目中请更换为其它方案记录用户会话数据 </b>
*
* @author kong
* @since: 2022-4-30
*/
@Component
public class MyHttpSessionHolder implements HttpSessionListener {
public static List<HttpSession> sessionList = new ArrayList<>();
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
sessionList.add(httpSessionEvent.getSession());
}
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
HttpSession session = httpSessionEvent.getSession();
sessionList.remove(session);
}
}
@@ -0,0 +1,36 @@
# 端口
server:
port: 9001
forest:
# 打开/关闭Forest请求日志(默认为 true)
log-request: true
spring:
# Redis连接
redis:
# Redis数据库索引
database: 2
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<version>2.5.12</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
@@ -34,6 +34,13 @@
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token 插件:整合SSO -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token 整合redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
@@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.RestController;
import com.ejlchina.okhttps.OkHttps;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.SaSsoHandle;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
@@ -43,9 +43,10 @@ public class SsoClientController {
// 配置SSO相关参数
@Autowired
private void configSso(SaTokenConfig cfg) {
private void configSso(SaSsoConfig sso) {
// 配置Http请求处理器
cfg.sso.setSendHttp(url -> {
sso.setSendHttp(url -> {
System.out.println("发起请求:" + url);
return OkHttps.sync(url).get().getBody().toString();
});
}
@@ -22,7 +22,7 @@ sa-token:
userinfo-url: http://sa-sso-server.com:9000/sso/userinfo
spring:
# 配置Sa-Token单独使用的Redis连接 (此处与SSO-Server端连接不同的Redis
# 配置 Redis 连接 (此处与SSO-Server端连接不同的Redis
redis:
# Redis数据库索引
database: 2
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<version>2.5.12</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
+2 -2
View File
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<version>2.5.12</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<dependencies>
@@ -19,6 +19,7 @@ public class DefineRoutes {
* 函数式编程初始化路由表
* @return 路由表
*/
@SuppressWarnings("deprecation")
@Bean
public RouterFunction<ServerResponse> getRoutes() {
return RouterFunctions.route(RequestPredicates.GET("/fun"), req -> {
@@ -37,7 +37,7 @@ public class GlobalException {
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
} else if(e instanceof NotPermissionException) { // 如果是权限异常
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此权限:" + ee.getCode());
aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
} else if(e instanceof DisableLoginException) { // 如果是被封禁异常
DisableLoginException ee = (DisableLoginException) e;
aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封");
@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath
@@ -0,0 +1,69 @@
<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-websocket-spring</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.30.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>
<!-- WebScoket 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</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>
<!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency> -->
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,32 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaManager;
/**
* Sa-Token 整合 WebSocket 鉴权示例
* @author kong
*
*/
@SpringBootApplication
public class SaTokenWebSocketSpringApplication {
/*
* 1访问登录接口拿到会话Token
* http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
*
* 2找一个WebSocket在线测试页面进行连接
* 例如
* https://www.bejson.com/httputil/websocket/
* 然后连接地址
* ws://localhost:8081/ws-connect?satoken=2e6db38f-1e78-40bc-aa8f-e8f1f77fbef5
*/
public static void main(String[] args) {
SpringApplication.run(SaTokenWebSocketSpringApplication.class, args);
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}
@@ -0,0 +1,48 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 登录测试
* @author kong
*
*/
@RestController
@RequestMapping("/acc/")
public class LoginController {
// 测试登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
// 此处仅作模拟示例真实项目需要从数据库中查询数据进行比对
if("zhang".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功").set("token", StpUtil.getTokenValue());
}
return SaResult.error("登录失败");
}
// 查询登录状态 ---- http://localhost:8081/acc/isLogin
@RequestMapping("isLogin")
public SaResult isLogin() {
return SaResult.ok("是否登录:" + StpUtil.isLogin());
}
// 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
@RequestMapping("tokenInfo")
public SaResult tokenInfo() {
return SaResult.data(StpUtil.getTokenInfo());
}
// 测试注销 ---- http://localhost:8081/acc/logout
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}
@@ -0,0 +1,83 @@
package com.pj.ws;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/**
* 处理 WebSocket 连接
*
* @author kong
* @since: 2022-2-11
*/
public class MyWebSocketHandler extends TextWebSocketHandler {
/**
* 固定前缀
*/
private static final String USER_ID = "user_id_";
/**
* 存放Session集合方便推送消息
*/
private static ConcurrentHashMap<String, WebSocketSession> webSocketSessionMaps = new ConcurrentHashMap<>();
// 监听连接开启
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// put到集合方便后续操作
String userId = session.getAttributes().get("userId").toString();
webSocketSessionMaps.put(USER_ID + userId, session);
// 给个提示
String tips = "Web-Socket 连接成功,sid=" + session.getId() + "userId=" + userId;
System.out.println(tips);
sendMessage(session, tips);
}
// 监听连接关闭
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// 从集合移除
String userId = session.getAttributes().get("userId").toString();
webSocketSessionMaps.remove(USER_ID + userId);
// 给个提示
String tips = "Web-Socket 连接关闭,sid=" + session.getId() + "userId=" + userId;
System.out.println(tips);
}
// 收到消息
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
System.out.println("sid为:" + session.getId() + ",发来:" + message);
}
// -----------
// 向指定客户端推送消息
public static void sendMessage(WebSocketSession session, String message) {
try {
System.out.println("向sid为:" + session.getId() + ",发送:" + message);
session.sendMessage(new TextMessage(message));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 向指定用户推送消息
public static void sendMessage(long userId, String message) {
WebSocketSession session = webSocketSessionMaps.get(USER_ID + userId);
if(session != null) {
sendMessage(session, message);
}
}
}
@@ -0,0 +1,30 @@
package com.pj.ws;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* WebSocket 相关配置
*
* @author kong
* @since: 2022-2-11
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
// 注册 WebSocket 处理器
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry
// WebSocket 连接处理器
.addHandler(new MyWebSocketHandler(), "/ws-connect")
// WebSocket 拦截器
.addInterceptors(new WebSocketInterceptor())
// 允许跨域
.setAllowedOrigins("*");
}
}
@@ -0,0 +1,45 @@
package com.pj.ws;
import java.util.Map;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import cn.dev33.satoken.stp.StpUtil;
/**
* WebSocket 握手的前置拦截器
*
* @author kong
* @since: 2022-2-11
*/
public class WebSocketInterceptor implements HandshakeInterceptor {
// 握手之前触发 (return true 才会握手成功 )
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler,
Map<String, Object> attr) {
System.out.println("---- 握手之前触发 " + StpUtil.getTokenValue());
// 未登录情况下拒绝握手
if(StpUtil.isLogin() == false) {
System.out.println("---- 未授权客户端,连接失败");
return false;
}
// 标记 userId握手成功
attr.put("userId", StpUtil.getLoginIdAsLong());
return true;
}
// 握手之后触发
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
System.out.println("---- 握手之后触发 ");
}
}
@@ -0,0 +1,49 @@
# 端口
server:
port: 8081
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
spring:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath
@@ -0,0 +1,69 @@
<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-websocket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.30.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>
<!-- WebScoket 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</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>
<!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency> -->
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,32 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaManager;
/**
* Sa-Token 整合 WebSocket 鉴权示例
* @author kong
*
*/
@SpringBootApplication
public class SaTokenWebSocketApplication {
/*
* 1访问登录接口拿到会话Token
* http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
*
* 2找一个WebSocket在线测试页面进行连接
* 例如
* https://www.bejson.com/httputil/websocket/
* 然后连接地址
* ws://localhost:8081/ws-connect/2e6db38f-1e78-40bc-aa8f-e8f1f77fbef5
*/
public static void main(String[] args) {
SpringApplication.run(SaTokenWebSocketApplication.class, args);
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}

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