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

Compare commits

...

63 Commits

Author SHA1 Message Date
click33 1e87426481 v1.34.0 更新 2023-01-11 14:35:04 +08:00
click33 69cecb5497 移除过期类测试 2023-01-10 19:50:14 +08:00
click33 fc903cf296 完善文档 2023-01-10 19:02:11 +08:00
click33 a917da0b72 ... 2023-01-10 16:58:15 +08:00
孔明 9e2d1fb19b !211 alone-redis 的 single 模式追加一个参数
Merge pull request !211 from AppleOfGray/dev
2023-01-10 08:57:40 +00:00
hjc 85f0f9f55a Merge remote-tracking branch 'origin/dev' into dev 2023-01-10 16:53:51 +08:00
hjc 24b6168ce4 alone-redis的single模式追加一个参数 2023-01-10 16:52:13 +08:00
click33 c6c1ad1ac5 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2023-01-10 16:36:06 +08:00
click33 9469251376 细节优化 2023-01-10 16:35:35 +08:00
孔明 0eb9e5bcdc !210 添加alone-redis集群模式文档
Merge pull request !210 from AppleOfGray/N/A
2023-01-10 06:43:46 +00:00
AppleOfGray 0a25a62abb 添加alone-redis集群模式文档
这些配置都是和spring redis保持一致的, 底层实现也是调用的spring相关的配置类, 
且配置参数并不是satoken独创的, 开发者只需要有spring redis对接基础就可以用. 
而关于spring redis的集群配置文献, 百度上太多了
2023-01-10 01:02:44 +00:00
click33 081b6a1b70 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2023-01-09 18:36:01 +08:00
孔明 6d0a2455f1 !209 搜索配置的时候又发现了一些其他的配置类, 一并提交了
Merge pull request !209 from AppleOfGray/dev
2023-01-09 10:35:01 +00:00
hjc 0cf045e171 发现了一些新的配置, 一并提交 2023-01-09 13:31:57 +08:00
click33 a1111c5e50 Merge branch 'dev' of github.com:dromara/sa-token into dev 2023-01-09 00:41:56 +08:00
click33 f2c883621e Merge pull request #365 from dromara/dependabot/npm_and_yarn/sa-token-demo/sa-token-demo-sso-client-vue2/json5-1.0.2
Bump json5 from 1.0.1 to 1.0.2 in /sa-token-demo/sa-token-demo-sso-client-vue2
2023-01-09 00:41:34 +08:00
孔明 46a98b8ad5 !208 alone-redis 尝试添加集群模式
Merge pull request !208 from AppleOfGray/dev
2023-01-06 15:46:28 +00:00
孔明 1eac25ea46 !207 add 新增 演示案例 sa-token-demo-springboot-redisson 项目
Merge pull request !207 from 疯狂的狮子Li/dev
2023-01-06 15:45:51 +00:00
hjc 80153984e5 alone-redis添加集群模式 2023-01-06 11:18:05 +08:00
疯狂的狮子Li b7a004d7ce add 新增 演示案例 sa-token-demo-springboot-redisson 项目 2023-01-05 23:12:37 +08:00
dependabot[bot] f5dbe6c6ba Bump json5 in /sa-token-demo/sa-token-demo-sso-client-vue2
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-05 13:30:30 +00:00
click33 374cf70785 为 sa-token-dao-redisson-jackson 适配上 springboot3 2023-01-05 21:29:35 +08:00
click33 40d1056713 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2023-01-05 21:10:13 +08:00
孔明 6de36aec40 !206 add 增加 适配 redisson 客户端 jackson 序列化 插件
Merge pull request !206 from 疯狂的狮子Li/dev
2023-01-05 13:00:38 +00:00
click33 2cccf35223 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2023-01-05 19:23:37 +08:00
click33 1feca73e2a 适配 springboot3 2023-01-05 19:20:21 +08:00
疯狂的狮子li 9ce29219d6 update 优化 不强制覆盖序列化模式 使用每次赋值的方式 灵活变更序列化 2023-01-03 13:54:16 +08:00
疯狂的狮子li 0cc497f21f update 更新文档说明 2023-01-03 13:32:47 +08:00
疯狂的狮子li 44ecbe7845 add 增加 适配 redisson 客户端 jackson 序列化 插件 2023-01-03 13:20:20 +08:00
孔明 4bb07de8ff !205 添加示例项目:sa-token-demo-sso-server-solon
Merge pull request !205 from 西东/dev
2023-01-03 04:11:50 +00:00
noear c744bd7744 添加示例项目:sa-token-demo-sso-server-solon 2023-01-03 11:57:22 +08:00
click33 d09366602a 细节优化 2022-12-31 09:06:54 +08:00
click33 418172362a Merge branch 'dev' of github.com:dromara/sa-token into dev 2022-12-27 12:41:20 +08:00
click33 81c1ddacde Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2022-12-27 12:40:30 +08:00
click33 28b3c0567a ticket与client锁定. 2022-12-27 12:31:37 +08:00
click33 cbce0aefd9 Merge pull request #353 from dromara/dependabot/maven/sa-token-starter/sa-token-reactor-spring-boot-starter/org.springframework-spring-web-6.0.0
Bump spring-web from 5.3.7 to 6.0.0 in /sa-token-starter/sa-token-reactor-spring-boot-starter
2022-12-26 23:53:03 +08:00
click33 75bfb395c5 Merge pull request #358 from dromara/dependabot/npm_and_yarn/sa-token-demo/sa-token-demo-sso-client-vue2/loader-utils-1.4.2
Bump loader-utils from 1.4.0 to 1.4.2 in /sa-token-demo/sa-token-demo-sso-client-vue2
2022-12-26 23:52:42 +08:00
click33 660b932f61 Merge pull request #341 from thebitmin/dev
refactor: 修改 for 循环条件,减少循环次数
2022-12-26 23:51:29 +08:00
dependabot[bot] 7fc188123d Bump loader-utils in /sa-token-demo/sa-token-demo-sso-client-vue2
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.2.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.2)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-26 15:43:39 +00:00
click33 3e183a270b Merge pull request #325 from dromara/dependabot/npm_and_yarn/sa-token-demo/sa-token-demo-sso-client-vue2/node-forge-and-vue/cli-plugin-babel-and-vue/cli-plugin-eslint-and-vue/cli-service-1.3.1
Bump node-forge, @vue/cli-plugin-babel, @vue/cli-plugin-eslint and @vue/cli-service in /sa-token-demo/sa-token-demo-sso-client-vue2
2022-12-26 23:43:18 +08:00
click33 30d9b325dd Merge pull request #324 from dromara/dependabot/npm_and_yarn/sa-token-demo/sa-token-demo-sso-client-vue2/ejs-and-vue/cli-plugin-babel-and-vue/cli-plugin-eslint-and-vue/cli-service--removed
Bump ejs, @vue/cli-plugin-babel, @vue/cli-plugin-eslint and @vue/cli-service in /sa-token-demo/sa-token-demo-sso-client-vue2
2022-12-26 23:43:05 +08:00
孔明 4370f366b5 update sa-token-doc/api/sa-token-dao.md.
Signed-off-by: 孔明 <2393584716@qq.com>
2022-12-26 15:00:15 +00:00
孔明 9816e0dc91 !204 update sa-token-doc/use/config.md.
Merge pull request !204 from AppleOfGray/N/A
2022-12-26 14:55:47 +00:00
孔明 2dce51bfbf !197 优化文档
Merge pull request !197 from Rain/N/A
2022-12-26 14:55:29 +00:00
孔明 3f079c3514 !203 sa-token-solon-plugin: 升级 solon 为 1.12.0
Merge pull request !203 from 西东/dev
2022-12-26 14:54:39 +00:00
AppleOfGray abfccdb5d1 update sa-token-doc/use/config.md.
代码多了
2022-12-26 13:22:04 +00:00
孔明 0f2aa12727 !201 update sa-token-doc/api/sa-session.md.
Merge pull request !201 from 小可开源/N/A
2022-12-26 13:19:43 +00:00
noear 422f7d2e4c sa-token-solon-plugin: 升级 solon 为 1.12.0 2022-12-26 15:37:39 +08:00
dependabot[bot] 7ab607486c Bump spring-web
Bumps [spring-web](https://github.com/spring-projects/spring-framework) from 5.3.7 to 6.0.0.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.7...v6.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-10 06:23:07 +00:00
小可开源 c62f61755f update sa-token-doc/api/sa-session.md.
常量说明中
SaSession.ROLE_LIST= "USER";   // 在Session 上存储用户对象时建议使用的key
写成了角色的值SaSession.ROLE_LIST,应该为SaSession.USER

Signed-off-by: 小可开源 <2479427380@qq.com>
2022-12-09 07:40:21 +00:00
click33 90d14ef23b 增加 ticket 的 client 锁定功能 2022-12-07 20:39:24 +08:00
click33 385cf3b2e4 升级临时 Token 认证模块,可指定 service 参数。 2022-12-07 09:59:23 +08:00
noear 37efe9a8d8 sa-token-solon-plugin: 升级 solon 为 1.11.3 2022-12-01 14:28:20 +08:00
bitmin 026aef16bd refactor: 修改 for 循环条件,减少循环次数 2022-11-24 14:21:13 +08:00
Rain d3d9fd32ec 优化文档
Signed-off-by: Rain <938448486@qq.com>
2022-11-22 05:55:58 +00:00
孔明 6b8dcbc42f !196 StringBuffer替换为为StringBuilder
Merge pull request !196 from z.h.z/dev
2022-11-19 13:57:33 +00:00
zhz 25231c4b9e StringBuffer替换为为StringBuilder
在变量为局部变量没有线程安全问题时可以选用性能更好的StringBuilder
2022-11-17 14:54:25 +08:00
click33 57d2843f90 去除不必要的打印语句 2022-11-17 10:42:25 +08:00
click33 c333d08f34 文档 2022-11-17 10:17:15 +08:00
click33 6f75c7ec24 更新日志 2022-11-17 10:02:38 +08:00
click33 6fe8c6bad9 文档 2022-11-16 23:22:47 +08:00
dependabot[bot] aaf190132b Bump node-forge, @vue/cli-plugin-babel, @vue/cli-plugin-eslint and @vue/cli-service
Bumps [node-forge](https://github.com/digitalbazaar/forge) to 1.3.1 and updates ancestor dependencies [node-forge](https://github.com/digitalbazaar/forge), [@vue/cli-plugin-babel](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-babel), [@vue/cli-plugin-eslint](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-eslint) and [@vue/cli-service](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-service). These dependencies need to be updated together.


Updates `node-forge` from 0.10.0 to 1.3.1
- [Release notes](https://github.com/digitalbazaar/forge/releases)
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/0.10.0...v1.3.1)

Updates `@vue/cli-plugin-babel` from 4.4.6 to 5.0.8
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v5.0.8/packages/@vue/cli-plugin-babel)

Updates `@vue/cli-plugin-eslint` from 4.4.6 to 5.0.8
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v5.0.8/packages/@vue/cli-plugin-eslint)

Updates `@vue/cli-service` from 4.4.6 to 5.0.8
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v5.0.8/packages/@vue/cli-service)

---
updated-dependencies:
- dependency-name: node-forge
  dependency-type: indirect
- dependency-name: "@vue/cli-plugin-babel"
  dependency-type: direct:development
- dependency-name: "@vue/cli-plugin-eslint"
  dependency-type: direct:development
- dependency-name: "@vue/cli-service"
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-27 12:54:17 +00:00
dependabot[bot] 9614b24c92 Bump ejs, @vue/cli-plugin-babel, @vue/cli-plugin-eslint and @vue/cli-service
Removes [ejs](https://github.com/mde/ejs). It's no longer used after updating ancestor dependencies [ejs](https://github.com/mde/ejs), [@vue/cli-plugin-babel](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-babel), [@vue/cli-plugin-eslint](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-eslint) and [@vue/cli-service](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-service). These dependencies need to be updated together.


Removes `ejs`

Updates `@vue/cli-plugin-babel` from 4.4.6 to 5.0.8
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v5.0.8/packages/@vue/cli-plugin-babel)

Updates `@vue/cli-plugin-eslint` from 4.4.6 to 5.0.8
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v5.0.8/packages/@vue/cli-plugin-eslint)

Updates `@vue/cli-service` from 4.4.6 to 5.0.8
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v5.0.8/packages/@vue/cli-service)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
- dependency-name: "@vue/cli-plugin-babel"
  dependency-type: direct:development
- dependency-name: "@vue/cli-plugin-eslint"
  dependency-type: direct:development
- dependency-name: "@vue/cli-service"
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-27 12:54:16 +00:00
247 changed files with 12690 additions and 15943 deletions
+7 -1
View File
@@ -1,7 +1,7 @@
<p align="center">
<img alt="logo" src="https://sa-token.cc/logo.png" width="150" height="150">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.33.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.34.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>
@@ -194,3 +194,9 @@ QQ交流群:707350988 [点击加入](https://jq.qq.com/?_wv=1027&k=tqbzHT2D)
<br>
加入群聊的好处:
- 第一时间收到框架更新通知。
- 第一时间收到框架 bug 通知。
- 第一时间收到新增开源案例通知。
- 和众多大佬一起互相 (huá shǔi) 交流 (mō yú)。
+6
View File
@@ -6,6 +6,8 @@ call mvn clean
cd sa-token-demo
cd sa-token-demo-alone-redis & call mvn clean & cd ..
cd sa-token-demo-alone-redis-cluster & call mvn clean & cd ..
cd sa-token-demo-case & call mvn clean & cd ..
cd sa-token-demo-dubbo-consumer & call mvn clean & cd ..
cd sa-token-demo-dubbo-provider & call mvn clean & cd ..
cd sa-token-demo-grpc & call mvn clean & cd ..
@@ -16,15 +18,19 @@ cd sa-token-demo-quick-login & call mvn clean & cd ..
cd sa-token-demo-solon & call mvn clean & cd ..
cd sa-token-demo-case & call mvn clean & cd ..
cd sa-token-demo-springboot & call mvn clean & cd ..
cd sa-token-demo-springboot3-redis & call mvn clean & cd ..
cd sa-token-demo-springboot-redis & call mvn clean & cd ..
cd sa-token-demo-springboot-redisson & call mvn clean & cd ..
cd sa-token-demo-test & 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-sso-server & call mvn clean & cd ..
cd sa-token-demo-sso-server-solon & call mvn clean & cd ..
cd sa-token-demo-thymeleaf & call mvn clean & cd ..
cd sa-token-demo-webflux & call mvn clean & cd ..
cd sa-token-demo-webflux-springboot3 & call mvn clean & cd ..
cd sa-token-demo-websocket & call mvn clean & cd ..
cd sa-token-demo-websocket-spring & call mvn clean & cd ..
+24 -6
View File
@@ -24,9 +24,10 @@
<module>sa-token-plugin</module>
<!-- <module>sa-token-test</module> -->
<!-- <module>sa-token-demo/sa-token-demo-solon</module> -->
</modules>
<!-- 开源协议 apache 2.0 -->
<!-- <module>sa-token-demo/sa-token-demo-sso-server-solon</module> -->
</modules>
<!-- 开源协议 apache 2.0 -->
<licenses>
<license>
<name>Apache 2</name>
@@ -38,7 +39,7 @@
<!-- 一些属性 -->
<properties>
<revision>1.33.0</revision>
<revision>1.34.0</revision>
<jdk.version>1.8</jdk.version>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
@@ -94,14 +95,31 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<version>3.10.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<additionalparam>-Xdoclint:none</additionalparam>
</configuration>
</execution>
</executions>
</plugin>
<!-- 统一版本号管理 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
@@ -8,7 +8,7 @@ import java.lang.annotation.Target;
import cn.dev33.satoken.basic.SaBasicTemplate;
/**
* Http Basic 认证:只有通过 Basic 认证后才能进入该方法
* Http Basic 认证校验:只有通过 Basic 认证后才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
* @author kong
*
@@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 登录认证:只有登录之后才能进入该方法
* 登录认证校验:只有登录之后才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
* @author kong
*
@@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限认证:必须具有指定权限才能进入该方法
* 权限认证校验:必须具有指定权限才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
* @author kong
*
@@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 角色认证:必须具有指定角色标识才能进入该方法
* 角色认证校验:必须具有指定角色标识才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
* @author kong
*
@@ -10,86 +10,86 @@ import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* Cookie Model
* Cookie Model
* @author kong
*
*/
public class SaCookie {
/**
* 写入响应头时使用的key
* 写入响应头时使用的key
*/
public static final String HEADER_NAME = "Set-Cookie";
/**
* 名称
* 名称
*/
private String name;
/**
* 值
* 值
*/
private String value;
/**
* 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
* 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
*/
private int maxAge = -1;
private int maxAge = -1;
/**
* 域
*/
private String domain;
private String domain;
/**
* 路径
* 路径
*/
private String path;
/**
* 是否只在 https 协议下有效
* 是否只在 https 协议下有效
*/
private Boolean secure = false;
private Boolean secure = false;
/**
* 是否禁止 js 操作 Cookie
* 是否禁止 js 操作 Cookie
*/
private Boolean httpOnly = false;
private Boolean httpOnly = false;
/**
* 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
*/
private String sameSite;
/**
* 构造一个
* 构造一个
*/
public SaCookie() {
}
/**
* 构造一个
* 构造一个
* @param name 名字
* @param value 值
* @param value 值
*/
public SaCookie(String name, String value) {
this.name = name;
this.value = value;
}
/**
* @return 名称
* @return 名称
*/
public String getName() {
return name;
}
/**
* @param name 名称
* @return 对象自身
* @param name 名称
* @return 对象自身
*/
public SaCookie setName(String name) {
this.name = name;
@@ -97,15 +97,15 @@ public class SaCookie {
}
/**
* @return 值
* @return 值
*/
public String getValue() {
return value;
}
/**
* @param value 值
* @return 对象自身
* @param value 值
* @return 对象自身
*/
public SaCookie setValue(String value) {
this.value = value;
@@ -113,15 +113,15 @@ public class SaCookie {
}
/**
* @return 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
* @return 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
*/
public int getMaxAge() {
return maxAge;
}
/**
* @param maxAge 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
* @return 对象自身
* @param maxAge 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
* @return 对象自身
*/
public SaCookie setMaxAge(int maxAge) {
this.maxAge = maxAge;
@@ -129,15 +129,15 @@ public class SaCookie {
}
/**
* @return 域
* @return 域
*/
public String getDomain() {
return domain;
}
/**
* @param domain 域
* @return 对象自身
* @param domain 域
* @return 对象自身
*/
public SaCookie setDomain(String domain) {
this.domain = domain;
@@ -145,15 +145,15 @@ public class SaCookie {
}
/**
* @return 路径
* @return 路径
*/
public String getPath() {
return path;
}
/**
* @param path 路径
* @return 对象自身
* @param path 路径
* @return 对象自身
*/
public SaCookie setPath(String path) {
this.path = path;
@@ -161,15 +161,15 @@ public class SaCookie {
}
/**
* @return 是否只在 https 协议下有效
* @return 是否只在 https 协议下有效
*/
public Boolean getSecure() {
return secure;
}
/**
* @param secure 是否只在 https 协议下有效
* @return 对象自身
* @param secure 是否只在 https 协议下有效
* @return 对象自身
*/
public SaCookie setSecure(Boolean secure) {
this.secure = secure;
@@ -177,15 +177,15 @@ public class SaCookie {
}
/**
* @return 是否禁止 js 操作 Cookie
* @return 是否禁止 js 操作 Cookie
*/
public Boolean getHttpOnly() {
return httpOnly;
}
/**
* @param httpOnly 是否禁止 js 操作 Cookie
* @return 对象自身
* @param httpOnly 是否禁止 js 操作 Cookie
* @return 对象自身
*/
public SaCookie setHttpOnly(Boolean httpOnly) {
this.httpOnly = httpOnly;
@@ -201,7 +201,7 @@ public class SaCookie {
/**
* @param sameSite 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
* @return 对象自身
* @return 对象自身
*/
public SaCookie setSameSite(String sameSite) {
this.sameSite = sameSite;
@@ -209,57 +209,57 @@ public class SaCookie {
}
// toString
// toString
@Override
public String toString() {
return "SaCookie [name=" + name + ", value=" + value + ", maxAge=" + maxAge + ", domain=" + domain + ", path=" + path
return "SaCookie [name=" + name + ", value=" + value + ", maxAge=" + maxAge + ", domain=" + domain + ", path=" + path
+ ", secure=" + secure + ", httpOnly=" + httpOnly + ", sameSite="
+ sameSite + "]";
}
/**
* 构建一下
* 构建一下
*/
public void builde() {
if(path == null) {
path = "/";
}
}
/**
* 转换为响应头 Set-Cookie 参数需要的值
* @return /
* @return /
*/
public String toHeaderValue() {
this.builde();
if(SaFoxUtil.isEmpty(name)) {
throw new SaTokenException("name不能为空").setCode(SaErrorCode.CODE_12002);
}
if(value != null && value.indexOf(";") > -1) {
if(value != null && value.contains(";")) {
throw new SaTokenException("无效Value" + value).setCode(SaErrorCode.CODE_12003);
}
// Set-Cookie: name=value; Max-Age=100000; Expires=Tue, 05-Oct-2021 20:28:17 GMT; Domain=localhost; Path=/; Secure; HttpOnly; SameSite=Lax
StringBuffer sb = new StringBuffer();
sb.append(name + "=" + value);
// Set-Cookie: name=value; Max-Age=100000; Expires=Tue, 05-Oct-2021 20:28:17 GMT; Domain=localhost; Path=/; Secure; HttpOnly; SameSite=Lax
StringBuilder sb = new StringBuilder();
sb.append(name).append("=").append(value);
if(maxAge >= 0) {
sb.append("; Max-Age=" + maxAge);
sb.append("; Max-Age=").append(maxAge);
String expires;
if(maxAge == 0) {
expires = Instant.EPOCH.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME);
} else {
expires = OffsetDateTime.now().plusSeconds(maxAge).format(DateTimeFormatter.RFC_1123_DATE_TIME);
}
sb.append("; Expires=" + expires);
sb.append("; Expires=").append(expires);
}
if(!SaFoxUtil.isEmpty(domain)) {
sb.append("; Domain=" + domain);
sb.append("; Domain=").append(domain);
}
if(!SaFoxUtil.isEmpty(path)) {
sb.append("; Path=" + path);
sb.append("; Path=").append(path);
}
if(secure) {
sb.append("; Secure");
@@ -268,10 +268,10 @@ public class SaCookie {
sb.append("; HttpOnly");
}
if(!SaFoxUtil.isEmpty(sameSite)) {
sb.append("; SameSite=" + sameSite);
sb.append("; SameSite=").append(sameSite);
}
return sb.toString();
}
}
@@ -155,4 +155,15 @@ public interface SaErrorCode {
/** RSA 私钥解密异常 */
public static final int CODE_12119 = 12119;
// ------------
/** 参与参数签名的秘钥不可为空 */
public static final int CODE_12201 = 12201;
/** 给定的签名无效 */
public static final int CODE_12202 = 12202;
/** timestamp 超出允许的范围 */
public static final int CODE_12203 = 12203;
}
@@ -1,175 +0,0 @@
package cn.dev33.satoken.id;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.IdTokenInvalidException;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* <h1> 本类设计已过时,未来版本可能移除此类,请及时更换为 SaSameTemplate ,使用方式保持不变 </h1>
*
* Sa-Token-Id 身份凭证模块
* <p> 身份凭证的获取与校验,可用于微服务内部调用鉴权
* @author kong
*
*/
@Deprecated
public class SaIdTemplate {
/**
* 在 Request 上储存 Id-Token 时建议使用的key
*/
public static final String ID_TOKEN = "SA_ID_TOKEN";
// -------------------- 获取 & 校验
/**
* 获取当前Id-Token, 如果不存在,则立即创建并返回
* @return 当前token
*/
public String getToken() {
String currentToken = getTokenNh();
if(SaFoxUtil.isEmpty(currentToken)) {
// 注意这里的自刷新不能做到高并发可用
currentToken = refreshToken();
}
return currentToken;
}
/**
* 判断一个Id-Token是否有效
* @param token 要验证的token
* @return 这个token是否有效
*/
public boolean isValid(String token) {
// 1、 如果传入的token未空,立即返回false
if(SaFoxUtil.isEmpty(token)) {
return false;
}
// 2、 验证当前 Id-Token 及 Past-Id-Token
return token.equals(getToken()) || token.equals(getPastTokenNh());
}
/**
* 校验一个Id-Token是否有效 (如果无效则抛出异常)
* @param token 要验证的token
*/
public void checkToken(String token) {
if(isValid(token) == false) {
token = (token == null ? "" : token);
throw new IdTokenInvalidException("无效Id-Token" + token);
}
}
/**
* 校验当前Request提供的Id-Token是否有效 (如果无效则抛出异常)
*/
public void checkCurrentRequestToken() {
checkToken(SaHolder.getRequest().getHeader(ID_TOKEN));
}
/**
* 刷新一次Id-Token (注意集群环境中不要多个服务重复调用)
* @return 新Token
*/
public String refreshToken() {
// 1. 先将当前 Id-Token 写入到 Past-Id-Token 中
String idToken = getTokenNh();
if(SaFoxUtil.isEmpty(idToken) == false) {
savePastToken(idToken, getTokenTimeout());
}
// 2. 再刷新当前Id-Token
String newIdToken = createToken();
saveToken(newIdToken);
// 3. 返回新的 Id-Token
return newIdToken;
}
// ------------------------------ 保存Token
/**
* 保存Id-Token
* @param token token
*/
public void saveToken(String token) {
if(SaFoxUtil.isEmpty(token)) {
return;
}
SaManager.getSaTokenDao().set(splicingTokenSaveKey(), token, SaManager.getConfig().getIdTokenTimeout());
}
/**
* 保存Past-Id-Token
* @param token token
* @param timeout 有效期(单位:秒)
*/
public void savePastToken(String token, long timeout){
if(SaFoxUtil.isEmpty(token)) {
return;
}
SaManager.getSaTokenDao().set(splicingPastTokenSaveKey(), token, timeout);
}
// -------------------- 获取Token
/**
* 获取Id-Token,不做任何处理
* @return token
*/
public String getTokenNh() {
return SaManager.getSaTokenDao().get(splicingTokenSaveKey());
}
/**
* 获取Past-Id-Token,不做任何处理
* @return token
*/
public String getPastTokenNh() {
return SaManager.getSaTokenDao().get(splicingPastTokenSaveKey());
}
/**
* 获取Id-Token的剩余有效期 (单位:秒)
* @return token
*/
public long getTokenTimeout() {
return SaManager.getSaTokenDao().getTimeout(splicingTokenSaveKey());
}
// -------------------- 创建Token
/**
* 创建一个Id-Token
* @return Token
*/
public String createToken() {
return SaFoxUtil.getRandomString(64);
}
// -------------------- 拼接key
/**
* 拼接keyId-Token 的存储 key
* @return key
*/
public String splicingTokenSaveKey() {
return SaManager.getConfig().getTokenName() + ":var:id-token";
}
/**
* 拼接key:次级 Id-Token 的存储 key
* @return key
*/
public String splicingPastTokenSaveKey() {
return SaManager.getConfig().getTokenName() + ":var:past-id-token";
}
}
@@ -1,86 +0,0 @@
package cn.dev33.satoken.id;
/**
* <h1> 本类设计已过时,未来版本可能移除此类,请及时更换为 SaSameUtil ,使用方式保持不变 </h1>
*
* Sa-Token-Id 身份凭证模块-工具类
* @author kong
*
*/
@Deprecated
public class SaIdUtil {
private SaIdUtil(){}
/**
* 在 Request 上储存 Id-Token 时建议使用的key
*/
public static final String ID_TOKEN = SaIdTemplate.ID_TOKEN;
/**
* 底层 SaIdTemplate 对象
*/
public static SaIdTemplate saIdTemplate = new SaIdTemplate();
// -------------------- 获取 & 校验
/**
* 获取当前Id-Token, 如果不存在,则立即创建并返回
* @return 当前token
*/
public static String getToken() {
return saIdTemplate.getToken();
}
/**
* 判断一个Id-Token是否有效
* @param token 要验证的token
* @return 这个token是否有效
*/
public static boolean isValid(String token) {
return saIdTemplate.isValid(token);
}
/**
* 校验一个Id-Token是否有效 (如果无效则抛出异常)
* @param token 要验证的token
*/
public static void checkToken(String token) {
saIdTemplate.checkToken(token);
}
/**
* 校验当前Request提供的Id-Token是否有效 (如果无效则抛出异常)
*/
public static void checkCurrentRequestToken() {
saIdTemplate.checkCurrentRequestToken();
}
/**
* 刷新一次Id-Token (注意集群环境中不要多个服务重复调用)
* @return 新Token
*/
public static String refreshToken() {
return saIdTemplate.refreshToken();
}
// -------------------- 获取Token
/**
* 获取Id-Token,不做任何处理
* @return token
*/
public static String getTokenNh() {
return saIdTemplate.getTokenNh();
}
/**
* 获取Past-Id-Token,不做任何处理
* @return token
*/
public static String getPastTokenNh() {
return saIdTemplate.getPastTokenNh();
}
}
@@ -1,5 +1,6 @@
package cn.dev33.satoken.secure;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -27,7 +28,7 @@ import cn.dev33.satoken.exception.SaTokenException;
/**
* Sa-Token 常见加密算法工具类
*
*
* @author kong
*
*/
@@ -35,7 +36,7 @@ public class SaSecureUtil {
private SaSecureUtil() {
}
/**
* Base64编码
*/
@@ -45,11 +46,11 @@ public class SaSecureUtil {
* Base64解码
*/
private static Base64.Decoder decoder = Base64.getDecoder();
// ----------------------- 摘要加密 -----------------------
/**
* md5加密
* md5加密
* @param str 指定字符串
* @return 加密后的字符串
*/
@@ -75,8 +76,8 @@ public class SaSecureUtil {
}
/**
* sha1加密
*
* sha1加密
*
* @param str 指定字符串
* @return 加密后的字符串
*/
@@ -103,8 +104,8 @@ public class SaSecureUtil {
}
/**
* sha256加密
*
* sha256加密
*
* @param str 指定字符串
* @return 加密后的字符串
*/
@@ -112,8 +113,8 @@ public class SaSecureUtil {
try {
str = (str == null ? "" : str);
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str.getBytes("UTF-8"));
messageDigest.update(str.getBytes(StandardCharsets.UTF_8));
byte[] bytes = messageDigest.digest();
StringBuilder builder = new StringBuilder();
String temp;
@@ -124,7 +125,7 @@ public class SaSecureUtil {
}
builder.append(temp);
}
return builder.toString();
} catch (Exception e) {
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12113);
@@ -132,20 +133,20 @@ public class SaSecureUtil {
}
/**
* md5加盐加密: md5(md5(str) + md5(salt))
* md5加盐加密: md5(md5(str) + md5(salt))
* @param str 字符串
* @param salt 盐
* @param salt 盐
* @return 加密后的字符串
*/
public static String md5BySalt(String str, String salt) {
return md5(md5(str) + md5(salt));
}
// ----------------------- 对称加密 AES -----------------------
/**
* 默认密码算法
* 默认密码算法
*/
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
@@ -159,7 +160,7 @@ public class SaSecureUtil {
public static String aesEncrypt(String key, String text) {
try {
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
byte[] byteContent = text.getBytes("utf-8");
byte[] byteContent = text.getBytes(StandardCharsets.UTF_8);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));
byte[] result = cipher.doFinal(byteContent);
return encoder.encodeToString(result);
@@ -169,7 +170,7 @@ public class SaSecureUtil {
}
/**
* AES解密
* AES解密
* @param key 加密的密钥
* @param text 已加密的密文
* @return 返回解密后的数据
@@ -179,16 +180,16 @@ public class SaSecureUtil {
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));
byte[] result = cipher.doFinal(decoder.decode(text));
return new String(result, "utf-8");
return new String(result, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12115);
}
}
/**
* 生成加密秘钥
* 生成加密秘钥
* @param password 秘钥
* @return SecretKeySpec
* @return SecretKeySpec
* @throws NoSuchAlgorithmException
*/
private static SecretKeySpec getSecretKey(final String password) throws NoSuchAlgorithmException {
@@ -204,14 +205,14 @@ public class SaSecureUtil {
// ----------------------- 非对称加密 RSA -----------------------
private static final String ALGORITHM = "RSA";
private static final int KEY_SIZE = 1024;
// ---------- 5个常用方法
// ---------- 5个常用方法
/**
* 生成密钥对
* 生成密钥对
* @return Map对象 (private=私钥, public=公钥)
* @throws Exception 异常
*/
@@ -242,24 +243,24 @@ public class SaSecureUtil {
public static String rsaEncryptByPublic(String publicKeyString, String content) {
try {
// 获得公钥对象
PublicKey publicKey = getPublicKeyFromString(publicKeyString);
PublicKey publicKey = getPublicKeyFromString(publicKeyString);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 该密钥能够加密的最大字节长度
int splitLength = ((RSAPublicKey) publicKey).getModulus().bitLength() / 8 - 11;
byte[][] arrays = splitBytes(content.getBytes(), splitLength);
StringBuffer stringBuffer = new StringBuffer();
StringBuilder stringBuilder = new StringBuilder();
for (byte[] array : arrays) {
stringBuffer.append(bytesToHexString(cipher.doFinal(array)));
stringBuilder.append(bytesToHexString(cipher.doFinal(array)));
}
return stringBuffer.toString();
return stringBuilder.toString();
} catch (Exception e) {
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12116);
}
}
/**
/**
* RSA私钥加密
* @param privateKeyString 私钥
* @param content 内容
@@ -274,17 +275,17 @@ public class SaSecureUtil {
// 该密钥能够加密的最大字节长度
int splitLength = ((RSAPrivateKey) privateKey).getModulus().bitLength() / 8 - 11;
byte[][] arrays = splitBytes(content.getBytes(), splitLength);
StringBuffer stringBuffer = new StringBuffer();
StringBuilder stringBuilder = new StringBuilder();
for (byte[] array : arrays) {
stringBuffer.append(bytesToHexString(cipher.doFinal(array)));
stringBuilder.append(bytesToHexString(cipher.doFinal(array)));
}
return stringBuffer.toString();
return stringBuilder.toString();
} catch (Exception e) {
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12117);
}
}
/**
/**
* RSA公钥解密
* @param publicKeyString 公钥
* @param content 已加密内容
@@ -301,11 +302,11 @@ public class SaSecureUtil {
int splitLength = ((RSAPublicKey) publicKey).getModulus().bitLength() / 8;
byte[] contentBytes = hexStringToBytes(content);
byte[][] arrays = splitBytes(contentBytes, splitLength);
StringBuffer stringBuffer = new StringBuffer();
StringBuilder stringBuilder = new StringBuilder();
for (byte[] array : arrays) {
stringBuffer.append(new String(cipher.doFinal(array)));
stringBuilder.append(new String(cipher.doFinal(array)));
}
return stringBuffer.toString();
return stringBuilder.toString();
} catch (Exception e) {
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12118);
}
@@ -327,18 +328,18 @@ public class SaSecureUtil {
int splitLength = ((RSAPrivateKey) privateKey).getModulus().bitLength() / 8;
byte[] contentBytes = hexStringToBytes(content);
byte[][] arrays = splitBytes(contentBytes, splitLength);
StringBuffer stringBuffer = new StringBuffer();
StringBuilder stringBuilder = new StringBuilder();
for (byte[] array : arrays) {
stringBuffer.append(new String(cipher.doFinal(array)));
stringBuilder.append(new String(cipher.doFinal(array)));
}
return stringBuffer.toString();
return stringBuilder.toString();
} catch (Exception e) {
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12119);
}
}
// ---------- 获取*钥
// ---------- 获取*钥
/** 根据公钥字符串获取 公钥对象 */
private static PublicKey getPublicKeyFromString(String key)
@@ -352,9 +353,7 @@ public class SaSecureUtil {
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
return publicKey;
return keyFactory.generatePublic(x509KeySpec);
}
/** 根据私钥字符串获取 私钥对象 */
@@ -369,9 +368,7 @@ public class SaSecureUtil {
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(x509KeySpec);
return privateKey;
return keyFactory.generatePrivate(x509KeySpec);
}
@@ -435,5 +432,5 @@ public class SaSecureUtil {
return (byte) "0123456789ABCDEF".indexOf(c);
}
}
@@ -3,6 +3,8 @@ package cn.dev33.satoken.sign;
import java.util.Map;
import java.util.TreeMap;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.util.SaFoxUtil;
@@ -15,15 +17,11 @@ import cn.dev33.satoken.util.SaFoxUtil;
public interface SaSignTemplate {
/**
* 将所有参数连接成一个字符串
* 将所有参数连接成一个字符串(不排序),形如:b=28a=18c=3
* @param paramsMap 参数列表
* @return 字符串
* @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();
@@ -43,6 +41,21 @@ public interface SaSignTemplate {
return sb.toString();
}
/**
* 将所有参数按照字典顺序连接成一个字符串,形如:a=18b=28c=3
* @param paramsMap 参数列表
* @return 拼接出的参数字符串
*/
public default String joinParamsDictSort(Map<String, Object> paramsMap) {
// 保证字段按照字典顺序排列
if(paramsMap instanceof TreeMap == false) {
paramsMap = new TreeMap<>(paramsMap);
}
// 拼接
return joinParams(paramsMap);
}
/**
* 创建签名:md5(paramsStr + keyStr)
* @param paramsMap 参数列表
@@ -50,10 +63,85 @@ public interface SaSignTemplate {
* @return 签名
*/
public default String createSign(Map<String, Object> paramsMap, String key) {
String paramsStr = joinParams(paramsMap);
SaTokenException.throwByNull(key, "参与参数签名的秘钥不可为空", SaErrorCode.CODE_12201);
String paramsStr = joinParamsDictSort(paramsMap);
String fullStr = paramsStr + "&key=" + key;
return SaSecureUtil.md5(fullStr);
}
/**
* 判断:给定的参数 + 秘钥 生成的签名是否为有效签名
* @param paramsMap 参数列表
* @param key 秘钥
* @param sign 待验证的签名
* @return 签名是否有效
*/
public default boolean isValidSign(Map<String, Object> paramsMap, String key, String sign) {
String theSign = createSign(paramsMap, key);
return theSign.equals(sign);
}
/**
* 校验:给定的参数 + 秘钥 生成的签名是否为有效签名,如果签名无效则抛出异常
* @param paramsMap 参数列表
* @param key 秘钥
* @param sign 待验证的签名
*/
public default void checkSign(Map<String, Object> paramsMap, String key, String sign) {
if(isValidSign(paramsMap, key, sign) == false) {
throw new SaTokenException("无效签名:" + sign).setCode(SaErrorCode.CODE_12202);
}
}
/**
* 给 paramsMap 追加 timestamp、nonce、sign 三个参数
* @param paramsMap 参数列表
* @param key 秘钥
* @return 加工后的参数列表
*/
public default Map<String, Object> addSignParams(Map<String, Object> paramsMap, String key) {
paramsMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
paramsMap.put("nonce", SaFoxUtil.getRandomString(32));
paramsMap.put("sign", createSign(paramsMap, key));
return paramsMap;
}
/**
* 给 paramsMap 追加 timestamp、nonce、sign 三个参数,并转换为参数字符串,形如:
* <code>data=xxx8nonce=xxx8timestamp=xxx8sign=xxx</code>
* @param paramsMap 参数列表
* @param key 秘钥
* @return 加工后的参数列表 转化为的参数字符串
*/
public default String addSignParamsToString(Map<String, Object> paramsMap, String key) {
// 追加参数
paramsMap = addSignParams(paramsMap, key);
// .
return joinParams(paramsMap);
}
/**
* 判断:指定时间戳与当前时间戳的差距是否在允许的范围内
* @param timestamp 待校验的时间戳
* @param allowDisparity 允许的最大时间差(单位:ms),-1 代表不限制
* @return 是否在允许的范围内
*/
public default boolean isValidTimestamp(long timestamp, long allowDisparity) {
long disparity = Math.abs(System.currentTimeMillis() - timestamp);
return allowDisparity == -1 || disparity <= allowDisparity;
}
/**
* 校验:指定时间戳与当前时间戳的差距是否在允许的范围内,如果超出则抛出异常
* @param timestamp 待校验的时间戳
* @param allowDisparity 允许的最大时间差(单位:ms),-1 代表不限制
*/
public default void checkTimestamp(long timestamp, long allowDisparity) {
if(isValidTimestamp(timestamp, allowDisparity) == false) {
throw new SaTokenException("timestamp 超出允许的范围:" + timestamp).setCode(SaErrorCode.CODE_12203);
}
}
}
@@ -412,14 +412,10 @@ public class StpLogic {
if(isConcurrent) {
// 全局配置是否允许复用旧 Token
if(getConfigOfIsShare()) {
// 为确保 jwt-simple 模式的 token Extra 数据生成不受旧token影响,这里必须确保 is-share 配置项在 ExtraData 为空时才可以生效
// 即:在 login 时提供了 Extra 数据后,即使配置了 is-share=true 也不能复用旧 Token,必须创建新 Token
if(loginModel.isSetExtraData() == false) {
String tokenValue = getTokenValueByLoginId(id, loginModel.getDeviceOrDefault());
// 复用成功的话就直接返回,否则还是要继续新建Token
if(SaFoxUtil.isNotEmpty(tokenValue)) {
return tokenValue;
}
String tokenValue = getTokenValueByLoginId(id, loginModel.getDeviceOrDefault());
// 复用成功的话就直接返回,否则还是要继续新建Token
if(SaFoxUtil.isNotEmpty(tokenValue)) {
return tokenValue;
}
}
}
@@ -506,13 +502,9 @@ public class StpLogic {
}
}
List<TokenSign> list = session.tokenSignListCopyByDevice(device);
// 遍历操作
for (int i = 0; i < list.size(); i++) {
// 只操作前n条
if(i >= list.size() - maxLoginCount) {
continue;
}
// 清理: token签名、token最后活跃时间
// 遍历操作,只操作前n条
for (int i = 0; i < list.size() - maxLoginCount; i++) {
// 清理: token签名、token最后活跃时间
String tokenValue = list.get(i).getValue();
session.removeTokenSign(tokenValue);
clearLastActivity(tokenValue);
@@ -3,6 +3,7 @@ package cn.dev33.satoken.temp;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaTokenConsts;
/**
* Sa-Token 临时令牌验证模块接口
@@ -12,18 +13,29 @@ import cn.dev33.satoken.util.SaFoxUtil;
public interface SaTempInterface {
/**
* 根据value创建一个token
* 为 指定值 创建一个临时 Token
* @param value 指定值
* @param timeout 有效期,单位:秒
* @param timeout 有效期,单位:秒-1代表永久有效
* @return 生成的token
*/
public default String createToken(Object value, long timeout) {
return createToken(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, value, timeout);
}
/**
* 为 指定服务 指定值 创建一个 Token
* @param service 服务标识
* @param value 指定值
* @param timeout 有效期,单位:秒,-1代表永久有效
* @return 生成的token
*/
public default String createToken(String service, Object value, long timeout) {
// 生成 token
String token = SaStrategy.me.createToken.apply(null, null);
// 持久化映射关系
String key = splicingKeyTempToken(token);
String key = splicingKeyTempToken(service, token);
SaManager.getSaTokenDao().setObject(key, value, timeout);
// 返回
@@ -31,53 +43,96 @@ public interface SaTempInterface {
}
/**
* 解析token获取value
* @param token 指定token
* @return See Note
* 解析 Token 获取 value
* @param token 指定 Token
* @return /
*/
public default Object parseToken(String token) {
String key = splicingKeyTempToken(token);
return parseToken(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, token);
}
/**
* 解析 Token 获取 value
* @param service 服务标识
* @param token 指定 Token
* @return /
*/
public default Object parseToken(String service, String token) {
String key = splicingKeyTempToken(service, token);
return SaManager.getSaTokenDao().getObject(key);
}
/**
* 解析token获取value,并转换为指定类型
* @param token 指定token
* 解析 Token 获取 value,并转换为指定类型
* @param token 指定 Token
* @param cs 指定类型
* @param <T> 默认值的类型
* @return See Note
* @return /
*/
public default<T> T parseToken(String token, Class<T> cs) {
return SaFoxUtil.getValueByType(parseToken(token), cs);
return parseToken(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, token, cs);
}
/**
* 解析 Token 获取 value,并转换为指定类型
* @param service 服务标识
* @param token 指定 Token
* @param cs 指定类型
* @param <T> 默认值的类型
* @return /
*/
public default<T> T parseToken(String service, String token, Class<T> cs) {
return SaFoxUtil.getValueByType(parseToken(service, token), cs);
}
/**
* 获取指定 token 的剩余有效期,单位:秒
* 获取指定 Token 的剩余有效期,单位:秒
* <p> 返回值 -1 代表永久,-2 代表token无效
* @param token see note
* @return see note
* @param token 指定 Token
* @return /
*/
public default long getTimeout(String token) {
String key = splicingKeyTempToken(token);
return getTimeout(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, token);
}
/**
* 获取指定服务指定 Token 的剩余有效期,单位:秒
* <p> 返回值 -1 代表永久,-2 代表token无效
* @param service 服务标识
* @param token 指定 Token
* @return /
*/
public default long getTimeout(String service, String token) {
String key = splicingKeyTempToken(service, token);
return SaManager.getSaTokenDao().getObjectTimeout(key);
}
/**
* 删除一个 token
* @param token 指定token
* 删除一个 Token
* @param token 指定 Token
*/
public default void deleteToken(String token) {
String key = splicingKeyTempToken(token);
deleteToken(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, token);
}
/**
* 删除一个 Token
* @param service 服务标识
* @param token 指定 Token
*/
public default void deleteToken(String service, String token) {
String key = splicingKeyTempToken(service, token);
SaManager.getSaTokenDao().deleteObject(key);
}
/**
* 获取映射关系的持久化key
* @param service 服务标识
* @param token token值
* @return key
*/
public default String splicingKeyTempToken(String token) {
return SaManager.getConfig().getTokenName() + ":temp-token:" + token;
public default String splicingKeyTempToken(String service, String token) {
return SaManager.getConfig().getTokenName() + ":temp-token:" + service + ":" + token;
}
/**
@@ -11,53 +11,106 @@ public class SaTempUtil {
private SaTempUtil() {
}
/**
* 根据value创建一个token
* 为 指定值 创建一个临时 Token
* @param value 指定值
* @param timeout 有效期,单位:秒
* @param timeout 有效期,单位:秒-1代表永久有效
* @return 生成的token
*/
public static String createToken(Object value, long timeout) {
return SaManager.getSaTemp().createToken(value, timeout);
}
/**
* 解析token获取value
* @param token 指定token
* @return See Note
* 为 指定服务 指定值 创建一个 Token
* @param service 服务标识
* @param value 指定值
* @param timeout 有效期,单位:秒,-1代表永久有效
* @return 生成的token
*/
public static String createToken(String service, Object value, long timeout) {
return SaManager.getSaTemp().createToken(service, value, timeout);
}
/**
* 解析 Token 获取 value
* @param token 指定 Token
* @return /
*/
public static Object parseToken(String token) {
return SaManager.getSaTemp().parseToken(token);
}
/**
* 解析token获取value,并转换为指定类型
* @param token 指定token
* 解析 Token 获取 value
* @param service 服务标识
* @param token 指定 Token
* @return /
*/
public static Object parseToken(String service, String token) {
return SaManager.getSaTemp().parseToken(service, token);
}
/**
* 解析 Token 获取 value,并转换为指定类型
* @param token 指定 Token
* @param cs 指定类型
* @param <T> 默认值的类型
* @return See Note
* @return /
*/
public static<T> T parseToken(String token, Class<T> cs) {
return SaManager.getSaTemp().parseToken(token, cs);
}
/**
* 解析 Token 获取 value,并转换为指定类型
* @param service 服务标识
* @param token 指定 Token
* @param cs 指定类型
* @param <T> 默认值的类型
* @return /
*/
public static<T> T parseToken(String service, String token, Class<T> cs) {
return SaManager.getSaTemp().parseToken(service, token, cs);
}
/**
* 获取指定 token 的剩余有效期,单位:秒
* 获取指定 Token 的剩余有效期,单位:秒
* <p> 返回值 -1 代表永久,-2 代表token无效
* @param token see note
* @return see note
* @param token 指定 Token
* @return /
*/
public static long getTimeout(String token) {
return SaManager.getSaTemp().getTimeout(token);
}
/**
* 删除一个 token
* @param token 指定token
* 获取指定服务指定 Token 的剩余有效期,单位:秒
* <p> 返回值 -1 代表永久,-2 代表token无效
* @param service 服务标识
* @param token 指定 Token
* @return /
*/
public static long getTimeout(String service, String token) {
return SaManager.getSaTemp().getTimeout(service, token);
}
/**
* 删除一个 Token
* @param token 指定 Token
*/
public static void deleteToken(String token) {
SaManager.getSaTemp().deleteToken(token);
}
/**
* 删除一个 Token
* @param service 服务标识
* @param token 指定 Token
*/
public static void deleteToken(String service, String token) {
SaManager.getSaTemp().deleteToken(service, token);
}
}
@@ -22,8 +22,8 @@ import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.SaTokenException;
/**
* Sa-Token 内部工具类
*
* Sa-Token 内部工具类
*
* @author kong
*
*/
@@ -31,20 +31,20 @@ public class SaFoxUtil {
private SaFoxUtil() {
}
/**
* 打印 Sa-Token 版本字符画
*/
public static void printSaToken() {
String str = ""
+ "____ ____ ___ ____ _ _ ____ _ _ \r\n"
+ "____ ____ ___ ____ _ _ ____ _ _ \r\n"
+ "[__ |__| __ | | | |_/ |___ |\\ | \r\n"
+ "___] | | | |__| | \\_ |___ | \\| "
// + SaTokenConsts.VERSION_NO
// + "sa-token"
+ "___] | | | |__| | \\_ |___ | \\| "
// + SaTokenConsts.VERSION_NO
// + "sa-token"
// + "\r\n" + "DevDoc" + SaTokenConsts.DEV_DOC_URL // + "\r\n";
+ "\r\n" + SaTokenConsts.DEV_DOC_URL // + "\r\n";
+ " (" + SaTokenConsts.VERSION_NO + ")"
+ " (" + SaTokenConsts.VERSION_NO + ")"
// + "\r\n" + "GitHub" + SaTokenConsts.GITHUB_URL // + "\r\n";
;
System.out.println(str);
@@ -52,13 +52,13 @@ public class SaFoxUtil {
/**
* 生成指定长度的随机字符串
*
*
* @param length 字符串的长度
* @return 一个随机字符串
*/
public static String getRandomString(int length) {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
StringBuffer sb = new StringBuffer();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
int number = ThreadLocalRandom.current().nextInt(62);
sb.append(str.charAt(number));
@@ -68,7 +68,7 @@ public class SaFoxUtil {
/**
* 指定元素是否为null或者空字符串
* @param str 指定元素
* @param str 指定元素
* @return 是否为null或者空字符串
*/
public static boolean isEmpty(Object str) {
@@ -77,7 +77,7 @@ public class SaFoxUtil {
/**
* 指定元素是否不为 (null或者空字符串)
* @param str 指定元素
* @param str 指定元素
* @return 是否为null或者空字符串
*/
public static boolean isNotEmpty(Object str) {
@@ -95,18 +95,27 @@ public class SaFoxUtil {
}
/**
* 比较两个对象是否相等
* @param a 第一个对象
* @param b 第二个对象
* @return 两个对象是否相等
* 比较两个对象是否相等
* @param a 第一个对象
* @param b 第二个对象
* @return 两个对象是否相等
*/
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
/**
* 比较两个对象是否不相等
* @param a 第一个对象
* @param b 第二个对象
* @return 两个对象是否不相等
*/
public static boolean notEquals(Object a, Object b) {
return !equals(a, b);
}
/**
* 以当前时间戳和随机int数字拼接一个随机字符串
*
*
* @return 随机字符串
*/
public static String getMarking28() {
@@ -116,7 +125,7 @@ public class SaFoxUtil {
/**
* 将日期格式化 yyyy-MM-dd HH:mm:ss
* @param date 日期
* @return 格式化后的时间
* @return 格式化后的时间
*/
public static String formatDate(Date date){
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
@@ -141,17 +150,17 @@ public class SaFoxUtil {
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault());
return formatDate(zonedDateTime);
}
/**
* 从集合里查询数据
*
*
* @param dataList 数据集合
* @param prefix 前缀
* @param keyword 关键字
* @param start 起始位置 (-1代表查询所有)
* @param size 获取条数
* @param sortType 排序类型(true=正序,false=反序)
*
*
* @return 符合条件的新数据集合
*/
public static List<String> searchList(Collection<String> dataList, String prefix, String keyword, int start, int size, boolean sortType) {
@@ -176,34 +185,34 @@ public class SaFoxUtil {
/**
* 从集合里查询数据
*
*
* @param list 数据集合
* @param start 起始位置
* @param size 获取条数 (-1代表从start处一直取到末尾)
* @param size 获取条数 (-1代表从start处一直取到末尾)
* @param sortType 排序类型(true=正序,false=反序)
*
*
* @return 符合条件的新数据集合
*/
public static List<String> searchList(List<String> list, int start, int size, boolean sortType) {
// 如果是反序的话
// 如果是反序的话
if(sortType == false) {
Collections.reverse(list);
}
// start 至少为0
// start 至少为0
if (start < 0) {
start = 0;
}
// size为-1时,代表一直取到末尾,否则取到 start + size
// size为-1时,代表一直取到末尾,否则取到 start + size
int end;
if(size == -1) {
end = list.size();
} else {
end = start + size;
}
// 取出的数据放到新集合中
// 取出的数据放到新集合中
List<String> list2 = new ArrayList<String>();
for (int i = start; i < end; i++) {
// 如果已经取到list的末尾,则直接退出
// 如果已经取到list的末尾,则直接退出
if (i >= list.size()) {
return list2;
}
@@ -211,62 +220,62 @@ public class SaFoxUtil {
}
return list2;
}
/**
* 字符串模糊匹配
* <p>example:
* <p> user* user-add -- true
* <p> user* art-add -- false
* @param patt 表达式
* @param str 待匹配的字符串
* @return 是否可以匹配
* <p> user* user-add -- true
* <p> user* art-add -- false
* @param patt 表达式
* @param str 待匹配的字符串
* @return 是否可以匹配
*/
public static boolean vagueMatch(String patt, String str) {
// 两者均为 null 时,直接返回 true
// 两者均为 null 时,直接返回 true
if(patt == null && str == null) {
return true;
}
// 两者其一为 null 时,直接返回 false
// 两者其一为 null 时,直接返回 false
if(patt == null || str == null) {
return false;
}
// 如果表达式不带有*号,则只需简单equals即可 (这样可以使速度提升200倍左右)
// 如果表达式不带有*号,则只需简单equals即可 (这样可以使速度提升200倍左右)
if(patt.indexOf("*") == -1) {
return patt.equals(str);
}
// 正则匹配
// 正则匹配
return Pattern.matches(patt.replaceAll("\\*", ".*"), str);
}
/**
* 判断类型是否为8大包装类型
* @param cs /
* @return /
* 判断类型是否为8大包装类型
* @param cs /
* @return /
*/
public static boolean isWrapperType(Class<?> cs) {
return cs == Integer.class || cs == Short.class || cs == Long.class || cs == Byte.class
|| cs == Float.class || cs == Double.class || cs == Boolean.class || cs == Character.class;
}
/**
* 判断类型是否为基础类型:8大基本数据类型、8大包装类、String
* @param cs /
* @return /
* 判断类型是否为基础类型:8大基本数据类型、8大包装类、String
* @param cs /
* @return /
*/
public static boolean isBasicType(Class<?> cs) {
return cs.isPrimitive() || isWrapperType(cs) || cs == String.class;
}
/**
* 将指定值转化为指定类型
* @param <T> 泛型
* @param obj 值
* @param cs 类型
* @return 转换后的值
* @return 转换后的值
*/
@SuppressWarnings("unchecked")
public static <T> T getValueByType(Object obj, Class<T> cs) {
// 如果 obj 为 null 或者本来就是 cs 类型
// 如果 obj 为 null 或者本来就是 cs 类型
if(obj == null || obj.getClass().equals(cs)) {
return (T)obj;
}
@@ -296,15 +305,15 @@ public class SaFoxUtil {
}
return (T)obj3;
}
/**
* 在url上拼接上kv参数并返回
* 在url上拼接上kv参数并返回
* @param url url
* @param parameStr 参数, 例如 id=1001
* @return 拼接后的url字符串
* @return 拼接后的url字符串
*/
public static String joinParam(String url, String parameStr) {
// 如果参数为空, 直接返回
// 如果参数为空, 直接返回
if(parameStr == null || parameStr.length() == 0) {
return url;
}
@@ -330,19 +339,19 @@ public class SaFoxUtil {
return url + parameStr;
}
}
// 正常情况下, 代码不可能执行到此
// 正常情况下, 代码不可能执行到此
return url;
}
/**
* 在url上拼接上kv参数并返回
* 在url上拼接上kv参数并返回
* @param url url
* @param key 参数名称
* @param value 参数值
* @return 拼接后的url字符串
* @param value 参数值
* @return 拼接后的url字符串
*/
public static String joinParam(String url, String key, Object value) {
// 如果url或者key为空, 直接返回
// 如果url或者key为空, 直接返回
if(isEmpty(url) || isEmpty(key)) {
return url;
}
@@ -350,13 +359,13 @@ public class SaFoxUtil {
}
/**
* 在url上拼接锚参数
* 在url上拼接锚参数
* @param url url
* @param parameStr 参数, 例如 id=1001
* @return 拼接后的url字符串
* @return 拼接后的url字符串
*/
public static String joinSharpParam(String url, String parameStr) {
// 如果参数为空, 直接返回
// 如果参数为空, 直接返回
if(parameStr == null || parameStr.length() == 0) {
return url;
}
@@ -382,19 +391,19 @@ public class SaFoxUtil {
return url + parameStr;
}
}
// 正常情况下, 代码不可能执行到此
// 正常情况下, 代码不可能执行到此
return url;
}
/**
* 在url上拼接锚参数
* 在url上拼接锚参数
* @param url url
* @param key 参数名称
* @param value 参数值
* @return 拼接后的url字符串
* @param value 参数值
* @return 拼接后的url字符串
*/
public static String joinSharpParam(String url, String key, Object value) {
// 如果url或者key为空, 直接返回
// 如果url或者key为空, 直接返回
if(isEmpty(url) || isEmpty(key)) {
return url;
}
@@ -402,31 +411,31 @@ public class SaFoxUtil {
}
/**
* 拼接两个url
* 拼接两个url
* <p> 例如:url1=http://domain.cnurl2=/sso/auth,则返回:http://domain.cn/sso/auth </p>
*
* @param url1 第一个url
* @param url2 第二个url
* @return 拼接完成的url
*
* @param url1 第一个url
* @param url2 第二个url
* @return 拼接完成的url
*/
public static String spliceTwoUrl(String url1, String url2) {
// q1、任意一个为空,则直接返回另一个
// q1、任意一个为空,则直接返回另一个
if(url1 == null) {
return url2;
}
if(url2 == null) {
return url1;
}
// q2、如果 url2 以 http 开头,将其视为一个完整地址
// q2、如果 url2 以 http 开头,将其视为一个完整地址
if(url2.startsWith("http")) {
return url2;
}
// q3、将两个地址拼接在一起
// q3、将两个地址拼接在一起
return url1 + url2;
}
/**
* 将数组的所有元素使用逗号拼接在一起
* @param arr 数组
@@ -445,16 +454,16 @@ public class SaFoxUtil {
}
return str;
}
/**
* 验证URL的正则表达式
* 验证URL的正则表达式
*/
public static final String URL_REGEX = "(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]";
public static final String URL_REGEX = "(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]";
/**
* 使用正则表达式判断一个字符串是否为URL
* @param str 字符串
* @return 拼接后的url字符串
* @param str 字符串
* @return 拼接后的url字符串
*/
public static boolean isUrl(String str) {
if(isEmpty(str)) {
@@ -462,11 +471,11 @@ public class SaFoxUtil {
}
return str.toLowerCase().matches(URL_REGEX);
}
/**
* URL编码
* @param url see note
* @return see note
* URL编码
* @param url see note
* @return see note
*/
public static String encodeUrl(String url) {
try {
@@ -477,9 +486,9 @@ public class SaFoxUtil {
}
/**
* URL解码
* @param url see note
* @return see note
* URL解码
* @param url see note
* @return see note
*/
public static String decoderUrl(String url) {
try {
@@ -488,11 +497,11 @@ public class SaFoxUtil {
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12104);
}
}
/**
* 将指定字符串按照逗号分隔符转化为字符串集合
* 将指定字符串按照逗号分隔符转化为字符串集合
* @param str 字符串
* @return 分割后的字符串集合
* @return 分割后的字符串集合
*/
public static List<String> convertStringToList(String str) {
List<String> list = new ArrayList<String>();
@@ -510,9 +519,9 @@ public class SaFoxUtil {
}
/**
* 将指定集合按照逗号连接成一个字符串
* @param list 集合
* @return 字符串
* 将指定集合按照逗号连接成一个字符串
* @param list 集合
* @return 字符串
*/
public static String convertListToString(List<?> list) {
if(list == null || list.size() == 0) {
@@ -527,11 +536,11 @@ public class SaFoxUtil {
}
return str;
}
/**
* String 转 Array,按照逗号切割
* @param str 字符串
* @return 数组
* String 转 Array,按照逗号切割
* @param str 字符串
* @return 数组
*/
public static String[] convertStringToArray(String str) {
List<String> list = convertStringToList(str);
@@ -539,9 +548,9 @@ public class SaFoxUtil {
}
/**
* Array 转 String,按照逗号连接
* @param arr 数组
* @return 字符串
* Array 转 String,按照逗号连接
* @param arr 数组
* @return 字符串
*/
public static String convertArrayToString(String[] arr) {
if(arr == null || arr.length == 0) {
@@ -549,20 +558,20 @@ public class SaFoxUtil {
}
return String.join(",", arr);
}
/**
* 返回一个空集合
* @param <T> 集合类型
* @return 空集合
* 返回一个空集合
* @param <T> 集合类型
* @return 空集合
*/
public static <T>List<T> emptyList() {
return new ArrayList<>();
}
/**
* String数组转集合
* @param strs String数组
* @return 集合
* String数组转集合
* @param strs String数组
* @return 集合
*/
public static List<String> toList(String... strs) {
List<String> list = new ArrayList<>();
@@ -571,9 +580,9 @@ public class SaFoxUtil {
}
return list;
}
public static List<String> logLevelList = Arrays.asList("", "trace", "debug", "info", "warn", "error", "fatal");
/**
* 将日志等级从 String 格式转化为 int 格式
* @param level /
@@ -598,5 +607,5 @@ public class SaFoxUtil {
}
return logLevelList.get(level);
}
}
@@ -15,7 +15,7 @@ public class SaTokenConsts {
/**
* Sa-Token 当前版本号
*/
public static final String VERSION_NO = "v1.33.0";
public static final String VERSION_NO = "v1.34.0";
/**
* Sa-Token 开源地址 Gitee
@@ -95,6 +95,11 @@ public class SaTokenConsts {
*/
public static final String DEFAULT_SAFE_AUTH_SERVICE = "important";
/**
* 常量key标记: 临时 Token 认证模块,默认的业务类型
*/
public static final String DEFAULT_TEMP_TOKEN_SERVICE = "record";
// =================== token-style 相关 ===================
@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath
@@ -0,0 +1,71 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-alone-redis-cluster</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.14</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.34.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 权限认证, 在线文档:https://sa-token.cc/ -->
<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>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Sa-Token插件:权限缓存与业务缓存分离 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-alone-redis</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,21 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaManager;
/**
* Sa-Token整合SpringBoot 示例
* @author kong
*
*/
@SpringBootApplication
public class SaTokenAloneRedisClusterApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenAloneRedisClusterApplication.class, args);
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}
@@ -0,0 +1,162 @@
package com.pj.test;
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,39 @@
package com.pj.test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
/**
* 测试专用Controller
* @author kong
*
*/
@RestController
@RequestMapping("/test/")
public class TestController {
@Autowired
StringRedisTemplate stringRedisTemplate;
// 测试Sa-Token缓存, 浏览器访问: http://localhost:8081/test/login
@RequestMapping("login")
public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
System.out.println("--------------- 测试Sa-Token缓存");
StpUtil.login(id);
return AjaxJson.getSuccess();
}
// 测试业务缓存 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public AjaxJson test() {
System.out.println("--------------- 测试业务缓存");
stringRedisTemplate.opsForValue().set("hello", "Hello World");
return AjaxJson.getSuccess();
}
}
@@ -0,0 +1,66 @@
# 端口
server:
port: 8081
# Sa-Token配置
sa-token:
# Token名称 (同时也是cookie名称)
token-name: satoken
# Token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# Token风格
token-style: uuid
# 配置Sa-Token单独使用的Redis连接
alone-redis:
# 普通集群
pattern: cluster
# Redis服务器连接用户名(默认为空)
username:
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 10s
cluster:
# Redis集群服务器节点地址
nodes: 127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002
# 最大重定向次数
maxRedirects: 2
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
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,68 @@
# 端口
server:
port: 8081
# Sa-Token配置
sa-token:
# Token名称 (同时也是cookie名称)
token-name: satoken
# Token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# Token风格
token-style: uuid
# 配置Sa-Token单独使用的Redis连接
alone-redis:
# 哨兵模式
pattern: sentinel
# Redis数据库索引(默认为0
database: 2
# Redis服务器连接用户名(默认为空)
username:
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 10s
sentinel:
#哨兵的名字
master: master_name
# Redis集群服务器节点地址
nodes: 127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
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
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
@@ -11,7 +11,9 @@ sa-token:
# Token风格
token-style: uuid
# 配置Sa-Token单独使用的Redis连接
alone-redis:
alone-redis:
# Redis模式(默认单体)
# pattern: single
# Redis数据库索引(默认为0
database: 2
# Redis服务器地址
+2 -2
View File
@@ -10,14 +10,14 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
@@ -9,14 +9,15 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<!--<version>2.3.3.RELEASE</version>-->
<version>2.5.14</version>
</parent>
<!-- 指定一些属性 -->
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
@@ -9,14 +9,15 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<!--<version>2.3.3.RELEASE</version>-->
<version>2.5.14</version>
</parent>
<!-- 指定一些属性 -->
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
+1 -1
View File
@@ -27,7 +27,7 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<lombok.version>1.18.10</lombok.version>
<sa-token.version>1.33.0</sa-token.version>
<sa-token.version>1.34.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.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
@@ -9,7 +9,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<version>2.5.14</version>
</parent>
<!-- 指定一些属性 -->
@@ -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.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
@@ -9,7 +9,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<version>2.5.14</version>
</parent>
<!-- 指定一些属性 -->
@@ -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.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
@@ -18,7 +18,7 @@ public class SaOAuth2TemplateImpl extends SaOAuth2Template {
// 此为模拟数据,真实环境需要从数据库查询
if("1001".equals(clientId)) {
return new SaClientModel()
.setClientId("10001")
.setClientId("1001")
.setClientSecret("aaaa-bbbb-cccc-dddd-eeee")
.setAllowUrl("*")
.setContractScope("userinfo")
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<!-- <version>2.6.0</version> -->
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
+3 -1
View File
@@ -9,8 +9,10 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
<solon.version>1.10.13</solon.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
@@ -10,14 +10,14 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
@@ -17,13 +17,13 @@ sa-token:
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
is-log: true
spring:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath
@@ -0,0 +1,65 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-springboot-redisson</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.14</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.34.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 权限认证, 在线文档:https://sa-token.cc/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token整合 Redisson (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redisson-jackson</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- 无需提供Redis连接池 Redisson使用Netty管理 -->
<!-- <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.DisableServiceException;
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 DisableServiceException) { // 如果是被封禁异常
DisableServiceException ee = (DisableServiceException) e;
aj = AjaxJson.getNotJur("当前账号 " + ee.getService() + " 服务已被封禁 (level=" + ee.getLevel() + ")" + ee.getDisableTime() + "秒后解封");
}
else { // 普通异常, 输出:500 + 异常信息
aj = AjaxJson.getError(e.getMessage());
}
// 返回给前端
return aj;
}
}
@@ -0,0 +1,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,50 @@
package com.pj.redisson;
import org.redisson.config.SingleServerConfig;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* redisson 配置
*
* @author 疯狂的狮子Li
*/
@Configuration
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonConfig {
@Autowired
private RedissonProperties redissonProperties;
/**
* 自定义Redisson配置注入器 被RedissonAutoConfiguration调用执行
* 具体参考 {@link org.redisson.spring.starter.RedissonAutoConfiguration}
* <p/>
* 使用自定义配置类手动注入配置数据
* 也可根据redisson官网使用properties文件配置
*/
@Bean
public RedissonAutoConfigurationCustomizer redissonCustomizer() {
return config -> {
config.setThreads(redissonProperties.getThreads());
config.setNettyThreads(redissonProperties.getNettyThreads());
SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
if (singleServerConfig != null) {
// 使用单机模式
config.useSingleServer()
.setTimeout(singleServerConfig.getTimeout())
.setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
}
};
}
}
@@ -0,0 +1,56 @@
package com.pj.redisson;
import org.redisson.config.SingleServerConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.stereotype.Component;
/**
* Redisson 配置属性
*
* @author 疯狂的狮子Li
*/
@Component
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {
/**
* 线程池数量,默认值 = 当前处理核数量 * 2
*/
private int threads;
/**
* Netty线程池数量,默认值 = 当前处理核数量 * 2
*/
private int nettyThreads;
/**
* 单机服务配置
*/
@NestedConfigurationProperty
private SingleServerConfig singleServerConfig;
public int getThreads() {
return threads;
}
public void setThreads(int threads) {
this.threads = threads;
}
public int getNettyThreads() {
return nettyThreads;
}
public void setNettyThreads(int nettyThreads) {
this.nettyThreads = nettyThreads;
}
public SingleServerConfig getSingleServerConfig() {
return singleServerConfig;
}
public void setSingleServerConfig(SingleServerConfig singleServerConfig) {
this.singleServerConfig = singleServerConfig;
}
}
@@ -0,0 +1,70 @@
package com.pj.satoken;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.util.SaResult;
/**
* [Sa-Token 权限认证] 配置类
* @author kong
*
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/**
* 注册 Sa-Token 拦截器打开注解鉴权功能
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器打开注解鉴权功能
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
/**
* 注册 [Sa-Token 全局过滤器]
*/
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
// 指定 [拦截路由] 与 [放行路由]
.addInclude("/**")// .addExclude("/favicon.ico")
// 认证函数: 每次请求执行
.setAuth(obj -> {
// System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath());
})
// 异常处理函数:每次认证函数发生异常时执行此函数
.setError(e -> {
System.out.println("---------- sa全局异常 ");
return SaResult.error(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")
;
})
;
}
}
@@ -0,0 +1,44 @@
package com.pj.satoken;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.stp.StpInterface;
/**
* 自定义权限验证接口扩展
*/
@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
List<String> list = new ArrayList<String>();
list.add("101");
list.add("user-add");
list.add("user-delete");
list.add("user-update");
list.add("user-get");
list.add("article-get");
return list;
}
/**
* 返回一个账号所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
List<String> list = new ArrayList<String>();
list.add("admin");
list.add("super-admin");
return list;
}
}
@@ -0,0 +1,80 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.annotation.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.Ttime;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 压力测试
* @author kong
*
*/
@RestController
@RequestMapping("/s-test/")
public class StressTestController {
// 测试 浏览器访问: http://localhost:8081/s-test/login
// 测试前,请先将 is-read-cookie 配置为 false
@RequestMapping("login")
public SaResult login() {
// StpUtil.getTokenSession().logout();
// StpUtil.logoutByLoginId(10001);
int count = 10; // 循环多少轮
int loginCount = 10000; // 每轮循环多少次
// 循环10次 取平均时间
List<Double> list = new ArrayList<>();
for (int i = 1; i <= count; i++) {
System.out.println("\n---------------------第" + i + "轮---------------------");
Ttime t = new Ttime().start();
// 每次登录的次数
for (int j = 1; j <= loginCount; j++) {
StpUtil.login("1000" + j, "PC-" + j);
if(j % 1000 == 0) {
System.out.println("已登录:" + j);
}
}
t.end();
list.add((t.returnMs() + 0.0) / 1000);
System.out.println("" + i + "" + "用时:" + t.toString());
}
// System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size());
System.out.println("\n---------------------测试结果---------------------");
System.out.println(list.size() + "次测试: " + list);
double ss = 0;
for (int i = 0; i < list.size(); i++) {
ss += list.get(i);
}
System.out.println("平均用时: " + ss / list.size());
return SaResult.ok();
}
}
@@ -0,0 +1,30 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.util.SaResult;
/**
* 测试专用Controller
* @author kong
*
*/
@RestController
@RequestMapping("/test/")
public class TestController {
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public SaResult test() {
System.out.println("------------进来了");
return SaResult.ok();
}
// 测试 浏览器访问: http://localhost:8081/test/test2
@RequestMapping("test2")
public SaResult test2() {
return SaResult.ok();
}
}
@@ -0,0 +1,162 @@
package com.pj.util;
import java.io.Serializable;
import java.util.List;
/**
* ajax请求返回Json格式数据的封装
*/
public class AjaxJson implements Serializable{
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200; // 成功状态码
public static final int CODE_ERROR = 500; // 错误状态码
public static final int CODE_WARNING = 501; // 警告状态码
public static final int CODE_NOT_JUR = 403; // 无权限状态码
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
public int code; // 状态码
public String msg; // 描述信息
public Object data; // 携带对象
public Long dataCount; // 数据总数,用于分页
/**
* 返回code
* @return
*/
public int getCode() {
return this.code;
}
/**
* 给msg赋值,连缀风格
*/
public AjaxJson setMsg(String msg) {
this.msg = msg;
return this;
}
public String getMsg() {
return this.msg;
}
/**
* 给data赋值,连缀风格
*/
public AjaxJson setData(Object data) {
this.data = data;
return this;
}
/**
* 将data还原为指定类型并返回
*/
@SuppressWarnings("unchecked")
public <T> T getData(Class<T> cs) {
return (T) data;
}
// ============================ 构建 ==================================
public AjaxJson(int code, String msg, Object data, Long dataCount) {
this.code = code;
this.msg = msg;
this.data = data;
this.dataCount = dataCount;
}
// 返回成功
public static AjaxJson getSuccess() {
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
}
public static AjaxJson getSuccess(String msg) {
return new AjaxJson(CODE_SUCCESS, msg, null, null);
}
public static AjaxJson getSuccess(String msg, Object data) {
return new AjaxJson(CODE_SUCCESS, msg, data, null);
}
public static AjaxJson getSuccessData(Object data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
public static AjaxJson getSuccessArray(Object... data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
// 返回失败
public static AjaxJson getError() {
return new AjaxJson(CODE_ERROR, "error", null, null);
}
public static AjaxJson getError(String msg) {
return new AjaxJson(CODE_ERROR, msg, null, null);
}
// 返回警告
public static AjaxJson getWarning() {
return new AjaxJson(CODE_ERROR, "warning", null, null);
}
public static AjaxJson getWarning(String msg) {
return new AjaxJson(CODE_WARNING, msg, null, null);
}
// 返回未登录
public static AjaxJson getNotLogin() {
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
}
// 返回没有权限的
public static AjaxJson getNotJur(String msg) {
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
}
// 返回一个自定义状态码的
public static AjaxJson get(int code, String msg){
return new AjaxJson(code, msg, null, null);
}
// 返回分页和数据的
public static AjaxJson getPageData(Long dataCount, Object data){
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
}
// 返回,根据受影响行数的(大于0=ok,小于0=error)
public static AjaxJson getByLine(int line){
if(line > 0){
return getSuccess("ok", line);
}
return getError("error").setData(line);
}
// 返回,根据布尔值来确定最终结果的 (true=okfalse=error)
public static AjaxJson getByBoolean(boolean b){
return b ? getSuccess("ok") : getError("error");
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@SuppressWarnings("rawtypes")
@Override
public String toString() {
String data_string = null;
if(data == null){
} else if(data instanceof List){
data_string = "List(length=" + ((List)data).size() + ")";
} else {
data_string = data.toString();
}
return "{"
+ "\"code\": " + this.getCode()
+ ", \"msg\": \"" + this.getMsg() + "\""
+ ", \"data\": " + data_string
+ ", \"dataCount\": " + dataCount
+ "}";
}
}
@@ -0,0 +1,63 @@
package com.pj.util;
/**
* 用于测试用时
* @author 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,58 @@
# 端口
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: true
spring:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 1
# Redis服务器地址
host: localhost
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码 为空需注释掉
# password:
# 连接超时时间
timeout: 10s
redisson:
# 线程池数量
threads: 8
# Netty线程池数量
nettyThreads: 32
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: test
# 最小空闲连接数
connectionMinimumIdleSize: 8
# 连接池大小
connectionPoolSize: 32
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
retryAttempts: 3
# 命令重试发送时间间隔,单位:毫秒
retryInterval: 1500
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
@@ -10,14 +10,14 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath
@@ -0,0 +1,71 @@
<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-springboot3-redis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.1</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.34.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 权限认证, 在线文档:https://sa-token.cc/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-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 整合 SpringBoot3 示例,整合redis
* @author kong
*
*/
@SpringBootApplication
public class SaTokenSpringBoot3Application {
public static void main(String[] args) {
SpringApplication.run(SaTokenSpringBoot3Application.class, args);
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}
@@ -0,0 +1,56 @@
package com.pj.current;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.exception.DisableServiceException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 全局异常处理
*/
@RestControllerAdvice
public class GlobalException {
// 全局异常拦截(拦截项目中的所有异常)
@ExceptionHandler
public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
throws Exception {
// 打印堆栈,以供调试
System.out.println("全局异常---------------");
e.printStackTrace();
// 不同异常返回不同状态码
AjaxJson aj = null;
if (e instanceof NotLoginException) { // 如果是未登录异常
NotLoginException ee = (NotLoginException) e;
aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
}
else if(e instanceof NotRoleException) { // 如果是角色异常
NotRoleException ee = (NotRoleException) e;
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
}
else if(e instanceof NotPermissionException) { // 如果是权限异常
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
}
else if(e instanceof DisableServiceException) { // 如果是被封禁异常
DisableServiceException ee = (DisableServiceException) e;
aj = AjaxJson.getNotJur("当前账号 " + ee.getService() + " 服务已被封禁 (level=" + ee.getLevel() + ")" + ee.getDisableTime() + "秒后解封");
}
else { // 普通异常, 输出:500 + 异常信息
aj = AjaxJson.getError(e.getMessage());
}
// 返回给前端
return aj;
}
}
@@ -0,0 +1,26 @@
package com.pj.current;
import java.io.IOException;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.util.SaResult;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 处理 404
* @author 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,71 @@
package com.pj.satoken;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.util.SaResult;
/**
* [Sa-Token 权限认证] 配置类
* @author kong
*
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/**
* 注册 Sa-Token 拦截器打开注解鉴权功能
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器打开注解鉴权功能
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
/**
* 注册 [Sa-Token 全局过滤器]
*/
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
// 指定 [拦截路由] 与 [放行路由]
.addInclude("/**")// .addExclude("/favicon.ico")
// 认证函数: 每次请求执行
.setAuth(obj -> {
// System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath());
})
// 异常处理函数:每次认证函数发生异常时执行此函数
.setError(e -> {
System.out.println("---------- sa全局异常 ");
e.printStackTrace();
return SaResult.error(e.getMessage());
})
// 前置函数:在每次认证函数之前执行
.setBeforeAuth(r -> {
// ---------- 设置一些安全响应头 ----------
SaHolder.getResponse()
// 服务器名称
.setServer("sa-server")
// 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
.setHeader("X-Frame-Options", "SAMEORIGIN")
// 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
.setHeader("X-XSS-Protection", "1; mode=block")
// 禁用浏览器内容嗅探
.setHeader("X-Content-Type-Options", "nosniff")
;
})
;
}
}
@@ -0,0 +1,44 @@
package com.pj.satoken;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.stp.StpInterface;
/**
* 自定义权限验证接口扩展
*/
@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
List<String> list = new ArrayList<String>();
list.add("101");
list.add("user-add");
list.add("user-delete");
list.add("user-update");
list.add("user-get");
list.add("article-get");
return list;
}
/**
* 返回一个账号所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
List<String> list = new ArrayList<String>();
list.add("admin");
list.add("super-admin");
return list;
}
}
@@ -0,0 +1,80 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.annotation.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.Ttime;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 压力测试
* @author kong
*
*/
@RestController
@RequestMapping("/s-test/")
public class StressTestController {
// 测试 浏览器访问: http://localhost:8081/s-test/login
// 测试前,请先将 is-read-cookie 配置为 false
@RequestMapping("login")
public SaResult login() {
// StpUtil.getTokenSession().logout();
// StpUtil.logoutByLoginId(10001);
int count = 10; // 循环多少轮
int loginCount = 10000; // 每轮循环多少次
// 循环10次 取平均时间
List<Double> list = new ArrayList<>();
for (int i = 1; i <= count; i++) {
System.out.println("\n---------------------第" + i + "轮---------------------");
Ttime t = new Ttime().start();
// 每次登录的次数
for (int j = 1; j <= loginCount; j++) {
StpUtil.login("1000" + j, "PC-" + j);
if(j % 1000 == 0) {
System.out.println("已登录:" + j);
}
}
t.end();
list.add((t.returnMs() + 0.0) / 1000);
System.out.println("" + i + "" + "用时:" + t.toString());
}
// System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size());
System.out.println("\n---------------------测试结果---------------------");
System.out.println(list.size() + "次测试: " + list);
double ss = 0;
for (int i = 0; i < list.size(); i++) {
ss += list.get(i);
}
System.out.println("平均用时: " + ss / list.size());
return SaResult.ok();
}
}
@@ -0,0 +1,30 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.util.SaResult;
/**
* 测试专用Controller
* @author kong
*
*/
@RestController
@RequestMapping("/test/")
public class TestController {
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public SaResult test() {
System.out.println("------------进来了");
return SaResult.ok();
}
// 测试 浏览器访问: http://localhost:8081/test/test2
@RequestMapping("test2")
public SaResult test2() {
return SaResult.ok();
}
}
@@ -0,0 +1,162 @@
package com.pj.util;
import java.io.Serializable;
import java.util.List;
/**
* ajax请求返回Json格式数据的封装
*/
public class AjaxJson implements Serializable{
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200; // 成功状态码
public static final int CODE_ERROR = 500; // 错误状态码
public static final int CODE_WARNING = 501; // 警告状态码
public static final int CODE_NOT_JUR = 403; // 无权限状态码
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
public int code; // 状态码
public String msg; // 描述信息
public Object data; // 携带对象
public Long dataCount; // 数据总数,用于分页
/**
* 返回code
* @return
*/
public int getCode() {
return this.code;
}
/**
* 给msg赋值,连缀风格
*/
public AjaxJson setMsg(String msg) {
this.msg = msg;
return this;
}
public String getMsg() {
return this.msg;
}
/**
* 给data赋值,连缀风格
*/
public AjaxJson setData(Object data) {
this.data = data;
return this;
}
/**
* 将data还原为指定类型并返回
*/
@SuppressWarnings("unchecked")
public <T> T getData(Class<T> cs) {
return (T) data;
}
// ============================ 构建 ==================================
public AjaxJson(int code, String msg, Object data, Long dataCount) {
this.code = code;
this.msg = msg;
this.data = data;
this.dataCount = dataCount;
}
// 返回成功
public static AjaxJson getSuccess() {
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
}
public static AjaxJson getSuccess(String msg) {
return new AjaxJson(CODE_SUCCESS, msg, null, null);
}
public static AjaxJson getSuccess(String msg, Object data) {
return new AjaxJson(CODE_SUCCESS, msg, data, null);
}
public static AjaxJson getSuccessData(Object data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
public static AjaxJson getSuccessArray(Object... data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
// 返回失败
public static AjaxJson getError() {
return new AjaxJson(CODE_ERROR, "error", null, null);
}
public static AjaxJson getError(String msg) {
return new AjaxJson(CODE_ERROR, msg, null, null);
}
// 返回警告
public static AjaxJson getWarning() {
return new AjaxJson(CODE_ERROR, "warning", null, null);
}
public static AjaxJson getWarning(String msg) {
return new AjaxJson(CODE_WARNING, msg, null, null);
}
// 返回未登录
public static AjaxJson getNotLogin() {
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
}
// 返回没有权限的
public static AjaxJson getNotJur(String msg) {
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
}
// 返回一个自定义状态码的
public static AjaxJson get(int code, String msg){
return new AjaxJson(code, msg, null, null);
}
// 返回分页和数据的
public static AjaxJson getPageData(Long dataCount, Object data){
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
}
// 返回,根据受影响行数的(大于0=ok,小于0=error)
public static AjaxJson getByLine(int line){
if(line > 0){
return getSuccess("ok", line);
}
return getError("error").setData(line);
}
// 返回,根据布尔值来确定最终结果的 (true=okfalse=error)
public static AjaxJson getByBoolean(boolean b){
return b ? getSuccess("ok") : getError("error");
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@SuppressWarnings("rawtypes")
@Override
public String toString() {
String data_string = null;
if(data == null){
} else if(data instanceof List){
data_string = "List(length=" + ((List)data).size() + ")";
} else {
data_string = data.toString();
}
return "{"
+ "\"code\": " + this.getCode()
+ ", \"msg\": \"" + this.getMsg() + "\""
+ ", \"data\": " + data_string
+ ", \"dataCount\": " + dataCount
+ "}";
}
}
@@ -0,0 +1,63 @@
package com.pj.util;
/**
* 用于测试用时
* @author 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,50 @@
# 端口
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: true
spring:
data:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
File diff suppressed because it is too large Load Diff
@@ -14,9 +14,9 @@
"vue-router": "^3.6.5"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.4.6",
"@vue/cli-plugin-eslint": "~4.4.6",
"@vue/cli-service": "~4.4.6",
"@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-eslint": "~5.0.8",
"@vue/cli-service": "~5.0.8",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
@@ -42,7 +42,12 @@ sa.ajax = function(url, data, successFn) {
// ----------------------------------- 相关事件 -----------------------------------
// 检查当前是否已经登录,如果已登录则直接开始跳转,如果未登录则等待用户输入账号密码
sa.ajax("/sso/getRedirectUrl", {redirect: getParam('redirect', ''), mode: getParam('mode', '')}, function(res) {
var pData = {
client: getParam('client', ''),
redirect: getParam('redirect', ''),
mode: getParam('mode', '')
};
sa.ajax("/sso/getRedirectUrl", pData, function(res) {
if(res.code == 200) {
// 已登录,并且redirect地址有效,开始跳转
location.href = decodeURIComponent(res.data);
@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath
@@ -0,0 +1,68 @@
<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-sso-server-solon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.noear</groupId>
<artifactId>solon-parent</artifactId>
<version>1.12.0</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
<!-- SpringBoot Web依赖 -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-web</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-solon-plugin</artifactId>
<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>
<artifactId>sa-token-dao-redisx</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- 视图引擎(在前后端不分离模式下提供视图支持) -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon.view.thymeleaf</artifactId>
</dependency>
<!-- Http请求工具(在模式三的单点注销功能下用到,如不需要可以注释掉) -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>forest-solon-plugin</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,13 @@
package com.pj;
import org.noear.solon.Solon;
public class SaSsoServerApp {
public static void main(String[] args) {
Solon.start(SaSsoServerApp.class, args);
System.out.println("\n------ Sa-Token-SSO 统一认证中心启动成功 ");
}
}
@@ -0,0 +1,39 @@
package com.pj.h5;
import org.noear.solon.annotation.Component;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Filter;
import org.noear.solon.core.handle.FilterChain;
/**
* 跨域过滤器
* @author kong
*/
@Component(index = -200)
public class CorsFilter implements Filter {
static final String OPTIONS = "OPTIONS";
@Override
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
// 允许指定域访问跨域资源
ctx.headerSet("Access-Control-Allow-Origin", "*");
// 允许所有请求方式
ctx.headerSet("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
// 有效时间
ctx.headerSet("Access-Control-Max-Age", "3600");
// 允许的header参数
ctx.headerSet("Access-Control-Allow-Headers", "x-requested-with,satoken");
// 如果是预检请求,直接返回
if (OPTIONS.equals(ctx.method())) {
System.out.println("=======================浏览器发来了OPTIONS预检请求==========");
ctx.output("");
return;
}
// System.out.println("*********************************过滤器被使用**************************");
chain.doFilter(ctx);
}
}
@@ -0,0 +1,57 @@
package com.pj.h5;
import cn.dev33.satoken.sso.SaSsoConsts;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Render;
/**
* 前后台分离架构下集成SSO所需的代码 (SSO-Server端)
* <p>(注:如果不需要前后端分离架构下集成SSO,可删除此包下所有代码)</p>
* @author kong
*
*/
@Controller
public class H5Controller implements Render {
/**
* 获取 redirectUrl
*/
@Mapping("/sso/getRedirectUrl")
private Object getRedirectUrl(String redirect, String mode, String client) {
// 未登录情况下,返回 code=401
if (StpUtil.isLogin() == false) {
return SaResult.code(401);
}
// 已登录情况下,构建 redirectUrl
if (SaSsoConsts.MODE_SIMPLE.equals(mode)) {
// 模式一
SaSsoUtil.checkRedirectUrl(SaFoxUtil.decoderUrl(redirect));
return SaResult.data(redirect);
} else {
// 模式二或模式三
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), client, redirect);
return SaResult.data(redirectUrl);
}
}
/**
* 控制当前类的异常
*/
@Override
public void render(Object data, Context ctx) throws Throwable {
if (data instanceof Throwable) {
Throwable e = (Throwable) data;
e.printStackTrace();
ctx.render(SaResult.error(e.getMessage()));
} else {
ctx.render(data);
}
}
}
@@ -0,0 +1,28 @@
package com.pj.sso;
import cn.dev33.satoken.util.SaResult;
import org.noear.solon.annotation.Component;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Filter;
import org.noear.solon.core.handle.FilterChain;
/**
* 全局异常处理
* @author kong
*
*/
@Component
public class GlobalExceptionFilter implements Filter {
@Override
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
try {
chain.doFilter(ctx);
} catch (Exception e) {
e.printStackTrace();
ctx.render(SaResult.error(e.getMessage()));
}
}
}
@@ -0,0 +1,66 @@
package com.pj.sso;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.SaTokenDaoOfRedis;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.dtflys.forest.Forest;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.handle.ModelAndView;
/**
* @author noear 2023/1/3 created
*/
@Configuration
public class SsoConfig {
/**
* 构建 SaSsoConfig bean
* */
@Bean
public SaSsoConfig getSaSsoConfig(@Inject("${sa-token.sso}") SaSsoConfig ssoConfig) {
return ssoConfig;
}
/**
* 构建建 SaToken redis dao(如果不需要 redis;可以注释掉)
* */
@Bean
public SaTokenDao saTokenDaoInit(@Inject("${sa-token-dao.redis}") SaTokenDaoOfRedis saTokenDao) {
return saTokenDao;
}
// 配置SSO相关参数
@Bean
public void configSso(SaSsoConfig sso) {
// 配置:未登录时返回的View
sso.setNotLoginView(() -> {
return new ModelAndView("sa-login.html");
});
// 配置:登录处理函数
sso.setDoLoginHandle((name, pwd) -> {
// 此处仅做模拟登录,真实环境应该查询数据进行登录
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
}
return SaResult.error("登录失败!");
});
// 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉)
sso.setSendHttp(url -> {
try {
// 发起 http 请求
System.out.println("------ 发起请求:" + url);
return Forest.get(url).executeAsString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
});
}
}
@@ -0,0 +1,27 @@
package com.pj.sso;
import cn.dev33.satoken.sso.SaSsoProcessor;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Mapping;
/**
* Sa-Token-SSO Server端 Controller
* @author kong
*
*/
@Controller
public class SsoServerController {
/*
* SSO-Server端:处理所有SSO相关请求
* http://{host}:{port}/sso/auth -- 单点登录授权地址,接受参数:redirect=授权重定向地址
* http://{host}:{port}/sso/doLogin -- 账号密码登录接口,接受参数:name、pwd
* http://{host}:{port}/sso/checkTicket -- Ticket校验接口(isHttp=true时打开),接受参数:ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选]
* http://{host}:{port}/sso/signout -- 单点注销地址(isSlo=true时打开),接受参数:loginId=账号id、secretkey=接口调用秘钥
*/
@Mapping("/sso/*")
public Object ssoRequest() {
return SaSsoProcessor.instance.serverDister();
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
/*! layer mobile-v2.0.0 Web弹层组件 MIT License http://layer.layui.com/mobile By 贤心 */
;!function(e){"use strict";var t=document,n="querySelectorAll",i="getElementsByClassName",a=function(e){return t[n](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var n in e)t[n]=e[n];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var r=0,o=["layui-m-layer"],c=function(e){var t=this;t.config=l.extend(e),t.view()};c.prototype.view=function(){var e=this,n=e.config,s=t.createElement("div");e.id=s.id=o[0]+r,s.setAttribute("class",o[0]+" "+o[0]+(n.type||0)),s.setAttribute("index",r);var l=function(){var e="object"==typeof n.title;return n.title?'<h3 style="'+(e?n.title[1]:"")+'">'+(e?n.title[0]:n.title)+"</h3>":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e='<span yes type="1">'+n.btn[0]+"</span>",2===t&&(e='<span no type="0">'+n.btn[1]+"</span>"+e),'<div class="layui-m-layerbtn">'+e+"</div>"):""}();if(n.fixed||(n.top=n.hasOwnProperty("top")?n.top:100,n.style=n.style||"",n.style+=" top:"+(t.body.scrollTop+n.top)+"px"),2===n.type&&(n.content='<i></i><i class="layui-m-layerload"></i><i></i><p>'+(n.content||"")+"</p>"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"<div "+("string"==typeof n.shade?'style="'+n.shade+'"':"")+' class="layui-m-layershade"></div>':"")+'<div class="layui-m-layermain" '+(n.fixed?"":'style="position:static;"')+'><div class="layui-m-layersection"><div class="layui-m-layerchild '+(n.skin?"layui-m-layer-"+n.skin+" ":"")+(n.className?n.className:"")+" "+(n.anim?"layui-m-anim-"+n.anim:"")+'" '+(n.style?'style="'+n.style+'"':"")+">"+l+'<div class="layui-m-layercont">'+n.content+"</div>"+c+"</div></div></div>",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;o<r;o++)l.touch(s[o],a);if(e.shade&&e.shadeClose){var c=t[i]("layui-m-layershade")[0];l.touch(c,function(){layer.close(n.index,e.end)})}e.end&&(l.end[n.index]=e.end)},e.layer={v:"2.0",index:r,open:function(e){var t=new c(e||{});return t.index},close:function(e){var n=a("#"+o[0]+e)[0];n&&(n.innerHTML="",t.body.removeChild(n),clearTimeout(l.timer[e]),delete l.timer[e],"function"==typeof l.end[e]&&l.end[e](),delete l.end[e])},closeAll:function(){for(var e=t[i](o[0]),n=0,a=e.length;n<a;n++)layer.close(0|e[0].getAttribute("index"))}},"function"==typeof define?define(function(){return layer}):function(){var e=document.scripts,n=e[e.length-1],i=n.src,a=i.substring(0,i.lastIndexOf("/")+1);n.getAttribute("merge")||document.head.appendChild(function(){var e=t.createElement("link");return e.href=a+"need/layer.css?2.0",e.type="text/css",e.rel="styleSheet",e.id="layermcss",e}())}()}(window);
@@ -0,0 +1,59 @@
*{margin: 0; padding: 0;}
body{font-family: Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif;}
::-webkit-input-placeholder{color: #ccc;}
/* 视图盒子 */
.view-box{position: relative; width: 100vw; height: 100vh; overflow: hidden;}
/* 背景 EAEFF3 */
.bg-1{height: 50%; background: linear-gradient(to bottom right, #0466c5, #3496F5);}
.bg-2{height: 50%; background-color: #EAEFF3;}
/* 渐变背景 */
/*.bg-1{
background-size: 500%;
background-image: linear-gradient(125deg,#0466c5,#3496F5,#0466c5,#3496F5,#0466c5,#2496F5);
animation: bganimation 30s infinite;
}
@keyframes bganimation{
0%{background-position: 0% 50%;}
50%{background-position: 100% 50%;}
100%{background-position: 0% 50%;}
} */
/* 背景 */
.bg-1{background: #101C34;}
.bg-2{background: #101C34;}
/* .bg-1{height: 100%; background-image: url(./login-bg.png); background-size: 100% 100%;} */
/* 内容盒子 */
.content-box{position: absolute; width: 100vw; height: 100vh; top: 0px;}
/* 登录盒子 */
/* .login-box{width: 400px; height: 400px; position: absolute; left: calc(50% - 200px); top: calc(50% - 200px); max-width: 90%; } */
.login-box{width: 400px; margin: auto; max-width: 90%; height: 100%;}
.login-box{display: flex; align-items: center; text-align: center;}
/* 表单 */
.from-box{flex: 1; padding: 20px 50px; background-color: #FFF;}
.from-box{border-radius: 1px; box-shadow: 1px 1px 20px #666;}
.from-title{margin-top: 20px; margin-bottom: 30px; text-align: center;}
/* 输入框 */
.from-item{border: 0px #000 solid; margin-bottom: 15px;}
.s-input{width: 100%; line-height: 32px; height: 32px; text-indent: 1em; outline: 0; border: 1px #ccc solid; border-radius: 3px; transition: all 0.2s;}
.s-input{font-size: 12px;}
.s-input:focus{border-color: #409eff}
/* 登录按钮 */
.s-btn{ text-indent: 0; cursor: pointer; background-color: #409EFF; border-color: #409EFF; color: #FFF;}
.s-btn:hover{background-color: #50aEFF;}
/* 重置按钮 */
.reset-box{text-align: left; font-size: 12px;}
.reset-box a{text-decoration: none;}
.reset-box a:hover{text-decoration: underline;}
/* loading框样式 */
.ajax-layer-load.layui-layer-dialog{min-width: 0px !important; background-color: rgba(0,0,0,0.85);}
.ajax-layer-load.layui-layer-dialog .layui-layer-content{padding: 10px 20px 10px 40px; color: #FFF;}
.ajax-layer-load.layui-layer-dialog .layui-layer-content .layui-layer-ico{width: 20px; height: 20px; background-size: 20px 20px; top: 12px; }
@@ -0,0 +1,65 @@
// sa
var sa = {};
// 打开loading
sa.loading = function(msg) {
layer.closeAll(); // 开始前先把所有弹窗关了
return layer.msg(msg, {icon: 16, shade: 0.3, time: 1000 * 20, skin: 'ajax-layer-load' });
};
// 隐藏loading
sa.hideLoading = function() {
layer.closeAll();
};
// ----------------------------------- 登录事件 -----------------------------------
$('.login-btn').click(function(){
sa.loading("正在登录...");
// 开始登录
setTimeout(function() {
$.ajax({
url: "sso/doLogin",
type: "post",
data: {
name: $('[name=name]').val(),
pwd: $('[name=pwd]').val()
},
dataType: 'json',
success: function(res){
console.log('返回数据:', res);
sa.hideLoading();
if(res.code == 200) {
layer.msg('登录成功', {anim: 0, icon: 6 });
setTimeout(function() {
location.reload();
}, 800)
} else {
layer.msg(res.msg, {anim: 6, icon: 2 });
}
},
error: function(xhr, type, errorThrown){
sa.hideLoading();
if(xhr.status == 0){
return layer.alert('无法连接到服务器,请检查网络');
}
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}, 400);
});
// 绑定回车事件
$('[name=name],[name=pwd]').bind('keypress', function(event){
if(event.keyCode == "13") {
$('.login-btn').click();
}
});
// 输入框获取焦点
$("[name=name]").focus();
// 打印信息
var str = "This page is provided by Sa-Token, Please refer to: " + "https://sa-token.cc/";
console.log(str);
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<title>Sa-SSO-Server 认证中心-登录</title>
<meta charset="utf-8">
<base th:href="@{/static}" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="./sa-res/login.css">
</head>
<body>
<div class="view-box">
<div class="bg-1"></div>
<div class="bg-2"></div>
<div class="content-box">
<div class="login-box">
<div class="from-box">
<h2 class="from-title">Sa-SSO-Server 认证中心</h2>
<div class="from-item">
<input class="s-input" name="name" placeholder="请输入账号" />
</div>
<div class="from-item">
<input class="s-input" name="pwd" type="password" placeholder="请输入密码" />
</div>
<div class="from-item">
<button class="s-input s-btn login-btn">登录</button>
</div>
<div class="from-item reset-box">
<a href="javascript: location.reload();" >刷新</a>
</div>
</div>
</div>
</div>
<!-- 底部 版权 -->
<div style="position: absolute; bottom: 40px; width: 100%; text-align: center; color: #666;">
This page is provided by Sa-Token-SSO
</div>
</div>
<!-- scripts -->
<script src="./sa-res/jquery.min.js"></script>
<script src="./sa-res/layer/layer.js"></script>
<script src="./sa-res/login.js"></script>
</body>
</html>
@@ -0,0 +1,41 @@
# 端口
server:
port: 9000
# Sa-Token 配置
sa-token:
# ------- SSO-模式一相关配置 (非模式一不需要配置)
# cookie:
# 配置 Cookie 作用域
# domain: stp.com
# ------- SSO-模式二相关配置
sso:
# Ticket有效期 (单位: 秒),默认五分钟
ticket-timeout: 300
# 所有允许的授权回调地址
allow-url: "*"
# 是否打开单点注销功能
is-slo: true
# ------- SSO-模式三相关配置 (下面的配置在SSO模式三并且 is-slo=true 时打开)
# 是否打开模式三
isHttp: true
# 接口调用秘钥(用于SSO模式三的单点注销功能)
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器(文档有步骤说明)
sa-token-dao: #名字可以随意取
redis:
server: "localhost:6379"
password: 123456
db: 1
maxTotal: 200
forest:
# 关闭 forest 请求日志打印
log-enabled: false
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
@@ -23,7 +23,7 @@ public class H5Controller {
* 获取 redirectUrl
*/
@RequestMapping("/sso/getRedirectUrl")
private Object getRedirectUrl(String redirect, String mode) {
private Object getRedirectUrl(String redirect, String mode, String client) {
// 未登录情况下,返回 code=401
if(StpUtil.isLogin() == false) {
return SaResult.code(401);
@@ -35,7 +35,7 @@ public class H5Controller {
return SaResult.data(redirect);
} else {
// 模式二或模式三
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), redirect);
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), client, redirect);
return SaResult.data(redirectUrl);
}
}
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
@@ -10,13 +10,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.33.0</sa-token.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
@@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>

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