Compare commits
214 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b230e290f | |||
| 51d0378274 | |||
| f243f105ea | |||
| 4da5f2381f | |||
| 2929f5cbc9 | |||
| 074c790db5 | |||
| 79b0de9cd6 | |||
| 6f4af4351f | |||
| 97ddf27d00 | |||
| c229282e4a | |||
| be104951d9 | |||
| d4700d8693 | |||
| ec8d7651a5 | |||
| 449713a2ad | |||
| 9a54822d2e | |||
| 613304b088 | |||
| dbd0f42547 | |||
| 8b9cd0c813 | |||
| 76591b5243 | |||
| cc2ced9fdd | |||
| c30da2ceb9 | |||
| b736f8a4f4 | |||
| 1a90d8270a | |||
| 0d2f2d42bd | |||
| d7ee47f59d | |||
| 7e5c56c8e4 | |||
| c0f781a9fb | |||
| 47dff7059d | |||
| 3bc7daea87 | |||
| def8106fb4 | |||
| 3e3b505a3d | |||
| f8557c24dd | |||
| 368bbe9ce0 | |||
| edc46ef182 | |||
| 73df9def9e | |||
| b81c61cc6f | |||
| f6f420e107 | |||
| 41f3f1d4dd | |||
| 9278251696 | |||
| c0dbb1ecc8 | |||
| 98a5f7934d | |||
| 6332c794aa | |||
| 7e37ef63cd | |||
| 78599a3a87 | |||
| c582e24ffa | |||
| ad837c9640 | |||
| aa3f77bb3c | |||
| 80c7362bfa | |||
| 91dd4bb30a | |||
| f4a7ef6e00 | |||
| 79245edb5b | |||
| 98f4de6514 | |||
| 024d6e0491 | |||
| 5c675d0a37 | |||
| 1858306fde | |||
| 4c69912af0 | |||
| a24a5b2271 | |||
| 0e551cfd69 | |||
| 2d37f76a42 | |||
| 643f422ac8 | |||
| 3f9b07c25e | |||
| 6cbf628c0f | |||
| 33047fd760 | |||
| 3f3263fe33 | |||
| e966f6cec1 | |||
| d3bdd6319c | |||
| 22e4d7e80b | |||
| 890a8f860e | |||
| f9ebc52a39 | |||
| 34c6b752d7 | |||
| 46ab4d7849 | |||
| bd21035cf1 | |||
| 465dfaddeb | |||
| af81ccac75 | |||
| b97e60661d | |||
| 5e0ca81b9e | |||
| 1f4ff1ede4 | |||
| d75d5de438 | |||
| ab362c87e0 | |||
| 0c23371058 | |||
| 197ba44ce1 | |||
| c8a7b50cb8 | |||
| 9715526c34 | |||
| 22302760c8 | |||
| 466c215506 | |||
| 7e1d810667 | |||
| 809232d656 | |||
| 2c442f4c0a | |||
| dbbb47a1a9 | |||
| 4b176d49d5 | |||
| 750c346353 | |||
| 1a37efef22 | |||
| d9168711a0 | |||
| 1a89c68e68 | |||
| 22e9ee80d0 | |||
| 80f132c1f8 | |||
| 6a96d2fabd | |||
| f40f0d3adf | |||
| 6398ef4ca6 | |||
| e8fe3a83c7 | |||
| 8c0177c598 | |||
| fc27e2a49d | |||
| 7c95ec51fa | |||
| 9b7efbbcc8 | |||
| 2d671dc783 | |||
| f6c5131ad2 | |||
| 9c07f7a007 | |||
| c2becdf498 | |||
| defb2a0052 | |||
| 007bf2aa47 | |||
| 7725322002 | |||
| 91f97089b5 | |||
| ef9a0ac3d4 | |||
| 60a348e903 | |||
| 2eeb47034b | |||
| c83ccee894 | |||
| eba5294beb | |||
| 96cb8c5ad3 | |||
| a552e84488 | |||
| 82158d1ee1 | |||
| 26c4957020 | |||
| 49ac796460 | |||
| 9f8a2b29ba | |||
| e608272f3f | |||
| 8be310be15 | |||
| 9090eddd8d | |||
| 34c0775fa2 | |||
| 4c737cb342 | |||
| 2294d89db6 | |||
| 9444520ad3 | |||
| d0135299d1 | |||
| dce14ce384 | |||
| 150781e94f | |||
| 192a66aed2 | |||
| 7c4865d0e1 | |||
| f431473c90 | |||
| 6387b7eede | |||
| 7eb2772484 | |||
| 21df9682b6 | |||
| b707205607 | |||
| d4a3eef5c7 | |||
| 0e45a9067a | |||
| b3fbfa35d4 | |||
| 18f8ed74ff | |||
| ac74945004 | |||
| 2fc76c767e | |||
| cefa368f4d | |||
| 84795e9014 | |||
| f8d57189e0 | |||
| 43ae08bd3c | |||
| 4cbb0849a7 | |||
| b1723e5424 | |||
| 196d34be20 | |||
| ff3ce7daa5 | |||
| f61d8369ac | |||
| a27739f344 | |||
| 9378da7a91 | |||
| 47e8e91056 | |||
| e7c09e81d7 | |||
| 6cd765ce91 | |||
| d646099127 | |||
| 5708657f86 | |||
| 7e9201584d | |||
| a6a1b9b865 | |||
| 4b7e6d651f | |||
| f6d6e50a16 | |||
| fdead8cfa6 | |||
| aa15d56082 | |||
| 30ffe1713f | |||
| 74db9e997e | |||
| 6264c37780 | |||
| f4fa77edd0 | |||
| 2cf899e019 | |||
| c625a29df8 | |||
| d0c2ee7ffd | |||
| 39b468b150 | |||
| a16552c3fe | |||
| ed35640765 | |||
| b85b2815ea | |||
| 5b8b99e0d0 | |||
| 75256e3329 | |||
| a59c42802c | |||
| 6a21591e61 | |||
| 27c1a0b1a2 | |||
| 7a534d5aa4 | |||
| 43728fb6cc | |||
| 3c558c2472 | |||
| ae1bd30245 | |||
| a53874025e | |||
| 7a63be81d4 | |||
| 4a16556a56 | |||
| 5051f430c0 | |||
| fa76826ee0 | |||
| 4bbce74570 | |||
| 622cf3f3b5 | |||
| 0bebcd4b02 | |||
| e1f25ccae8 | |||
| ac6f06fb37 | |||
| 03613bf7a3 | |||
| e4da3d8e7b | |||
| 79f772ccc8 | |||
| 312ca71c47 | |||
| ac2e1d0abd | |||
| 808c3cdc0f | |||
| 4036788426 | |||
| f57303f7ec | |||
| 2919188470 | |||
| 5c33c83391 | |||
| 39a986071d | |||
| 072ae7c787 | |||
| eefb5e5f8e | |||
| f75b6595dd | |||
| b0d42821d7 | |||
| 2f6390a211 |
@@ -0,0 +1,41 @@
|
||||
|
||||
## 未完成目标
|
||||
|
||||
|
||||
### 1、尝试将所有 `<properties>` 依赖版本号定义在同一个 pom.xml 里。 **[❌失败]**
|
||||
|
||||
**尝试1:将所有 `<properties>` 定义在 `sa-token-dependencies` 里:**
|
||||
|
||||
结果: 无法在 `sa-token-spring-boot2/3/4-dependencies` 中引用这些 `<properties>`,因为 `<dependencyManagement> <dependencies> <scope>import</scope>` 只会导入目标的 `<dependencyManagement>` 版本号定义,不会导入目标的 `<properties>` 属性。
|
||||
|
||||
`<properties>` 只会在 父子结构中向下传递,不会在 `<dependencyManagement> <dependencies> <scope>import</scope>` 中传递。
|
||||
|
||||
|
||||
**尝试2:将所有 `<properties>` 定义在 `sa-token-parent` 里:**
|
||||
|
||||
结果:在 `sa-token-dependencies` 里无法引用这些 `<properties>`,因为 `sa-token-parent` 不是 `sa-token-dependencies` 的父模块。
|
||||
|
||||
将 `sa-token-parent` 定义为 `sa-token-dependencies` 的父模块行吗?
|
||||
|
||||
不行,因为在 `sa-token-parent` 通过 `<dependencyManagement> <dependencies> <scope>import</scope>` 导入了 `sa-token-dependencies`,如果再把 `sa-token-parent` 定义为 `sa-token-dependencies` 的父模块,会造成循环依赖。
|
||||
|
||||
执行 `mvn package` 打包时,maven 会直接报错:
|
||||
|
||||
```
|
||||
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
|
||||
[ERROR] The dependencies of type=pom and with scope=import form a cycle: cn.dev33:sa-token-parent:1.44.0 -> cn.dev33:sa-token-basic-dependencies:1.44.0 -> cn.dev33:sa-token-basic-dependencies:1.44.0 @ cn.dev33:sa-token-basic-dependencies:1.44.0
|
||||
@
|
||||
[ERROR] The build could not read 1 project -> [Help 1]
|
||||
[ERROR]
|
||||
[ERROR] The project cn.dev33:sa-token-parent:1.44.0 (E:\work\project-yun\sa-token\pom.xml) has 1 error
|
||||
[ERROR] The dependencies of type=pom and with scope=import form a cycle: cn.dev33:sa-token-parent:1.44.0 -> cn.dev33:sa-token-basic-dependencies:1.44.0 -> cn.dev33:sa-token-basic-dependencies:1.44.0 @ cn.dev33:sa-token-basic-dependencies:1.44.0
|
||||
[ERROR]
|
||||
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
|
||||
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
|
||||
[ERROR]
|
||||
[ERROR] For more information about the errors and possible solutions, please read the following articles:
|
||||
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
|
||||
2026-3-1 调试记录
|
||||
|
||||
启动 SaOAuth2ServerApplication,报错空指针:SaOAuth2ServerController 文件的 SaOAuth2Strategy.instance.notLoginView 空指针,SaOAuth2Strategy.instance 为 null
|
||||
|
||||
|
||||
SaOAuth2Strategy.instance 的定义为:
|
||||
public static final SaOAuth2Strategy instance = new SaOAuth2Strategy();
|
||||
|
||||
看代码是无论如何也不可能空指针的,诡异。
|
||||
|
||||
在 main 方法第一句加上测试
|
||||
|
||||
@SpringBootApplication
|
||||
public class SaOAuth2ServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(SaOAuth2Strategy.instance);
|
||||
SpringApplication.run(SaOAuth2ServerApplication.class, args);
|
||||
System.out.println("\nSa-Token-OAuth2 Server端启动成功,配置如下:");
|
||||
System.out.println(SaOAuth2Manager.getServerConfig());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
打印居然为 null。
|
||||
|
||||
询问 AI,解释的乱七八糟,没有参考价值。
|
||||
|
||||
然后在根目录执行 mvn clean,居然无法成功。sa-token-test 模块无法 clean 。
|
||||
|
||||
报错 test 依赖不存在
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
最后必须在 dependencyManagement 加上这个才行
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<version>2.7.18</version>
|
||||
</dependency>
|
||||
|
||||
可是就算不加,我也已经在 sa-token-spring-boot2-dependencies 中定义这个依赖了呀,为什么 在 sa-token-test 中无法 import spring-boot-starter-test ?
|
||||
|
||||
加了后,mvn clean 执行成功了
|
||||
|
||||
但是 mvn package 又开始无法打包。
|
||||
|
||||
可以昨天我明明能打包成功的啊?今天好像就变动了一下 sa-token-test 中的依赖配置。这有什么影响吗?
|
||||
|
||||
而且打包报错信息居然是:sa-token-jboot-plugin 插件中 javax.servlet.http.HttpServletRequest 无法转换为 HttpServletRequest
|
||||
|
||||
什么东西啊。
|
||||
|
||||
抓头挠腮解决不了。
|
||||
|
||||
这个插件已经十几个版本没有变动过代码了,代码不变,打包环境不变,命令不变,今天就突然报这种莫名其妙的错误,无奈,只能先去除这个插件,不让它参与打包。
|
||||
|
||||
继续打包,又开始报错:
|
||||
|
||||
sa-token-jfinal-plugin 中 cn.dev33.satoken.context.SaTokenContext 无法转换为 SaTokenContext。
|
||||
|
||||
这一瞬间我怀疑自己正处于梦中。
|
||||
|
||||
纠结了半分钟,继续去除此插件,继续打包。
|
||||
|
||||
打包成功了。
|
||||
|
||||
启动 SaOAuth2ServerApplication,启动成功,SaOAuth2ServerController 文件的 SaOAuth2Strategy.instance.notLoginView 空指针问题,消失了。
|
||||
|
||||
请问中间的这几个报错和这个空指针有任何关联吗?我请问呢?
|
||||
|
||||
|
||||
|
||||
注:以上所有叙述均为最后打包成功后进行回忆,可能细节上略有偏差。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
两小时后:
|
||||
本来可以运行成功的代码,只要一改子模块的代码就无法再运行成功,报错:java: 无法访问SaRequest。
|
||||
试了好多解决方案,不行。
|
||||
|
||||
---
|
||||
吃了两份炉盖香酥鸡饼,原来人在压力大的时候真的需要补充能量。
|
||||
---
|
||||
|
||||
继续报错:
|
||||
Maven 资源编译器: 模块 'sa-token-oauth2' 所需的 Maven 项目配置不可用。仅当从 IDE 启动外部构建时,才支持 Maven 项目编译。
|
||||
sa-token-jwt、sso、sign 等模块均出现此问题
|
||||
|
||||
最后:
|
||||
把项目删掉,重新下载一份,导入
|
||||
项目可以运行成功了,但是每次修改子模块,在 demo 示例里无法实时起作用。需要 mvn clean install 才能看到效果。
|
||||
|
||||
最后:
|
||||
取消勾选 maven 配置项:Delegate IDE build/run actions to Maven
|
||||
|
||||
一切问题解决,包括最上面的诡异调试现象也消失了。
|
||||
|
||||
idea,你给老子爬
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
<!-- sa-token-bom -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-bom</artifactId>
|
||||
<version>1.45.0</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-core -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-core</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-spring-boot-starter -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-spring-boot3-starter -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-spring-boot4-starter -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot4-starter</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-reactor-spring-boot-starter -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-reactor-spring-boot-starter</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-reactor-spring-boot3-starter -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-reactor-spring-boot4-starter -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-reactor-spring-boot4-starter</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-jboot-plugin -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-jboot-plugin</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-jfinal-plugin -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-jfinal-plugin</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-loveqq-boot-starter -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-loveqq-boot-starter</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-servlet -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-servlet</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-jakarta-servlet -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-jakarta-servlet</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-plugin -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-plugin</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-alone-redis -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-alone-redis</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-redis-template -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-template</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-redis-template-jdk-serializer -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-template-jdk-serializer</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-redis-jackson -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-jackson</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-redisson -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redisson</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-redisson-spring-boot-starter -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redisson-spring-boot-starter</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-redisx -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redisx</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-hutool-timed-cache -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-hutool-timed-cache</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-caffeine -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-caffeine</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-jackson -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-jackson</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-jackson3 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-jackson3</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-fastjson -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-fastjson</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-fastjson2 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-fastjson2</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-snack3 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-snack3</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-snack4 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-snack4</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-serializer-features -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-serializer-features</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-thymeleaf -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-thymeleaf</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-freemarker -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-freemarker</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-dubbo -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dubbo</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-dubbo3 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dubbo3</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-grpc -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-grpc</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-forest -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-forest</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-okhttps -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-okhttps</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-jwt -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-jwt</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-temp-jwt -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-temp-jwt</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-oauth2 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-oauth2</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-apikey -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-apikey</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-sign -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-sign</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-quick-login -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-quick-login</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-sso -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-sso</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-spring-aop -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-aop</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-spring-el -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-el</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-spring-boot-webmvc-reactor-v2v3v4-common -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-webmvc-reactor-v2v3v4-common</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-spring-boot-reactor-v2v3v4-common -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-reactor-v2v3v4-common</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-spring-boot-webmvc-v3v4-common -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-webmvc-v3v4-common</artifactId>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
|
||||
sa-token-bom 定义 sa-token 所有自身依赖的版本
|
||||
sa-token-dependencies 定义 sa-token 所有第三方依赖版本,并继承 sa-token-bom
|
||||
sa-token-spring-boot2-dependencies 定义 spring-boot2 相关依赖版本
|
||||
sa-token-spring-boot3-dependencies 定义 spring-boot3 相关依赖版本
|
||||
sa-token-spring-boot4-dependencies 定义 spring-boot4 相关依赖版本
|
||||
|
||||
sa-token-parent 父级 pom.xml 引入 sa-token-dependencies
|
||||
|
||||
|
||||
|
||||
sa-token-spring-boot-webmvc-reactor-v2v3v4-common import sa-token-spring-boot2-dependencies
|
||||
|
||||
sa-token-spring-boot-starter import sa-token-spring-boot2-dependencies
|
||||
sa-token-reactor-spring-boot-starter import sa-token-spring-boot2-dependencies
|
||||
sa-token-spring-boot-webmvc-v3v4-common import sa-token-spring-boot3-dependencies
|
||||
引入 sa-token-spring-boot-webmvc-reactor-v2v3v4-common
|
||||
|
||||
|
||||
sa-token-spring-boot3-starter import sa-token-spring-boot3-dependencies
|
||||
sa-token-spring-boot4-starter import sa-token-spring-boot4-dependencies
|
||||
引入 sa-token-spring-boot-webmvc-v3v4-common
|
||||
|
||||
|
||||
|
||||
|
||||
A 定义了 <dependencyManagement>,
|
||||
|
||||
B通过如下方式引入 A:
|
||||
<dependencies>
|
||||
<dependency>
|
||||
A
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
那么 A 里定义的 <dependencyManagement>,在 B 里生效吗?
|
||||
不生效
|
||||
+11
@@ -12,7 +12,18 @@ unpackage/
|
||||
/.factorypath
|
||||
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
sa-token-three-plugin/
|
||||
sa-token-doc/big-file/
|
||||
|
||||
.flattened-pom.xml
|
||||
|
||||
.qoder
|
||||
.soloncode/
|
||||
.cursor
|
||||
.agents
|
||||
.github
|
||||
|
||||
AGENTS.md
|
||||
CLAUDE.md
|
||||
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2011-2019 hubin.
|
||||
Copyright 2011-Present hubin.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,29 +1,39 @@
|
||||
<p align="center">
|
||||
<img alt="logo" src="https://sa-token.cc/logo.png" width="150" height="150">
|
||||
</p>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.44.0</h1>
|
||||
<h4 align="center">开源、免费、轻量级 Java 权限认证框架,让鉴权变得简单、优雅!</h4>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.45.0</h1>
|
||||
<h4 align="center">✨ 开源、免费、一站式 java 权限认证框架,让鉴权变得简单、优雅! </h4>
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/dromara/sa-token/stargazers"><img src="https://gitee.com/dromara/sa-token/badge/star.svg?theme=gvp"></a>
|
||||
<a href="https://gitee.com/dromara/sa-token/members"><img src="https://gitee.com/dromara/sa-token/badge/fork.svg?theme=gvp"></a>
|
||||
<a href="https://gitcode.com/dromara/sa-token/stargazers"><img src="https://gitcode.com/dromara/Sa-Token/star/badge.svg"></a>
|
||||
<a href="https://atomgit.com/dromara/sa-token/stargazers"><img src="https://atomgit.com/dromara/Sa-Token/star/badge.svg"></a>
|
||||
<a href="https://github.com/dromara/sa-token/stargazers"><img src="https://img.shields.io/github/stars/dromara/sa-token?style=flat-square&logo=GitHub"></a>
|
||||
<a href="https://github.com/dromara/sa-token/network/members"><img src="https://img.shields.io/github/forks/dromara/sa-token?style=flat-square&logo=GitHub"></a>
|
||||
<a href="https://github.com/dromara/sa-token/watchers"><img src="https://img.shields.io/github/watchers/dromara/sa-token?style=flat-square&logo=GitHub"></a>
|
||||
<!-- <a href="https://github.com/dromara/sa-token/watchers"><img src="https://img.shields.io/github/watchers/dromara/sa-token?style=flat-square&logo=GitHub"></a> -->
|
||||
<!-- <a href="https://github.com/dromara/sa-token/issues"><img src="https://img.shields.io/github/issues/dromara/sa-token.svg?style=flat-square&logo=GitHub"></a> -->
|
||||
<a href="https://github.com/dromara/sa-token/blob/master/LICENSE"><img src="https://img.shields.io/github/license/dromara/sa-token.svg?style=flat-square"></a>
|
||||
</p>
|
||||
<!-- <p align="center">学习测试请拉取 master 分支,dev 是在开发分支 (在根目录执行 `git checkout master`)</p> -->
|
||||
<p align="center"><a href="https://sa-token.cc" target="_blank">在线文档:https://sa-token.cc</a></p>
|
||||
<p align="center"><a href="https://sa-token.cc?way=readme" target="_blank">在线文档:https://sa-token.cc</a></p>
|
||||
|
||||
|
||||
---
|
||||
|
||||
### Sa-Token 介绍
|
||||
### 📝 前言:
|
||||
|
||||
回望 2020 年初,我为 Sa-Token 提交第一行代码之际,彼时市面上 Java 缺少的不仅是一个简洁好用的鉴权框架,更是一整套清晰、自洽的权限架构设计思想。
|
||||
|
||||
因此,这几年间我将大量时间倾注在 Sa-Token 的文档编写,几乎每一章节、每一句话、每一个字都经过反复修改、精细打磨,以求做到最清晰、干练、易懂的表述。用心阅读文档,你学习到的将不止是 Sa-Token 框架本身,更是绝大多数场景下权限设计的最佳实践。
|
||||
|
||||
|
||||
|
||||
### 🛠️ Sa-Token 介绍
|
||||
|
||||
Sa-Token 是一个轻量级 Java 权限认证框架,目前拥有五大核心模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。
|
||||
|
||||

|
||||
**你还在手搓以下功能?Stop ⚠️ 让 Sa-Token 来!**
|
||||
|
||||

|
||||
|
||||
要在 SpringBoot 项目中使用 Sa-Token,你只需要在 pom.xml 中引入依赖:
|
||||
|
||||
@@ -32,11 +42,11 @@ Sa-Token 是一个轻量级 Java 权限认证框架,目前拥有五大核心
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.44.0</version>
|
||||
<version>1.45.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
除了 SpringBoot2、Sa-Token 还为 SpringBoot3、Solon、JFinal 等常见 Web 框架提供集成包,做到真正的开箱即用。
|
||||
除了支持 SpringBoot2、Sa-Token 还为 SpringBoot3/4、Solon、JFinal 等常见 Web 框架提供集成包,做到真正的开箱即用。
|
||||
|
||||
|
||||
<details>
|
||||
@@ -144,59 +154,133 @@ registry.addInterceptor(new SaInterceptor(handler -> {
|
||||
|
||||
|
||||
|
||||
### SSO 单点登录
|
||||
### 🍃 SSO 单点登录
|
||||
|
||||
Sa-Token SSO 分为三种模式,解决同域、跨域、共享Redis、跨Redis、前后端一体、前后端分离……等不同架构下的 SSO 接入问题:
|
||||
Sa-Token SSO 分为三种模式,可解决:`同域、跨域、共享Redis、跨Redis、前后端一体、前后端分离、纯 js、vue2、vue3、java 项目、非 java 项目` 等架构下的 SSO 认证需求:
|
||||
|
||||

|
||||

|
||||
|
||||
| 系统架构 | 采用模式 | 简介 | 文档链接 |
|
||||
| :-------- | :-------- | :-------- | :-------- |
|
||||
| 前端同域 + 后端同 Redis | 模式一 | 共享Cookie同步会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso1-client) |
|
||||
| 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso2-client) |
|
||||
| 前端不同域 + 后端 不同Redis | 模式三 | Http请求获取会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso3-client) |
|
||||
|
||||
| 系统架构 | 采用模式 | 简介 | 文档链接 |
|
||||
| :-------- | :-------- |:----------------| :-------- |
|
||||
| 前端同域 + 后端同 Redis | 模式一 | 共享Cookie同步会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso1-client) |
|
||||
| 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso2-client) |
|
||||
| 前端不同域 + 后端 不同Redis | 模式三 | HTTP请求获取会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso3-client) |
|
||||
|
||||
|
||||
1. 前端同域:就是指多个系统可以部署在同一个主域名之下,比如:`c1.domain.com`、`c2.domain.com`、`c3.domain.com`
|
||||
2. 后端同Redis:就是指多个系统可以连接同一个Redis。(此处并非要所有项目数据都放在一个Redis中,Sa-Token提供 **`[权限缓存与业务缓存分离]`** 的解决方案)
|
||||
3. 如果既无法做到前端同域,也无法做到后端同Redis,可以走模式三,Http请求校验 ticket 获取会话。
|
||||
4. 提供 NoSdk 模式示例,不使用 Sa-Token 的系统也可以对接。
|
||||
5. 提供 sso-server 接口文档,不使用 java 语言的系统也可以对接。
|
||||
6. 提供前后端分离整合方案:无论是 sso-server 还是 sso-client 的前后端分离都可以整合。
|
||||
7. 提供安全校验:域名校验、ticket校验、参数签名校验,有效防 ticket 劫持,防请求重放等攻击。
|
||||
8. 参数防丢:笔者曾试验多个SSO框架,均有参数丢失情况,比如登录前是:`http://a.com?id=1&name=2`,登录成功后就变成了:`http://a.com?id=1`,Sa-Token-SSO 内有专门算法保证了参数不丢失,登录成功后精准原路返回。
|
||||
9. 提供用户数据同步/迁移方案的建议:开发前统一迁移、运行时实时数据同步、根据关联字段匹配、根据 center_id 字段匹配等。
|
||||
10. 提供直接可运行的 demo 示例,帮助你快速熟悉 SSO 大致登录流程。
|
||||
2. 后端同 Redis:就是指多个系统可以连接同一个 Redis,共享会话数据。
|
||||
3. 如果无法做到前端同域、后端同 Redis,可以走托底的模式三:Http请求校验 ticket 获取会话。
|
||||
4. 提供:NoSdk 模式示例 + sso-server 接口文档,非 Sa-Token 项目、非 java 项目也可以对接。
|
||||
5. 提供:多重安全校验:域名校验、ticket校验、参数签名校验,有效防 ticket 劫持,防请求重放等攻击。
|
||||
6. 提供:大量实战痛点教学:sso-server 前后端分离设计、sso-client 前后端分离设计、用户数据同步/迁移方案设计。
|
||||
7. 提供:直接可运行的 demo 示例,助你快速熟悉 SSO 大致登录流程。
|
||||
8. 提供:深度细节优化,参数防丢:笔者曾试验多个SSO框架,均有参数丢失情况,比如登录前是:`http://a.com?id=1&name=2`,登录成功后就变成了:`http://a.com?id=1`,Sa-Token-SSO 内有专门算法保证了参数不丢失,登录成功后精准原路返回。
|
||||
|
||||
|
||||
|
||||
### OAuth2 授权认证
|
||||
Sa-Token-OAuth2 模块分为四种授权模式,解决不同场景下的授权需求
|
||||
|
||||
| 授权模式 | 简介 |
|
||||
| :-------- | :-------- |
|
||||
| 授权码(Authorization Code) | OAuth2.0 标准授权步骤,Server 端向 Client 端下放 Code 码,Client 端再用 Code 码换取授权 Token |
|
||||
| 隐藏式(Implicit) | 无法使用授权码模式时的备用选择,Server 端使用 URL 重定向方式直接将 Token 下放到 Client 端页面 |
|
||||
| 密码式(Password) | Client直接拿着用户的账号密码换取授权 Token |
|
||||
| 客户端凭证(Client Credentials)| Server 端针对 Client 级别的 Token,代表应用自身的资源授权 |
|
||||
### 🍂 OAuth2 授权认证
|
||||
Sa-Token OAuth2 模块分为四种授权模式,解决不同场景下的授权需求
|
||||
|
||||
| 授权模式 | 简介 |
|
||||
| :-------- | :-------- |
|
||||
| 授权码式 | OAuth2 标准授权步骤,server 端下放 code,client 端获取 code 码兑换 access_token |
|
||||
| 隐藏式 | 备用选择,server 端使用 URL 重定向方式直接将 access_token 下放到 client 端页面 |
|
||||
| 密码式 | client 直接拿着用户的账号密码换取授权 access_token |
|
||||
| 客户端凭证式 | server 端针对 client 级别的 client_token,代表应用自身的资源授权 |
|
||||
|
||||
详细参考文档:[https://sa-token.cc/doc.html#/oauth2/readme](https://sa-token.cc/doc.html#/oauth2/readme)
|
||||
|
||||
|
||||
### 开源集成案例
|
||||
### 📖❓ 疑问解答
|
||||
|
||||
**1、Sa-Token 功能全不全?**
|
||||
|
||||
七年磨一剑:五大核心模块(登录、鉴权、SSO、OAuth2、微服务) + 众多实用插件 (短 token、jwt 集成、API 参数签名、API Key 秘钥授权...) 我们提供的不只是权限认证,我们提供的是一站式解决方案。
|
||||
|
||||
|
||||
**2、Sa-Token 好不好学?**
|
||||
|
||||
中文文档 + 中文代码注释 + 中文交流社区 + 大量实战案例博客 + 多个视频教程 + 大量优秀开源项目集成案例。
|
||||
|
||||
|
||||
**3、Sa-Token 用的人多不多?**
|
||||
|
||||
截止统计日 (2026-1-25) 起,Sa-Token 在:
|
||||
|
||||
- Gitee 关注量达到 48627 Star,位列平台所有推荐项目排行榜第一名。
|
||||
- GitHub 关注量达到 18523 Star,是主要竞争框架 Spring Security 的 1.97 倍,Apache Shiro 的 4.19 倍。
|
||||
- 25+ 微信粉丝群 (500人),8+ QQ粉丝群 (1000人 or 2000人) ,在线文档访问量月PV 20万+。
|
||||
|
||||
这是众多开发者用脚投票的数据,相信这些数据比任何言语都能证明 Sa-Token 的热度。
|
||||
|
||||
|
||||
**4、Sa-Token 有哪些权威认证?**
|
||||
|
||||
曾获荣誉包括但不限于:Gitee GVP 最有价值开源项目、GitCode G-Star 优质开源项目、OSCHINA 2021 人气指数 TOP 30 开源项目、OSCHINA 2022 年度最火热中国开源项目社区之一、开放原子基金会2023快速成长开源项目、 Dromara 组织顶尖项目(之一)、可信开源社区共同体预备成员、所在开源社区 “Dromara” 荣获《2024中国互联网发展创新与投资大赛(开源)》二等奖。 Gitee High Star 计划项目(5000+star)。Gitee 2025年度开源项目 Web应用开发 Top 2。
|
||||
|
||||
|
||||
**5、Sa-Token 收费吗?**
|
||||
|
||||
Sa-Token 采用 Apache-2.0 开源协议,承诺框架本身与在线文档永久免费开放。当然如果您有心赞助 Sa-Token,我们也不回避:[赞助链接](https://sa-token.cc/doc.html#/more/sa-token-donate)。
|
||||
我们将定期同步赞助者名单到在线文档展示。(您需要注意的一点是:该赞助仅为友情赞助,不提供任何商业交换)
|
||||
|
||||
|
||||
**6、Sa-Token 是封装的 SpringSecurity 吗?是套壳 ApacheShiro 吗?**
|
||||
|
||||
不是。Sa-Token 不是一个后台模板,也不是针对 xx 框架的二次封装套壳,而是从 0 开始的纯血自研框架,核心包零依赖,完全自主可控的架构内核 + 众多主流框架的集成适配。
|
||||
|
||||
|
||||
|
||||
|
||||
#### 证书 ⭐ 奖杯 🏆 荣誉展示
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/gvp.jpg" title="GVP - Gitee 最有价值开源项目" /></td>
|
||||
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/g-star.jpg" title="GitCode G-Star 优质开源项目" /></td>
|
||||
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/osc-2021.jpg" title="OSCHINA 2021 人气指数 TOP 30 开源项目" /></td>
|
||||
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/osc-2022--chang.jpg" title="OSCHINA 2022 年度最火热中国开源项目社区" /></td>
|
||||
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/kexin.jpg" title="可信开源社区共同体预备成员" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/gitee-star-5000.jpg" title="Gitee 5000 star 专属奖杯" /></td>
|
||||
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/gitee-2025--chang.jpg" title="Gitee 2025年度开源项目 Web应用开发 Top 2" /></td>
|
||||
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/dromara.jpg" title="Dromara 组织顶尖项目(之一)" /></td>
|
||||
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/kaifangyuanzi2--chang.jpg" title="开放原子基金会2023快速成长开源项目" /></td>
|
||||
<td align="center" width="330"><img src="https://sa-token.cc/big-file/index/awards-zip/dromara-2024-tzds.jpg" title="Dromara 荣获《2024中国互联网发展创新与投资大赛(开源)》二等奖" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
### 🚀 优秀开源集成案例
|
||||
|
||||
- [[ Snowy ]](https://gitee.com/xiaonuobase/snowy):国内首个国密前后分离快速开发平台,采用 Vue3 + Vite + SpringBoot + Mp + HuTool + SaToken。
|
||||
- [[ RuoYi-Vue-Plus ]](https://gitee.com/dromara/RuoYi-Vue-Plus):重写RuoYi-Vue所有功能 集成 Sa-Token、Mybatis-Plus、Xxl-Job、knife4j、OSS 定期同步。
|
||||
- [[ Smart-Admin ]](https://gitee.com/lab1024/smart-admin):SmartAdmin 国内首个以「高质量代码」为核心,「简洁、高效、安全」中后台快速开发平台。
|
||||
- [[ 橙单 ]](https://gitee.com/orangeform/orange-admin): 橙单中台化低代码生成器。可完整支持多应用、多租户、多渠道、工作流、框架技术栈自由组合等。
|
||||
- [[ 灯灯 ]](https://gitee.com/dromara/lamp-cloud): 专注于多租户解决方案的中后台快速开发平台。支持独立数据库、共享数据架构 和 非租户模式 ✨
|
||||
- [[ 拾壹博客 ]](https://gitee.com/quequnlong/shiyi-blog):一款 vue + springboot 前后端分离的博客系统。
|
||||
|
||||
- [[ Snowy ]](https://gitee.com/xiaonuobase/snowy):国内首个国密前后分离快速开发平台,采用 Vue3 + AntDesignVue3 + Vite + SpringBoot + Mp + HuTool + SaToken。
|
||||
- [[ RuoYi-Vue-Plus ]](https://gitee.com/dromara/RuoYi-Vue-Plus):重写RuoYi-Vue所有功能 集成 Sa-Token+Mybatis-Plus+Jackson+Xxl-Job+knife4j+Hutool+OSS 定期同步
|
||||
- [[Smart-Admin]](https://gitee.com/lab1024/smart-admin):SmartAdmin国内首个以「高质量代码」为核心,「简洁、高效、安全」中后台快速开发平台;
|
||||
- [[ 灯灯 ]](https://gitee.com/dromara/lamp-cloud): 专注于多租户解决方案的微服务中后台快速开发平台。租户模式支持独立数据库(DATASOURCE模式)、共享数据架构(COLUMN模式) 和 非租户模式(NONE模式)✨
|
||||
- [[ EasyAdmin ]](https://gitee.com/lakernote/easy-admin):一个基于SpringBoot2 + Sa-Token + Mybatis-Plus + Snakerflow + Layui 的后台管理系统,灵活多变可前后端分离,也可单体,内置代码生成器、权限管理、工作流引擎等
|
||||
- [[ sa-admin-server ]](https://gitee.com/wlf213/sa-admin-server): 基于 sa-admin-ui 的后台管理开发脚手架。
|
||||
|
||||
|
||||
还有更多优秀开源案例无法逐一展示,请参考:[Awesome-Sa-Token](https://gitee.com/sa-token/awesome-sa-token)
|
||||
|
||||
|
||||
### 友情链接
|
||||
### 🌍 其它语言版本
|
||||
|
||||
Sa-Token 社区成员贡献了多语言实现版本:
|
||||
|
||||
- Rust 版本:[https://gitee.com/sa-tokens/sa-token-rust](https://gitee.com/sa-tokens/sa-token-rust)
|
||||
- Go 版本:[https://gitee.com/sa-tokens/sa-token-go](https://gitee.com/sa-tokens/sa-token-go)
|
||||
- PHP 版本:[https://gitee.com/jinan-jimeng-network_0/real-token](https://gitee.com/jinan-jimeng-network_0/real-token)
|
||||
|
||||
我们诚邀对上述语言较为熟练的开发者,一起建设相关版本。🤝
|
||||
|
||||
|
||||
### 🔗 友情链接
|
||||
- [[ OkHttps ]](https://gitee.com/ejlchina-zhxu/okhttps):轻量级 http 通信框架,API无比优雅,支持 WebSocket、Stomp 协议
|
||||
- [[ Forest ]](https://gitee.com/dromara/forest):声明式与编程式双修,让天下没有难以发送的 HTTP 请求
|
||||
- [[ Bean Searcher ]](https://github.com/ejlchina/bean-searcher):专注高级查询的只读 ORM,使一行代码实现复杂列表检索!
|
||||
@@ -209,31 +293,44 @@ Sa-Token-OAuth2 模块分为四种授权模式,解决不同场景下的授权
|
||||
|
||||
|
||||
|
||||
### 代码托管
|
||||
### 📦 代码托管
|
||||
- Gitee:[https://gitee.com/dromara/sa-token](https://gitee.com/dromara/sa-token)
|
||||
- GitHub:[https://github.com/dromara/sa-token](https://github.com/dromara/sa-token)
|
||||
- GitCode:[https://gitcode.com/dromara/sa-token](https://gitcode.com/dromara/sa-token)
|
||||
- AtomGit:[https://atomgit.com/dromara/sa-token](https://atomgit.com/dromara/sa-token)
|
||||
|
||||
|
||||
|
||||
### 交流群
|
||||
### 📚 示例大全
|
||||
|
||||
**我们为框架几乎所有技术点均单独制作了对应的集成示例,此压缩包共计 60+ Demo**:涵盖 Sa-Token 登录认证、权限认证、SSO 单点登录、OAUth2 统一认证、微服务鉴权、API Key 认证、JWT集成、跨系统调用参数签名校验 等鉴权认证的方方面面。
|
||||
|
||||
下载地址:[https://sa-token.cc/doc.html#/more/download-demos](https://sa-token.cc/doc.html#/more/download-demos)
|
||||
|
||||
<img class="s-w" src="https://sa-token.cc/big-file/contact/show/sa-token-demos-pre-liubai.png" />
|
||||
|
||||
|
||||
|
||||
### 💬 交流群
|
||||
<!-- QQ交流群:685792424 [点击加入](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Y05Ld4125W92YSwZ0gA8e3RhG9Q4Vsfx&authKey=IomXuIuhP9g8G7l%2ByfkrRsS7i%2Fna0lIBpkTXxx%2BQEaz0NNEyJq00kgeiC4dUyNLS&noverify=0&group_code=685792424)-->
|
||||
|
||||
QQ交流群:823181187 [点击加入](https://qm.qq.com/q/EBIJVZBVGE)
|
||||
QQ交流群:1081649142 [点击加入](https://qm.qq.com/q/SCAaZ6Ros2)
|
||||
|
||||
微信交流群:
|
||||
|
||||
<!-- <img src="https://oss.dev33.cn/sa-token/qr/wx-qr-m-400k.png" width="230px" title="微信群" /> -->
|
||||
|
||||
<img src="https://oss.dev33.cn/sa-token/qr/i-wx-qr2.png" width="230px" title="微信群" />
|
||||
<img src="https://sa-token.cc/big-file/contact/i-wx-qr2.jpg" width="230px" title="微信群" />
|
||||
|
||||
(扫码添加微信,备注:sa-token,邀您加入群聊)
|
||||
PS:扫码添加微信 (备注:sa-token),邀您加入群聊。
|
||||
|
||||
<br>
|
||||
|
||||
<img class="s-w" src="https://sa-token.cc/big-file/contact/show/wx-group-show3--liubai.png" style="max-width: 50%;" alt="微信群" />
|
||||
|
||||
|
||||
加入群聊的好处:
|
||||
- 第一时间收到框架更新通知。
|
||||
- 第一时间收到框架 bug 通知。
|
||||
- 第一时间收到新增开源案例通知。
|
||||
- 和众多大佬一起互相 (huá shuǐ) 交流 (mō yú)。
|
||||
- 和众多大佬一起互相 (huá shuǐ) 交流 (mō yú) 🖐️🐟️。
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ cd sa-token-demo-solon & call mvn clean & cd ..
|
||||
cd sa-token-demo-solon-redisson & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot3-redis & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot4-redis & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot-low-version & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot-redis & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot-redisson & call mvn clean & cd ..
|
||||
|
||||
@@ -12,13 +12,14 @@
|
||||
|
||||
<!-- 项目介绍 -->
|
||||
<name>sa-token</name>
|
||||
<description>A Java Web lightweight authority authentication framework, comprehensive function, easy to use</description>
|
||||
<description>An open-source, free, and one-stop Java authentication framework that makes authentication simple and elegant!</description>
|
||||
<url>https://github.com/dromara/sa-token</url>
|
||||
|
||||
|
||||
<!-- 所有模块 -->
|
||||
<modules>
|
||||
<module>sa-token-dependencies</module>
|
||||
<module>sa-token-special-dependencies</module>
|
||||
<module>sa-token-bom</module>
|
||||
<module>sa-token-core</module>
|
||||
<module>sa-token-starter</module>
|
||||
@@ -37,10 +38,14 @@
|
||||
|
||||
<!-- 一些属性 -->
|
||||
<properties>
|
||||
<revision>1.44.0</revision>
|
||||
<revision>1.45.0</revision>
|
||||
<jdk.version>1.8</jdk.version>
|
||||
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
|
||||
|
||||
<!-- Maven GPG Plugin & Maven Central Portal -->
|
||||
<maven-gpg-plugin.version>3.2.8</maven-gpg-plugin.version>
|
||||
<central.publishing.maven.version>0.10.0</central.publishing.maven.version>
|
||||
</properties>
|
||||
|
||||
<!-- 仓库信息 -->
|
||||
@@ -54,20 +59,11 @@
|
||||
<!-- 作者信息 -->
|
||||
<developers>
|
||||
<developer>
|
||||
<name>shengzhang</name>
|
||||
<name>click33</name>
|
||||
<email>2393584716@qq.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
|
||||
<!-- 父仓库 -->
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
<artifactId>oss-parent</artifactId>
|
||||
<version>7</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 仓库依赖 -->
|
||||
<dependencies>
|
||||
|
||||
@@ -75,7 +71,11 @@
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
|
||||
|
||||
<!--
|
||||
导入 sa-token-dependencies 所有版本定义,并传导到每个子项目。
|
||||
需要注意的是:该 import 只会导入 <dependencyManagement> 部分,而不会导入 <dependencies> 部分和 <properties> 部分。
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dependencies</artifactId>
|
||||
@@ -89,79 +89,133 @@
|
||||
|
||||
<!-- 项目构建 -->
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.10.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<configuration>
|
||||
<!-- 统一生成聚合文档,解决 mvn package 时控制台发出 javadoc 警告的问题 -->
|
||||
<aggregate>true</aggregate>
|
||||
<!-- 忽略部分 error 和 warning -->
|
||||
<failOnError>false</failOnError>
|
||||
<failOnWarnings>false</failOnWarnings>
|
||||
<additionalOptions>-Xdoclint:none</additionalOptions>
|
||||
<detectLinks>false</detectLinks>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>aggregate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugins>
|
||||
|
||||
<!-- 统一版本号管理 -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>flatten-maven-plugin</artifactId>
|
||||
<version>1.2.7</version>
|
||||
<configuration>
|
||||
<updatePomFile>true</updatePomFile>
|
||||
<flattenMode>resolveCiFriendliesOnly</flattenMode>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>flatten</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>flatten</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>flatten.clean</id>
|
||||
<phase>clean</phase>
|
||||
<goals>
|
||||
<goal>clean</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
<pluginManagement>
|
||||
<!-- Source -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<configuration>
|
||||
<attach>true</attach>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- 源码编译 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.15.0</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- API 文档 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.12.0</version>
|
||||
<configuration>
|
||||
<!-- 统一生成聚合文档,解决 mvn package 时控制台发出 javadoc 警告的问题 -->
|
||||
<!-- <aggregate>true</aggregate> -->
|
||||
<!-- 忽略部分 error 和 warning -->
|
||||
<failOnError>false</failOnError>
|
||||
<failOnWarnings>false</failOnWarnings>
|
||||
<additionalOptions>-Xdoclint:none</additionalOptions>
|
||||
<detectLinks>false</detectLinks>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>aggregate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<doclint>none</doclint>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- flatten 统一版本号管理 -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>flatten-maven-plugin</artifactId>
|
||||
<version>1.7.3</version>
|
||||
<configuration>
|
||||
<updatePomFile>true</updatePomFile>
|
||||
<flattenMode>resolveCiFriendliesOnly</flattenMode>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>flatten</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>flatten</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>flatten.clean</id>
|
||||
<phase>clean</phase>
|
||||
<goals>
|
||||
<goal>clean</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- gpg 签名 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>${maven-gpg-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- 新版 Central Portal 中央仓库上传 -->
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>${central.publishing.maven.version}</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<!-- 必须与 settings.xml 中 server 的 id 一致 -->
|
||||
<publishingServerId>central</publishingServerId>
|
||||
<!-- 是否自动发布。设为 true 后,上传完成无需手动点击发布 -->
|
||||
<!-- <autoPublish>true</autoPublish> -->
|
||||
<!-- 等待直到发布完成,让构建过程等待最终结果 -->
|
||||
<!-- <waitUntil>published</waitUntil> -->
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.eclipse.m2e</groupId>
|
||||
@@ -188,7 +242,8 @@
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</pluginManagement>
|
||||
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
+100
-13
@@ -13,9 +13,39 @@
|
||||
<url>https://github.com/dromara/sa-token</url>
|
||||
|
||||
<properties>
|
||||
<revision>1.44.0</revision>
|
||||
<revision>1.45.0</revision>
|
||||
|
||||
<!-- Maven GPG Plugin & Maven Central Portal -->
|
||||
<maven-gpg-plugin.version>3.2.8</maven-gpg-plugin.version>
|
||||
<central.publishing.maven.version>0.10.0</central.publishing.maven.version>
|
||||
</properties>
|
||||
|
||||
<!-- 开源协议 apache 2.0 -->
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache 2</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
<comments>A business-friendly OSS license</comments>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<!-- 仓库信息 -->
|
||||
<scm>
|
||||
<tag>master</tag>
|
||||
<url>https://github.com/dromara/sa-token.git</url>
|
||||
<connection>scm:git:https://github.com/dromara/sa-token.git</connection>
|
||||
<developerConnection>scm:git:https://github.com/dromara/sa-token.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
<!-- 作者信息 -->
|
||||
<developers>
|
||||
<developer>
|
||||
<name>click33</name>
|
||||
<email>2393584716@qq.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- sa-token 核心 -->
|
||||
@@ -51,6 +81,11 @@
|
||||
<artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-reactor-spring-boot4-starter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-servlet</artifactId>
|
||||
@@ -68,7 +103,17 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-autoconfig</artifactId>
|
||||
<artifactId>sa-token-spring-boot-webmvc-reactor-v2v3v4-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-reactor-v2v3v4-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-webmvc-v3v4-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -81,6 +126,11 @@
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot4-starter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- endregion-->
|
||||
|
||||
<!-- region sa-token-plugin -->
|
||||
@@ -94,6 +144,11 @@
|
||||
<artifactId>sa-token-alone-redis</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-alone-redis-by-spring-boot4</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dubbo</artifactId>
|
||||
@@ -119,6 +174,11 @@
|
||||
<artifactId>sa-token-jackson</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-jackson3</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-fastjson</artifactId>
|
||||
@@ -239,21 +299,15 @@
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<!-- 父仓库 -->
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
<artifactId>oss-parent</artifactId>
|
||||
<version>7</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 项目构建 -->
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<!-- 源码编译 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.10.1</version>
|
||||
<version>3.15.0</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
@@ -261,11 +315,11 @@
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- 统一版本号管理 -->
|
||||
<!-- flatten 统一版本号管理 -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>flatten-maven-plugin</artifactId>
|
||||
<version>1.2.7</version>
|
||||
<version>1.7.3</version>
|
||||
<configuration>
|
||||
<updatePomFile>true</updatePomFile>
|
||||
<flattenMode>resolveCiFriendliesOnly</flattenMode>
|
||||
@@ -288,7 +342,40 @@
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- gpg 签名 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>${maven-gpg-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- 新版 Central Portal 中央仓库上传 -->
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>${central.publishing.maven.version}</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<!-- 必须与 settings.xml 中 server 的 id 一致 -->
|
||||
<publishingServerId>central</publishingServerId>
|
||||
<!-- 是否自动发布。设为 true 后,上传完成无需手动点击发布 -->
|
||||
<!-- <autoPublish>true</autoPublish> -->
|
||||
<!-- 等待直到发布完成,让构建过程等待最终结果 -->
|
||||
<!-- <waitUntil>published</waitUntil> -->
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
|
||||
<name>sa-token-core</name>
|
||||
<artifactId>sa-token-core</artifactId>
|
||||
<description>A Java Web lightweight authority authentication framework, comprehensive function, easy to use</description>
|
||||
<description>An open-source, free, and one-stop Java authentication framework that makes authentication simple and elegant!</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Zero dependence -->
|
||||
<!-- Zero Dependence -->
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package cn.dev33.satoken.application;
|
||||
|
||||
import cn.dev33.satoken.fun.SaRetFunction;
|
||||
import cn.dev33.satoken.fun.SaRetGenericFunction;
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@ package cn.dev33.satoken.config;
|
||||
|
||||
import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode;
|
||||
import cn.dev33.satoken.stp.parameter.enums.SaLogoutRange;
|
||||
import cn.dev33.satoken.stp.parameter.enums.SaReplacedLoginExitMode;
|
||||
import cn.dev33.satoken.stp.parameter.enums.SaReplacedRange;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
@@ -64,6 +65,11 @@ public class SaTokenConfig implements Serializable {
|
||||
*/
|
||||
private Boolean isShare = false;
|
||||
|
||||
/**
|
||||
* 在 isConcurrent=false 时,决定新旧设备谁将放弃会话 (OLD_DEVICE=旧设备下线,新设备登录成功, NEW_DEVICE=新设备登录失败,旧设备维持在线)
|
||||
*/
|
||||
private SaReplacedLoginExitMode replacedLoginExitMode = SaReplacedLoginExitMode.OLD_DEVICE;
|
||||
|
||||
/**
|
||||
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
|
||||
*/
|
||||
@@ -713,6 +719,22 @@ public class SaTokenConfig implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 在 isConcurrent=false 时,决定新旧设备谁将放弃会话 (OLD_DEVICE=旧设备下线,新设备登录成功, NEW_DEVICE=新设备登录失败,旧设备维持在线)
|
||||
*/
|
||||
public SaReplacedLoginExitMode getReplacedLoginExitMode() {
|
||||
return replacedLoginExitMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param replacedLoginExitMode 在 isConcurrent=false 时,决定新旧设备谁将放弃会话 (OLD_DEVICE=旧设备下线,新设备登录成功, NEW_DEVICE=新设备登录失败,旧设备维持在线)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setReplacedLoginExitMode(SaReplacedLoginExitMode replacedLoginExitMode) {
|
||||
this.replacedLoginExitMode = replacedLoginExitMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端 ALL_DEVICE_TYPE=所有设备类型端)
|
||||
*
|
||||
@@ -860,6 +882,7 @@ public class SaTokenConfig implements Serializable {
|
||||
+ ", isConcurrent=" + isConcurrent
|
||||
+ ", isShare=" + isShare
|
||||
+ ", replacedRange=" + replacedRange
|
||||
+ ", replacedLoginExitMode=" + replacedLoginExitMode
|
||||
+ ", maxLoginCount=" + maxLoginCount
|
||||
+ ", overflowLogoutMode=" + overflowLogoutMode
|
||||
+ ", maxTryTimes=" + maxTryTimes
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import cn.dev33.satoken.context.model.SaTokenContextModelBox;
|
||||
*
|
||||
* <p>
|
||||
* 使用 [ ThreadLocal 版本 ] 上下文处理器需要在全局过滤器或者拦截器内率先调用
|
||||
* SaTokenContextForThreadLocalStorage.setBox(req, res, sto) 初始化上下文
|
||||
* SaTokenContextForThreadLocalStaff.setBox(req, res, sto) 初始化上下文
|
||||
* </p>
|
||||
*
|
||||
* <p> 一般情况下你不需要直接操作此类,因为框架的 starter 集成包里已经封装了完整的上下文操作 </p>
|
||||
|
||||
@@ -54,7 +54,7 @@ public interface SaTokenDao {
|
||||
*
|
||||
* @param key 键名称
|
||||
* @param value 值
|
||||
* @param timeout 数据有效期(值大于0时限时存储,值=-1时永久存储,值=0或小于-2时不存储)
|
||||
* @param timeout 数据有效期(值大于0时限时存储,值=-1时永久存储,值=0或小于等于-2时不存储)
|
||||
*/
|
||||
void set(String key, String value, long timeout);
|
||||
|
||||
@@ -109,7 +109,7 @@ public interface SaTokenDao {
|
||||
*
|
||||
* @param key 键名称
|
||||
* @param object 值
|
||||
* @param timeout 存活时间(值大于0时限时存储,值=-1时永久存储,值=0或小于-2时不存储)
|
||||
* @param timeout 存活时间(值大于0时限时存储,值=-1时永久存储,值=0或小于等于-2时不存储)
|
||||
*/
|
||||
void setObject(String key, Object object, long timeout);
|
||||
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ public interface SaTokenDaoByObjectFollowString extends SaTokenDaoBySessionFollo
|
||||
*
|
||||
* @param key 键名称
|
||||
* @param object 值
|
||||
* @param timeout 存活时间(值大于0时限时存储,值=-1时永久存储,值=0或小于-2时不存储)
|
||||
* @param timeout 存活时间(值大于0时限时存储,值=-1时永久存储,值=0或小于等于-2时不存储)
|
||||
*/
|
||||
@Override
|
||||
default void setObject(String key, Object object, long timeout) {
|
||||
|
||||
@@ -80,6 +80,9 @@ public interface SaErrorCode {
|
||||
/** 更改 Token 指向的 账号Id 时,账号Id值为空 */
|
||||
int CODE_11003 = 11003;
|
||||
|
||||
/** 登录失败:当前账号已在其它客户端登录 */
|
||||
int CODE_11004 = 11004;
|
||||
|
||||
/** 未能读取到有效Token */
|
||||
int CODE_11011 = 11011;
|
||||
|
||||
|
||||
@@ -16,9 +16,7 @@
|
||||
package cn.dev33.satoken.httpauth.digest;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckHttpDigest;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,6 +36,7 @@ import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
|
||||
import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode;
|
||||
import cn.dev33.satoken.stp.parameter.enums.SaLogoutRange;
|
||||
import cn.dev33.satoken.stp.parameter.enums.SaReplacedLoginExitMode;
|
||||
import cn.dev33.satoken.stp.parameter.enums.SaReplacedRange;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
@@ -538,13 +539,25 @@ public class StpLogic {
|
||||
protected String distUsableToken(Object id, SaLoginParameter loginParameter) {
|
||||
|
||||
// 1、获取全局配置的 isConcurrent 参数
|
||||
// 如果配置为:不允许一个账号多地同时登录,则需要先将这个账号的历史登录会话标记为:被顶下线
|
||||
if( ! loginParameter.getIsConcurrent()) {
|
||||
if(loginParameter.getReplacedRange() == SaReplacedRange.CURR_DEVICE_TYPE) {
|
||||
replaced(id, loginParameter.getDeviceType());
|
||||
}
|
||||
if(loginParameter.getReplacedRange() == SaReplacedRange.ALL_DEVICE_TYPE) {
|
||||
replaced(id, createSaLogoutParameter());
|
||||
// 如果配置为:不允许一个账号多地同时登录,则需要根据配置选择:
|
||||
// 一.将这个账号的历史登录会话标记为:被顶下线
|
||||
// 二.提示错误并拒绝本次登录
|
||||
if (loginParameter.getReplacedLoginExitMode() == SaReplacedLoginExitMode.OLD_DEVICE){
|
||||
if(loginParameter.getReplacedRange() == SaReplacedRange.CURR_DEVICE_TYPE) {
|
||||
replaced(id, loginParameter.getDeviceType());
|
||||
}
|
||||
if(loginParameter.getReplacedRange() == SaReplacedRange.ALL_DEVICE_TYPE) {
|
||||
replaced(id, createSaLogoutParameter());
|
||||
}
|
||||
} else if (loginParameter.getReplacedLoginExitMode() == SaReplacedLoginExitMode.NEW_DEVICE){
|
||||
List<SaTerminalInfo> terminalListByLoginId = getTerminalListByLoginId(id);
|
||||
// 只有当存在有效地会话时才拒绝登录
|
||||
boolean hasActiveSession = terminalListByLoginId.stream()
|
||||
.anyMatch(terminal -> isValidToken(terminal.getTokenValue()));
|
||||
if (hasActiveSession) {
|
||||
throw new SaTokenException("登录失败:当前账号已在其它客户端登录").setCode(SaErrorCode.CODE_11004);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -548,7 +548,7 @@ public class StpUtil {
|
||||
* @param tokenValue token
|
||||
* @return 账号id
|
||||
*/
|
||||
public Object getLoginIdByTokenNotThinkFreeze(String tokenValue) {
|
||||
public static Object getLoginIdByTokenNotThinkFreeze(String tokenValue) {
|
||||
return stpLogic.getLoginIdByTokenNotThinkFreeze(tokenValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.fun.SaParamFunction;
|
||||
import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode;
|
||||
import cn.dev33.satoken.stp.parameter.enums.SaReplacedLoginExitMode;
|
||||
import cn.dev33.satoken.stp.parameter.enums.SaReplacedRange;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
|
||||
@@ -110,6 +111,11 @@ public class SaLoginParameter {
|
||||
*/
|
||||
private Boolean isWriteHeader;
|
||||
|
||||
/**
|
||||
* 在 isConcurrent=false 时,决定新旧设备谁将放弃会话
|
||||
*/
|
||||
private SaReplacedLoginExitMode replacedLoginExitMode;
|
||||
|
||||
/**
|
||||
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
|
||||
*/
|
||||
@@ -158,6 +164,7 @@ public class SaLoginParameter {
|
||||
this.replacedRange = config.getReplacedRange();
|
||||
this.overflowLogoutMode = config.getOverflowLogoutMode();
|
||||
this.rightNowCreateTokenSession = config.getRightNowCreateTokenSession();
|
||||
this.replacedLoginExitMode = config.getReplacedLoginExitMode();
|
||||
|
||||
this.setupCookieConfig(cookie -> {
|
||||
SaCookieConfig gCookie = config.getCookie();
|
||||
@@ -568,6 +575,25 @@ public class SaLoginParameter {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取:在 isConcurrent=false 时,决定新旧设备谁将放弃会话 (OLD_DEVICE=旧设备下线,新设备登录成功, NEW_DEVICE=新设备登录失败,旧设备维持在线)
|
||||
* @return /
|
||||
*/
|
||||
public SaReplacedLoginExitMode getReplacedLoginExitMode() {
|
||||
return replacedLoginExitMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置:在 isConcurrent=false 时,决定新旧设备谁将放弃会话 (OLD_DEVICE=旧设备下线,新设备登录成功, NEW_DEVICE=新设备登录失败,旧设备维持在线)
|
||||
* @param replacedLoginExitMode /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setReplacedLoginExitMode(SaReplacedLoginExitMode replacedLoginExitMode) {
|
||||
this.replacedLoginExitMode = replacedLoginExitMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* toString
|
||||
*/
|
||||
@@ -577,6 +603,7 @@ public class SaLoginParameter {
|
||||
+ "deviceType=" + deviceType
|
||||
+ ", deviceId=" + deviceId
|
||||
+ ", replacedRange=" + replacedRange
|
||||
+ ", replacedLoginExitMode=" + replacedLoginExitMode
|
||||
+ ", overflowLogoutMode=" + overflowLogoutMode
|
||||
+ ", isLastingCookie=" + isLastingCookie
|
||||
+ ", timeout=" + timeout
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.stp.parameter.enums;
|
||||
|
||||
/**
|
||||
* 在 isConcurrent=false 时,决定新旧设备谁将放弃会话
|
||||
* @author 石泽旭
|
||||
* @since 1.44.0
|
||||
*/
|
||||
public enum SaReplacedLoginExitMode {
|
||||
|
||||
/**
|
||||
* 旧设备下线,新设备登录成功
|
||||
*/
|
||||
OLD_DEVICE,
|
||||
|
||||
/**
|
||||
* 新设备登录失败,旧设备维持在线
|
||||
*/
|
||||
NEW_DEVICE
|
||||
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public class SaTokenConsts {
|
||||
/**
|
||||
* Sa-Token 当前版本号
|
||||
*/
|
||||
public static final String VERSION_NO = "v1.44.0";
|
||||
public static final String VERSION_NO = "v1.45.0";
|
||||
|
||||
/**
|
||||
* Sa-Token 开源地址 Gitee
|
||||
|
||||
@@ -28,12 +28,34 @@ package cn.dev33.satoken.util;
|
||||
public class StrFormatter {
|
||||
|
||||
/**
|
||||
* 占位符
|
||||
* 占位符(保留原有 public 访问权限,避免破坏外部依赖)
|
||||
* @deprecated 语义不明确,建议内部使用 {@link #DEFAULT_PLACEHOLDER} 替代
|
||||
*/
|
||||
@Deprecated
|
||||
public static String EMPTY_JSON = "{}";
|
||||
|
||||
|
||||
/**
|
||||
* 反斜杠转义字符(保留原有 public 访问权限,避免破坏外部依赖)
|
||||
* @deprecated 命名不规范,建议内部使用 {@link #BACKSLASH_CHAR} 替代
|
||||
*/
|
||||
@Deprecated
|
||||
public static char C_BACKSLASH = '\\';
|
||||
|
||||
|
||||
/**
|
||||
* 新增内部规范常量(private,仅内部使用)
|
||||
* 默认占位符 */
|
||||
private static final String DEFAULT_PLACEHOLDER = "{}";
|
||||
|
||||
/**
|
||||
* 反斜杠转义字符
|
||||
* */
|
||||
private static final char BACKSLASH_CHAR = '\\';
|
||||
|
||||
/**
|
||||
* 字符串构建器初始扩容长度
|
||||
* */
|
||||
private static final int BUFFER_INIT_CAPACITY = 50;
|
||||
|
||||
/**
|
||||
* 格式化字符串<br>
|
||||
* 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
|
||||
@@ -45,10 +67,10 @@ public class StrFormatter {
|
||||
*
|
||||
* @param strPattern 字符串模板
|
||||
* @param argArray 参数列表
|
||||
* @return 结果
|
||||
* @return 格式化后的结果
|
||||
*/
|
||||
public static String format(String strPattern, Object... argArray) {
|
||||
return formatWith(strPattern, EMPTY_JSON, argArray);
|
||||
return formatWith(strPattern, DEFAULT_PLACEHOLDER, argArray);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +85,7 @@ public class StrFormatter {
|
||||
* @param strPattern 字符串模板
|
||||
* @param placeHolder 占位符,例如{}
|
||||
* @param argArray 参数列表
|
||||
* @return 结果
|
||||
* @return 格式化后的结果
|
||||
* @since 1.33.0
|
||||
*/
|
||||
public static String formatWith(String strPattern, String placeHolder, Object... argArray) {
|
||||
@@ -74,7 +96,7 @@ public class StrFormatter {
|
||||
final int placeHolderLength = placeHolder.length();
|
||||
|
||||
// 初始化定义好的长度以获得更好的性能
|
||||
final StringBuilder sbu = new StringBuilder(strPatternLength + 50);
|
||||
final StringBuilder sbu = new StringBuilder(strPatternLength + BUFFER_INIT_CAPACITY);
|
||||
|
||||
int handledPosition = 0;// 记录已经处理到的位置
|
||||
int delimIndex;// 占位符所在位置
|
||||
@@ -90,8 +112,8 @@ public class StrFormatter {
|
||||
}
|
||||
|
||||
// 转义符
|
||||
if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) {// 转义符
|
||||
if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) {// 双转义符
|
||||
if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == BACKSLASH_CHAR) {// 转义符
|
||||
if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == BACKSLASH_CHAR) {// 双转义符
|
||||
// 转义符之前还有一个转义符,占位符依旧有效
|
||||
sbu.append(strPattern, handledPosition, delimIndex - 1);
|
||||
sbu.append(argArray[argIndex]);
|
||||
@@ -116,4 +138,4 @@ public class StrFormatter {
|
||||
return sbu.toString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
<modules>
|
||||
<module>sa-token-demo-alone-redis</module>
|
||||
<module>sa-token-demo-alone-redis-cluster</module>
|
||||
<module>sa-token-demo-alone-redis-sb4</module>
|
||||
<module>sa-token-demo-apikey</module>
|
||||
<module>sa-token-demo-async</module>
|
||||
<module>sa-token-demo-beetl</module>
|
||||
@@ -35,6 +36,7 @@
|
||||
<module>sa-token-demo-solon-redisson</module>
|
||||
<module>sa-token-demo-springboot</module>
|
||||
<module>sa-token-demo-springboot3-redis</module>
|
||||
<module>sa-token-demo-springboot4-redis</module>
|
||||
<module>sa-token-demo-springboot-low-version</module>
|
||||
<module>sa-token-demo-springboot-redis</module>
|
||||
<module>sa-token-demo-springboot-redisson</module>
|
||||
@@ -55,6 +57,7 @@
|
||||
<module>sa-token-demo-thymeleaf</module>
|
||||
<module>sa-token-demo-webflux</module>
|
||||
<module>sa-token-demo-webflux-springboot3</module>
|
||||
<module>sa-token-demo-webflux-springboot4</module>
|
||||
<module>sa-token-demo-websocket</module>
|
||||
<module>sa-token-demo-websocket-spring</module>
|
||||
<module>sa-token-demo-loveqq-boot</module>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-demo-alone-redis-sb4</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
||||
<!-- SpringBoot 4 -->
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>4.0.3</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- SpringBoot 4 依赖:webmvc 替代 deprecated 的 starter-web,aspectj 替代 starter-aop -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webmvc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aspectj</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot4-starter</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token整合 Redis -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-template</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token整合 Redis -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-template-jdk-serializer</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 提供Redis连接池 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token插件:权限缓存与业务缓存分离(Spring Boot 4) -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-alone-redis-by-spring-boot4</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- @ConfigurationProperties -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.pj;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* Sa-Token 整合 SpringBoot4 示例,整合 alone-redis 插件(权限缓存与业务缓存分离)
|
||||
*
|
||||
* @author click33
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SaTokenAloneRedisSb4Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SaTokenAloneRedisSb4Application.class, args);
|
||||
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 测试专用Controller,演示 alone-redis 权限缓存与业务缓存分离
|
||||
*
|
||||
* @author click33
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/test/")
|
||||
public class TestController {
|
||||
|
||||
@Autowired
|
||||
StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
// 测试Sa-Token缓存,浏览器访问: http://localhost:8083/test/login
|
||||
@RequestMapping("login")
|
||||
public SaResult login(@RequestParam(defaultValue = "10001") String id) {
|
||||
System.out.println("--------------- 测试Sa-Token缓存");
|
||||
StpUtil.login(id);
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 测试业务缓存,浏览器访问: http://localhost:8083/test/test
|
||||
@RequestMapping("test")
|
||||
public SaResult test() {
|
||||
System.out.println("--------------- 测试业务缓存");
|
||||
stringRedisTemplate.opsForValue().set("hello", "Hello World");
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8083
|
||||
|
||||
# Sa-Token配置
|
||||
sa-token:
|
||||
# Token名称 (同时也是cookie名称)
|
||||
token-name: satoken
|
||||
# Token有效期,单位s 默认30天, -1代表永不过期
|
||||
timeout: 2592000
|
||||
# Token风格
|
||||
token-style: uuid
|
||||
# 配置Sa-Token单独使用的Redis连接
|
||||
alone-redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 2
|
||||
# Redis服务器地址
|
||||
host: 127.0.0.1
|
||||
# Redis服务器连接端口
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
# password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
max-active: 200
|
||||
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 10
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
|
||||
# 配置业务使用的Redis连接
|
||||
spring:
|
||||
data:
|
||||
redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 0
|
||||
# Redis服务器地址
|
||||
host: 127.0.0.1
|
||||
# Redis服务器连接端口
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
max-active: 200
|
||||
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 10
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
<java.run.main.class>com.pj.SaTokenAsyncApplication</java.run.main.class>
|
||||
</properties>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -73,7 +73,7 @@
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-bom</artifactId>
|
||||
<version>1.44.0</version>
|
||||
<version>1.45.0</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
<java.run.main.class>com.pj.SaTokenDeviceLockApplication</java.run.main.class>
|
||||
</properties>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
<dubbo.version>2.7.21</dubbo.version>
|
||||
<nacos.version>1.4.2</nacos.version>
|
||||
</properties>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
<dubbo.version>2.7.21</dubbo.version>
|
||||
<nacos.version>1.4.2</nacos.version>
|
||||
</properties>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
<dubbo.version>3.2.2</dubbo.version>
|
||||
<nacos.version>2.2.2</nacos.version>
|
||||
</properties>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
<dubbo.version>3.2.2</dubbo.version>
|
||||
<nacos.version>2.2.2</nacos.version>
|
||||
</properties>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<lombok.version>1.18.10</lombok.version>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<java.version>17</java.version>
|
||||
<maven.source.version>17</maven.source.version>
|
||||
<maven.compile.version>17</maven.compile.version>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
|
||||
<div style="height: 200px;"></div>
|
||||
</div>
|
||||
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
||||
<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
||||
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
|
||||
<script>window.jQuery || alert('当前页面CDN服务商已宕机,请将所有js包更换为本地依赖')</script>
|
||||
<!-- 配置缓存读取 -->
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
</properties>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.traget>17</maven.compiler.traget>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
</properties>
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import cn.dev33.satoken.exception.SaTokenException;
|
||||
import com.pj.satoken.custom_annotation.CheckAccount;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
|
||||
/**
|
||||
* 注解 CheckAccount 的处理器
|
||||
@@ -25,7 +25,7 @@ public class CheckAccountHandler implements SaAnnotationHandlerInterface<CheckAc
|
||||
|
||||
// 每次请求校验注解时,会执行的方法
|
||||
@Override
|
||||
public void checkMethod(CheckAccount at, Method method) {
|
||||
public void checkMethod(CheckAccount at, AnnotatedElement method) {
|
||||
// 获取前端请求提交的参数
|
||||
String name = SaHolder.getRequest().getParamNotNull("name");
|
||||
String pwd = SaHolder.getRequest().getParamNotNull("pwd");
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
<java.run.main.class>com.pj.SaTokenApplication</java.run.main.class>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<version>3.5.11</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -48,7 +48,7 @@
|
||||
<!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-jackson</artifactId>
|
||||
<artifactId>sa-token-redis-template</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-demo-springboot4-redis</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
||||
<!-- SpringBoot 4 -->
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>4.0.3</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- SpringBoot 4 依赖:webmvc 替代 deprecated 的 starter-web,aspectj 替代 starter-aop -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webmvc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aspectj</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot4-starter</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token整合 Redis -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-template</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 提供Redis连接池 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- @ConfigurationProperties -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.pj;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* Sa-Token 整合 SpringBoot4 示例,整合 redis
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SaTokenSpringBoot4Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SaTokenSpringBoot4Application.class, args);
|
||||
System.out.println("\n🎉 启动成功:Sa-Token配置如下:" + SaManager.getConfig());
|
||||
}
|
||||
|
||||
}
|
||||
+56
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
//package com.pj.current;
|
||||
//
|
||||
//import java.io.IOException;
|
||||
//
|
||||
//import org.springframework.boot.web.servlet.error.ErrorController;
|
||||
//import org.springframework.web.bind.annotation.RequestMapping;
|
||||
//import org.springframework.web.bind.annotation.RestController;
|
||||
//
|
||||
//import cn.dev33.satoken.util.SaResult;
|
||||
//import jakarta.servlet.http.HttpServletRequest;
|
||||
//import jakarta.servlet.http.HttpServletResponse;
|
||||
//
|
||||
///**
|
||||
// * 处理 404
|
||||
// * @author click33
|
||||
// */
|
||||
//@RestController
|
||||
//public class NotFoundHandle implements ErrorController {
|
||||
//
|
||||
// @RequestMapping("/error")
|
||||
// public Object error(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
// response.setStatus(200);
|
||||
// return SaResult.get(404, "not found", null);
|
||||
// }
|
||||
//
|
||||
//}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 注册 Sa-Token 拦截器打开注解鉴权功能
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 注册 Sa-Token 拦截器打开注解鉴权功能
|
||||
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册 [Sa-Token 全局过滤器]
|
||||
*/
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
|
||||
// 指定 [拦截路由] 与 [放行路由]
|
||||
.addInclude("/**")// .addExclude("/favicon.ico")
|
||||
|
||||
// 认证函数: 每次请求执行
|
||||
.setAuth(obj -> {
|
||||
// System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath());
|
||||
|
||||
})
|
||||
|
||||
// 异常处理函数:每次认证函数发生异常时执行此函数
|
||||
.setError(e -> {
|
||||
System.out.println("---------- sa全局异常 ");
|
||||
e.printStackTrace();
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
|
||||
// 前置函数:在每次认证函数之前执行(BeforeAuth 不受 includeList 与 excludeList 的限制,所有请求都会进入)
|
||||
.setBeforeAuth(r -> {
|
||||
// ---------- 设置一些安全响应头 ----------
|
||||
SaHolder.getResponse()
|
||||
// 服务器名称
|
||||
.setServer("sa-server")
|
||||
// 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
|
||||
.setHeader("X-Frame-Options", "SAMEORIGIN")
|
||||
// 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
|
||||
.setHeader("X-XSS-Protection", "1; mode=block")
|
||||
// 禁用浏览器内容嗅探
|
||||
.setHeader("X-Content-Type-Options", "nosniff")
|
||||
;
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckHttpBasic;
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaCheckRole;
|
||||
import cn.dev33.satoken.annotation.SaCheckSafe;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 注解鉴权测试
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/at/")
|
||||
public class AtController {
|
||||
|
||||
// 登录认证,登录之后才可以进入方法 ---- http://localhost:8082/at/checkLogin
|
||||
@SaCheckLogin
|
||||
@RequestMapping("checkLogin")
|
||||
public SaResult checkLogin() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 权限认证,具备user-add权限才可以进入方法 ---- http://localhost:8082/at/checkPermission
|
||||
@SaCheckPermission("user-add")
|
||||
@RequestMapping("checkPermission")
|
||||
public SaResult checkPermission() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 权限认证,同时具备所有权限才可以进入 ---- http://localhost:8082/at/checkPermissionAnd
|
||||
@SaCheckPermission({"user-add", "user-delete", "user-update"})
|
||||
@RequestMapping("checkPermissionAnd")
|
||||
public SaResult checkPermissionAnd() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 权限认证,只要具备其中一个就可以进入 ---- http://localhost:8082/at/checkPermissionOr
|
||||
@SaCheckPermission(value = {"user-add", "user-delete", "user-update"}, mode = SaMode.OR)
|
||||
@RequestMapping("checkPermissionOr")
|
||||
public SaResult checkPermissionOr() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 角色认证,只有具备admin角色才可以进入 ---- http://localhost:8082/at/checkRole
|
||||
@SaCheckRole("admin")
|
||||
@RequestMapping("checkRole")
|
||||
public SaResult checkRole() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 完成二级认证 ---- http://localhost:8082/at/openSafe
|
||||
@RequestMapping("openSafe")
|
||||
public SaResult openSafe() {
|
||||
StpUtil.openSafe(200); // 打开二级认证,有效期为200秒
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 通过二级认证后才可以进入 ---- http://localhost:8082/at/checkSafe
|
||||
@SaCheckSafe
|
||||
@RequestMapping("checkSafe")
|
||||
public SaResult checkSafe() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 通过Basic认证后才可以进入 ---- http://localhost:8082/at/checkBasic
|
||||
@SaCheckHttpBasic(account = "sa:123456")
|
||||
@RequestMapping("checkBasic")
|
||||
public SaResult checkBasic() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class FaviconController {
|
||||
|
||||
@RequestMapping("/favicon.ico")
|
||||
public String favicon() {
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 登录测试
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/acc/")
|
||||
public class LoginController {
|
||||
|
||||
// 测试登录 ---- http://localhost:8082/acc/doLogin?name=zhang&pwd=123456
|
||||
@RequestMapping("doLogin")
|
||||
public SaResult doLogin(String name, String pwd) {
|
||||
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
|
||||
if("zhang".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok("登录成功");
|
||||
}
|
||||
return SaResult.error("登录失败");
|
||||
}
|
||||
|
||||
// 查询登录状态 ---- http://localhost:8082/acc/isLogin
|
||||
@RequestMapping("isLogin")
|
||||
public SaResult isLogin() {
|
||||
return SaResult.ok("是否登录:" + StpUtil.isLogin());
|
||||
}
|
||||
|
||||
// 查询 Token 信息 ---- http://localhost:8082/acc/tokenInfo
|
||||
@RequestMapping("tokenInfo")
|
||||
public SaResult tokenInfo() {
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
}
|
||||
|
||||
// 测试注销 ---- http://localhost:8082/acc/logout
|
||||
@RequestMapping("logout")
|
||||
public SaResult logout() {
|
||||
StpUtil.logout();
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.pj.test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.pj.util.Ttime;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 压力测试
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/s-test/")
|
||||
public class StressTestController {
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8082/s-test/login
|
||||
// 测试前,请先将 is-read-cookie 配置为 false
|
||||
@RequestMapping("login")
|
||||
public SaResult login() {
|
||||
// StpUtil.getTokenSession().logout();
|
||||
// StpUtil.logoutByLoginId(10001);
|
||||
|
||||
int count = 10; // 循环多少轮
|
||||
int loginCount = 10000; // 每轮循环多少次
|
||||
|
||||
// 循环10次 取平均时间
|
||||
List<Double> list = new ArrayList<>();
|
||||
for (int i = 1; i <= count; i++) {
|
||||
System.out.println("\n---------------------第" + i + "轮---------------------");
|
||||
Ttime t = new Ttime().start();
|
||||
// 每次登录的次数
|
||||
for (int j = 1; j <= loginCount; j++) {
|
||||
StpUtil.login("1000" + j, "PC-" + j);
|
||||
if(j % 1000 == 0) {
|
||||
System.out.println("已登录:" + j);
|
||||
}
|
||||
}
|
||||
t.end();
|
||||
list.add((t.returnMs() + 0.0) / 1000);
|
||||
System.out.println("第" + i + "轮" + "用时:" + t.toString());
|
||||
}
|
||||
// System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size());
|
||||
|
||||
System.out.println("\n---------------------测试结果---------------------");
|
||||
System.out.println(list.size() + "次测试: " + list);
|
||||
double ss = 0;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
ss += list.get(i);
|
||||
}
|
||||
System.out.println("平均用时: " + ss / list.size());
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.servlet.util.SaTokenContextJakartaServletUtil;
|
||||
import cn.dev33.satoken.spring.SpringMVCUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 测试专用Controller
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/test/")
|
||||
public class TestController {
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8082/test/test
|
||||
@RequestMapping("test")
|
||||
public SaResult test() {
|
||||
System.out.println("------------进来了");
|
||||
System.out.println(SpringMVCUtil.getRequest());
|
||||
System.out.println(SaTokenContextJakartaServletUtil.getRequest());
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8082/test/test2
|
||||
@RequestMapping("test2")
|
||||
public SaResult test2() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8082/test/getRequestPath
|
||||
@RequestMapping("getRequestPath")
|
||||
public SaResult getRequestPath() {
|
||||
System.out.println("-------------- 测试请求 path 获取");
|
||||
System.out.println("request.getRequestURI() " + SpringMVCUtil.getRequest().getRequestURI());
|
||||
System.out.println("saRequest.getRequestPath() " + SaHolder.getRequest().getRequestPath());
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+162
@@ -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=ok,false=error)
|
||||
public static AjaxJson getByBoolean(boolean b){
|
||||
return b ? getSuccess("ok") : getError("error");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public String toString() {
|
||||
String data_string = null;
|
||||
if(data == null){
|
||||
|
||||
} else if(data instanceof List){
|
||||
data_string = "List(length=" + ((List)data).size() + ")";
|
||||
} else {
|
||||
data_string = data.toString();
|
||||
}
|
||||
return "{"
|
||||
+ "\"code\": " + this.getCode()
|
||||
+ ", \"msg\": \"" + this.getMsg() + "\""
|
||||
+ ", \"data\": " + data_string
|
||||
+ ", \"dataCount\": " + dataCount
|
||||
+ "}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.pj.util;
|
||||
|
||||
|
||||
/**
|
||||
* 用于测试用时
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
public class Ttime {
|
||||
|
||||
private long start=0; //开始时间
|
||||
private long end=0; //结束时间
|
||||
|
||||
public static Ttime t = new Ttime(); //static快捷使用
|
||||
|
||||
/**
|
||||
* 开始计时
|
||||
* @return
|
||||
*/
|
||||
public Ttime start() {
|
||||
start=System.currentTimeMillis();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 结束计时
|
||||
*/
|
||||
public Ttime end() {
|
||||
end=System.currentTimeMillis();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回所用毫秒数
|
||||
*/
|
||||
public long returnMs() {
|
||||
return end-start;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化输出结果
|
||||
*/
|
||||
public void outTime() {
|
||||
System.out.println(this.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束并格式化输出结果
|
||||
*/
|
||||
public void endOutTime() {
|
||||
this.end().outTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (returnMs() + 0.0) / 1000 + "s"; // 格式化为:0.01s
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8082
|
||||
|
||||
# sa-token 配置
|
||||
sa-token:
|
||||
# token 名称 (同时也是 cookie 名称)
|
||||
token-name: satoken
|
||||
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
|
||||
timeout: 2592000
|
||||
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
||||
active-timeout: -1
|
||||
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
|
||||
is-share: false
|
||||
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
|
||||
token-style: uuid
|
||||
# 是否输出操作日志
|
||||
is-log: true
|
||||
|
||||
spring:
|
||||
data:
|
||||
# redis配置
|
||||
redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 1
|
||||
# Redis服务器地址
|
||||
host: 127.0.0.1
|
||||
# Redis服务器连接端口
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
max-active: 200
|
||||
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 10
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
<java.run.main.class>com.pj.SaTokenSseApplication</java.run.main.class>
|
||||
</properties>
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<!--<spring.version>4.2.5.RELEASE</spring.version>-->
|
||||
<spring.version>5.3.7</spring.version>
|
||||
<jackson.version>2.16.1</jackson.version>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// 服务端地址
|
||||
var baseUrl = "http://sa-sso-server.com:9000";
|
||||
|
||||
// sa
|
||||
var sa = {};
|
||||
|
||||
// 打开loading
|
||||
sa.loading = function(msg) {
|
||||
layer.closeAll(); // 开始前先把所有弹窗关了
|
||||
return layer.msg(msg, {icon: 16, shade: 0.3, time: 1000 * 20, skin: 'ajax-layer-load'});
|
||||
};
|
||||
|
||||
// 隐藏loading
|
||||
sa.hideLoading = function() {
|
||||
layer.closeAll();
|
||||
};
|
||||
|
||||
// 封装一下Ajax
|
||||
sa.ajax = function(url, data, successFn) {
|
||||
$.ajax({
|
||||
url: baseUrl + url,
|
||||
type: "post",
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'satoken': localStorage.getItem('satoken')
|
||||
},
|
||||
success: function(res){
|
||||
console.log('返回数据:', res);
|
||||
successFn(res);
|
||||
},
|
||||
error: function(xhr, type, errorThrown){
|
||||
if(xhr.status == 0){
|
||||
return alert('无法连接到服务器,请检查网络');
|
||||
}
|
||||
return alert("异常:" + JSON.stringify(xhr));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>SSO-Server 平台首页</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>SSO-Server 平台首页 (前后端分离模式) (平台中心模式)</h2>
|
||||
<p>
|
||||
<a href='sso-auth.html?client=sso-client3&redirect=http://sa-sso-client1.com:9003/sso/login?back=http://sa-sso-client1.com:9003/'
|
||||
target='_blank'> 进入Client1系统 </a>
|
||||
</p>
|
||||
<p>
|
||||
<a href='sso-auth.html?client=sso-client3&redirect=http://sa-sso-client2.com:9003/sso/login?back=http://sa-sso-client2.com:9003/'
|
||||
target='_blank'> 进入Client2系统 </a>
|
||||
</p>
|
||||
<p>
|
||||
<a href='sso-auth.html?client=sso-client3&redirect=http://sa-sso-client3.com:9003/sso/login?back=http://sa-sso-client3.com:9003/'
|
||||
target='_blank'> 进入Client3系统 </a>
|
||||
</p>
|
||||
|
||||
<!-- scripts -->
|
||||
<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
||||
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
|
||||
<script src="./common.js"></script>
|
||||
<script>
|
||||
sa.ajax("/sso/isLogin", {}, function(res) {
|
||||
if(res.data) {
|
||||
// 已登录...
|
||||
console.log('已登录,开始操作...');
|
||||
} else {
|
||||
layer.msg('未登录,请先登录...')
|
||||
setTimeout(function(){
|
||||
location.href = './sso-auth.html';
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,7 +4,7 @@
|
||||
<title>Sa-SSO-Server 认证中心-登录</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link rel="stylesheet" href="./login.css">
|
||||
<link rel="stylesheet" href="./sso-auth.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="view-box">
|
||||
@@ -35,9 +35,10 @@
|
||||
</div>
|
||||
|
||||
<!-- scripts -->
|
||||
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
||||
<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
||||
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
|
||||
<script src="./login.js"></script>
|
||||
<script src="./common.js"></script>
|
||||
<script src="./sso-auth.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+23
-50
@@ -1,43 +1,3 @@
|
||||
// 服务端地址
|
||||
var baseUrl = "http://sa-sso-server.com:9000";
|
||||
|
||||
// sa
|
||||
var sa = {};
|
||||
|
||||
// 打开loading
|
||||
sa.loading = function(msg) {
|
||||
layer.closeAll(); // 开始前先把所有弹窗关了
|
||||
return layer.msg(msg, {icon: 16, shade: 0.3, time: 1000 * 20, skin: 'ajax-layer-load'});
|
||||
};
|
||||
|
||||
// 隐藏loading
|
||||
sa.hideLoading = function() {
|
||||
layer.closeAll();
|
||||
};
|
||||
|
||||
// 封装一下Ajax
|
||||
sa.ajax = function(url, data, successFn) {
|
||||
$.ajax({
|
||||
url: baseUrl + url,
|
||||
type: "post",
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'satoken': localStorage.getItem('satoken')
|
||||
},
|
||||
success: function(res){
|
||||
console.log('返回数据:', res);
|
||||
successFn(res);
|
||||
},
|
||||
error: function(xhr, type, errorThrown){
|
||||
if(xhr.status == 0){
|
||||
return alert('无法连接到服务器,请检查网络');
|
||||
}
|
||||
return alert("异常:" + JSON.stringify(xhr));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------- 相关事件 -----------------------------------
|
||||
|
||||
@@ -47,16 +7,29 @@ var pData = {
|
||||
redirect: getParam('redirect', ''),
|
||||
mode: getParam('mode', '')
|
||||
};
|
||||
sa.ajax("/sso/getRedirectUrl", pData, function(res) {
|
||||
if(res.code == 200) {
|
||||
// 已登录,并且redirect地址有效,开始跳转
|
||||
location.href = res.data;
|
||||
} else if(res.code == 401) {
|
||||
console.log('未登录');
|
||||
} else {
|
||||
layer.alert(res.msg);
|
||||
}
|
||||
})
|
||||
// 提供 redirect 参数时,登录后往 redirect 跳转
|
||||
if(pData.redirect) {
|
||||
sa.ajax("/sso/getRedirectUrl", pData, function(res) {
|
||||
if(res.code == 200) {
|
||||
// 已登录,并且redirect地址有效,开始跳转
|
||||
location.href = res.data;
|
||||
} else if(res.code == 401) {
|
||||
console.log('未登录');
|
||||
} else {
|
||||
layer.alert(res.msg);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 未提供 redirect 参数时,登录后往 home 跳转
|
||||
sa.ajax("/sso/isLogin", {}, function(res) {
|
||||
if(res.data) {
|
||||
location.href = './home.html';
|
||||
} else {
|
||||
console.log('未登录,请先登录...');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 登录
|
||||
$('.login-btn').click(function(){
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
+9
-1
@@ -16,7 +16,15 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
*/
|
||||
@RestController
|
||||
public class H5Controller {
|
||||
|
||||
|
||||
/**
|
||||
* 返回当前是否已经登录
|
||||
*/
|
||||
@RequestMapping("/sso/isLogin")
|
||||
public SaResult isLogin() {
|
||||
return SaResult.data(StpUtil.isLogin());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 redirectUrl
|
||||
*/
|
||||
|
||||
+12
@@ -47,6 +47,18 @@ sa-token:
|
||||
push-url: http://sa-sso-client1.com:9003/sso/pushC
|
||||
# 接口调用秘钥 (如果不配置则使用全局默认秘钥)
|
||||
secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
# 应用 sso-client3-nosdk:采用 NoSdk 模式对接 (不依赖 Sa-Token 客户端 SDK,手动实现协议)
|
||||
sso-client3-nosdk:
|
||||
# 应用名称
|
||||
client: sso-client3-nosdk
|
||||
# 允许授权地址
|
||||
allow-url: "*"
|
||||
# 是否接收消息推送
|
||||
is-push: true
|
||||
# 消息推送地址
|
||||
push-url: http://sa-sso-client1.com:9004/sso/pushC
|
||||
# 接口调用秘钥 (如果不配置则使用全局默认秘钥)
|
||||
secret-key: SSO-C3-NoSdk-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
# 应用 sso-client3-resdk:采用 ReSdk 模式对接
|
||||
sso-client3-resdk:
|
||||
# 应用名称
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
-3
@@ -16,9 +16,6 @@ public class SaSsoClientNoSdkApplication {
|
||||
System.out.println("测试访问应用端三: http://sa-sso-client3.com:9004");
|
||||
System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456");
|
||||
System.out.println();
|
||||
|
||||
System.err.println("自 v1.43.0 版本起,Sa-Token SSO 不再维护 NoSdk 示例,此项目仅做留档");
|
||||
System.err.println("如您需要非 Sa-Token 技术栈项目接入 SSO-Server 认证中心,请参考 ReSdk 版本示例");
|
||||
}
|
||||
|
||||
}
|
||||
+120
-116
@@ -1,22 +1,22 @@
|
||||
package com.pj.sso;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import com.pj.sso.util.AjaxJson;
|
||||
import com.pj.sso.util.MyHttpSessionHolder;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.pj.sso.util.AjaxJson;
|
||||
import com.pj.sso.util.MyHttpSessionHolder;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* SSO Client端 Controller
|
||||
* SSO Client端 Controller
|
||||
* @author click33
|
||||
*/
|
||||
@RestController
|
||||
@@ -25,162 +25,166 @@ public class SsoClientController {
|
||||
// SSO-Client端:首页
|
||||
@RequestMapping("/")
|
||||
public String index(HttpSession session) {
|
||||
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
|
||||
"<p>当前会话登录账号:" + session.getAttribute("userId") + "</p>" +
|
||||
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a>" +
|
||||
" <a href='/sso/logout?back=' + + encodeURIComponent(location.href);>注销</a>" +
|
||||
Object userId = session.getAttribute("userId");
|
||||
String str = "<h2>Sa-Token SSO-Client 应用端 (模式三-NoSdk)</h2>" +
|
||||
"<p>当前会话是否登录:" + (userId != null) + " (" + userId + ")</p>" +
|
||||
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a>" +
|
||||
" <a href='/sso/logout?back=' + + encodeURIComponent(location.href);>注销</a>" +
|
||||
" <a href='/sso/myInfo' target=\"_blank\">获取资料</a></p>";
|
||||
return str;
|
||||
}
|
||||
|
||||
// SSO-Client端:单点登录地址
|
||||
// SSO-Client端:单点登录地址
|
||||
@RequestMapping("/sso/login")
|
||||
public Object ssoLogin(String ticket, @RequestParam(defaultValue = "/") String back,
|
||||
public Object ssoLogin(String ticket, @RequestParam(defaultValue = "/") String back,
|
||||
HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException {
|
||||
|
||||
// 如果已经登录,则直接返回
|
||||
if(session.getAttribute("userId") != null) {
|
||||
|
||||
// 如果已经登录,则直接返回
|
||||
if (session.getAttribute("userId") != null) {
|
||||
response.sendRedirect(back);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 此时有两种情况:
|
||||
* 情况1:ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
|
||||
* 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
|
||||
* 此时有两种情况:
|
||||
* 情况1:ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
|
||||
* 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
|
||||
*/
|
||||
if(ticket == null) {
|
||||
String currUrl = request.getRequestURL().toString();
|
||||
String clientLoginUrl = currUrl + "?back=" + SsoRequestUtil.encodeUrl(back);
|
||||
String serverAuthUrl = SsoRequestUtil.authUrl + "?redirect=" + clientLoginUrl;
|
||||
if (ticket == null) {
|
||||
// ------- 情况 1
|
||||
// 当前 url,形如:http://sso-client.com/sso/login?back=xxx
|
||||
String clientLoginUrl = request.getRequestURL().toString() + "?back=" + SsoRequestUtil.encodeUrl(back);
|
||||
// 最终授权地址,形如:http://sso-server.com/sso/auth?client=xxx&redirect=http://sso-client.com/sso/login?back=xxx
|
||||
String serverAuthUrl = SsoRequestUtil.authUrl
|
||||
+ "?client=" + SsoRequestUtil.clientId
|
||||
+ "&redirect=" + clientLoginUrl;
|
||||
response.sendRedirect(serverAuthUrl);
|
||||
return null;
|
||||
|
||||
} else {
|
||||
// 获取当前 client 端的单点注销回调地址
|
||||
String ssoLogoutCall = "";
|
||||
if(SsoRequestUtil.isSlo) {
|
||||
ssoLogoutCall = request.getRequestURL().toString().replace("/sso/login", "/sso/logoutCall");
|
||||
}
|
||||
|
||||
// 校验 ticket
|
||||
String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳
|
||||
String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串
|
||||
String sign = SsoRequestUtil.getSignByTicket(ticket, ssoLogoutCall, timestamp, nonce); // 参数签名
|
||||
String checkUrl = SsoRequestUtil.checkTicketUrl +
|
||||
"?timestamp=" + timestamp +
|
||||
"&nonce=" + nonce +
|
||||
"&sign=" + sign +
|
||||
"&ticket=" + ticket +
|
||||
"&ssoLogoutCall=" + ssoLogoutCall;
|
||||
AjaxJson result = SsoRequestUtil.request(checkUrl);
|
||||
|
||||
// 200 代表校验成功
|
||||
if(result.getCode() == 200 && SsoRequestUtil.isEmpty(result.getData()) == false) {
|
||||
// 登录上
|
||||
Object loginId = result.getData();
|
||||
session.setAttribute("userId", loginId);
|
||||
// ------- 情况 2
|
||||
// 构建 checkTicket 请求参数,以 ticket 查询 userId
|
||||
Map<String, String> params = new LinkedHashMap<>();
|
||||
params.put("msgType", "checkTicket");
|
||||
params.put("client", SsoRequestUtil.clientId);
|
||||
params.put("ticket", ticket);
|
||||
SsoSignUtil.addSignParams(params);
|
||||
String pushUrl = SsoRequestUtil.buildUrl(SsoRequestUtil.pushSUrl, params);
|
||||
AjaxJson result = SsoRequestUtil.request(pushUrl);
|
||||
|
||||
// 200 代表校验成功
|
||||
if (result.getCode() == 200 && !SsoRequestUtil.isEmpty(result.getData())) {
|
||||
// 登录上
|
||||
session.setAttribute("userId", result.getData());
|
||||
// 返回 back 地址
|
||||
response.sendRedirect(back);
|
||||
return null;
|
||||
|
||||
} else {
|
||||
// 将 sso-server 回应的消息作为异常抛出
|
||||
// 将 sso-server 回应的消息作为异常抛出
|
||||
throw new RuntimeException(result.getMsg());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// SSO-Client端:单点注销地址
|
||||
@RequestMapping("/sso/logout")
|
||||
public Object ssoLogout(@RequestParam(defaultValue = "/") String back,
|
||||
public Object ssoLogout(@RequestParam(defaultValue = "/") String back,
|
||||
HttpServletResponse response, HttpSession session) throws IOException {
|
||||
|
||||
// 如果未登录,则无需注销
|
||||
if(session.getAttribute("userId") == null) {
|
||||
|
||||
// 如果未登录,则无需注销
|
||||
if (session.getAttribute("userId") == null) {
|
||||
response.sendRedirect(back);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 调用 sso-server 认证中心单点注销API
|
||||
Object loginId = session.getAttribute("userId"); // 账号id
|
||||
String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳
|
||||
String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串
|
||||
String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce); // 参数签名
|
||||
|
||||
String url = SsoRequestUtil.sloUrl +
|
||||
"?loginId=" + loginId +
|
||||
"×tamp=" + timestamp +
|
||||
"&nonce=" + nonce +
|
||||
"&sign=" + sign;
|
||||
AjaxJson result = SsoRequestUtil.request(url);
|
||||
|
||||
// 校验响应状态码,200 代表成功
|
||||
if(result.getCode() == 200) {
|
||||
|
||||
// 极端场景下,sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀
|
||||
}
|
||||
|
||||
// 调用 sso-server 认证中心单点注销 API
|
||||
Object loginId = session.getAttribute("userId");
|
||||
Map<String, String> params = new LinkedHashMap<>();
|
||||
params.put("msgType", "signout");
|
||||
params.put("client", SsoRequestUtil.clientId);
|
||||
params.put("loginId", String.valueOf(loginId));
|
||||
SsoSignUtil.addSignParams(params);
|
||||
String pushUrl = SsoRequestUtil.buildUrl(SsoRequestUtil.pushSUrl, params);
|
||||
AjaxJson result = SsoRequestUtil.request(pushUrl);
|
||||
|
||||
// 校验响应状态码,200 代表成功
|
||||
if (result.getCode() == 200) {
|
||||
// 极端场景下,sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀
|
||||
session.removeAttribute("userId");
|
||||
// 返回 back 地址
|
||||
response.sendRedirect(back);
|
||||
return null;
|
||||
|
||||
} else {
|
||||
// 将 sso-server 回应的消息作为异常抛出
|
||||
// 将 sso-server 回应的消息作为异常抛出
|
||||
throw new RuntimeException(result.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
// SSO-Client端:单点注销回调地址
|
||||
@RequestMapping("/sso/logoutCall")
|
||||
public Object ssoLogoutCall(String loginId, String autoLogout, String timestamp, String nonce, String sign) {
|
||||
|
||||
// 校验签名
|
||||
String calcSign = SsoRequestUtil.getSignByLogoutCall(loginId, autoLogout, timestamp, nonce);
|
||||
if(calcSign.equals(sign) == false) {
|
||||
System.out.println("无效签名,拒绝应答:" + sign);
|
||||
return AjaxJson.getError("无效签名,拒绝应答" + sign);
|
||||
|
||||
// SSO-Server 端消息推送接收地址(单点注销回调等)
|
||||
@RequestMapping("/sso/pushC")
|
||||
public Object ssoPushC(HttpServletRequest request) {
|
||||
|
||||
// 将请求参数收集为 Map<String, String>
|
||||
Map<String, String> params = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
|
||||
params.put(entry.getKey(), entry.getValue()[0]);
|
||||
}
|
||||
|
||||
// 注销这个账号id
|
||||
for (HttpSession session: MyHttpSessionHolder.sessionList) {
|
||||
Object userId = session.getAttribute("userId");
|
||||
if(Objects.equals(String.valueOf(userId), loginId)) {
|
||||
session.removeAttribute("userId");
|
||||
|
||||
// 校验签名
|
||||
if (!SsoSignUtil.verifySign(params)) {
|
||||
return AjaxJson.getError("无效签名,拒绝应答");
|
||||
}
|
||||
|
||||
// 按 msgType 分发处理
|
||||
String msgType = params.get("msgType");
|
||||
|
||||
// 单点注销回调
|
||||
if ("logoutCall".equals(msgType)) {
|
||||
// 注销这个账号 id 在本 client 端的所有会话
|
||||
String loginId = params.get("loginId");
|
||||
for (HttpSession session : MyHttpSessionHolder.sessionList) {
|
||||
Object userId = session.getAttribute("userId");
|
||||
if (Objects.equals(String.valueOf(userId), loginId)) {
|
||||
session.removeAttribute("userId");
|
||||
}
|
||||
}
|
||||
return AjaxJson.getSuccess("账号id=" + loginId + " 注销成功");
|
||||
}
|
||||
|
||||
return AjaxJson.getSuccess("账号id=" + loginId + " 注销成功");
|
||||
|
||||
// 其它消息类型
|
||||
// if("xxx".equals(msgType)) {
|
||||
// // 处理 xxx 消息
|
||||
// }
|
||||
|
||||
return AjaxJson.getError("未知消息类型:" + msgType);
|
||||
}
|
||||
|
||||
// 查询我的账号信息 (调用此接口的前提是 sso-server 端开放了 /sso/userinfo 路由)
|
||||
// 查询我的账号信息(调用 sso-server 端 userinfo 消息处理器)
|
||||
@RequestMapping("/sso/myInfo")
|
||||
public Object myInfo(HttpSession session) {
|
||||
// 如果尚未登录
|
||||
if(session.getAttribute("userId") == null) {
|
||||
// 如果尚未登录
|
||||
if (session.getAttribute("userId") == null) {
|
||||
return "尚未登录,无法获取";
|
||||
}
|
||||
|
||||
// 组织 url 参数
|
||||
Object loginId = session.getAttribute("userId"); // 账号id
|
||||
String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳
|
||||
String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串
|
||||
String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce); // 参数签名
|
||||
|
||||
String url = SsoRequestUtil.getDataUrl +
|
||||
"?loginId=" + loginId +
|
||||
"×tamp=" + timestamp +
|
||||
"&nonce=" + nonce +
|
||||
"&sign=" + sign;
|
||||
AjaxJson result = SsoRequestUtil.request(url);
|
||||
|
||||
// 返回给前端
|
||||
Object loginId = session.getAttribute("userId");
|
||||
Map<String, String> params = new LinkedHashMap<>();
|
||||
params.put("msgType", "userinfo");
|
||||
params.put("client", SsoRequestUtil.clientId);
|
||||
params.put("loginId", String.valueOf(loginId));
|
||||
SsoSignUtil.addSignParams(params);
|
||||
String pushUrl = SsoRequestUtil.buildUrl(SsoRequestUtil.pushSUrl, params);
|
||||
AjaxJson result = SsoRequestUtil.request(pushUrl);
|
||||
|
||||
// 返回给前端
|
||||
return result;
|
||||
}
|
||||
|
||||
// 全局异常拦截
|
||||
// 全局异常拦截
|
||||
@ExceptionHandler
|
||||
public AjaxJson handlerException(Exception e) {
|
||||
e.printStackTrace();
|
||||
e.printStackTrace();
|
||||
return AjaxJson.getError(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+27
-88
@@ -5,61 +5,47 @@ import com.pj.sso.util.AjaxJson;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* 封装一些 sso 共用方法
|
||||
*
|
||||
* 封装一些 sso 共用方法
|
||||
*
|
||||
* @author click33
|
||||
* @since 2022-4-30
|
||||
*/
|
||||
public class SsoRequestUtil {
|
||||
|
||||
/**
|
||||
* SSO-Server端主机地址
|
||||
* SSO-Server 端主机地址
|
||||
*/
|
||||
public static String serverUrl = "http://sa-sso-server.com:9000";
|
||||
|
||||
/**
|
||||
* SSO-Server端 统一认证地址
|
||||
* SSO-Server 端统一认证地址
|
||||
*/
|
||||
public static String authUrl = serverUrl + "/sso/auth";
|
||||
|
||||
/**
|
||||
* SSO-Server端 ticket校验地址
|
||||
* SSO-Server 端统一消息推送地址(ticket校验、单点注销、获取用户信息等均通过此入口)
|
||||
*/
|
||||
public static String checkTicketUrl = serverUrl + "/sso/checkTicket";
|
||||
public static String pushSUrl = serverUrl + "/sso/pushS";
|
||||
|
||||
/**
|
||||
* 单点注销地址
|
||||
* 当前应用的客户端标识(需与 sso-server 端 clients 配置一致)
|
||||
*/
|
||||
public static String sloUrl = serverUrl + "/sso/signout";
|
||||
public static String clientId = "sso-client3-nosdk";
|
||||
|
||||
/**
|
||||
* SSO-Server端 查询userinfo地址
|
||||
* 接口调用秘钥(需与 sso-server 端对应 client 配置一致)
|
||||
*/
|
||||
public static String getDataUrl = serverUrl + "/sso/getData";
|
||||
public static String secretKey = "SSO-C3-NoSdk-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor";
|
||||
|
||||
// -------------------------- 工具方法
|
||||
|
||||
/**
|
||||
* 打开单点注销功能
|
||||
*/
|
||||
public static boolean isSlo = true;
|
||||
|
||||
/**
|
||||
* 接口调用秘钥
|
||||
*/
|
||||
public static String secretKey = "kQwIOrYvnXmSDkwEiFngrKidMcdrgKor";
|
||||
|
||||
|
||||
|
||||
// -------------------------- 工具方法
|
||||
|
||||
/**
|
||||
* 发出请求,并返回 SaResult 结果
|
||||
* @param url 请求地址
|
||||
* @return 返回的结果
|
||||
* 发出请求,并返回 AjaxJson 结果
|
||||
* @param url 请求地址(含查询参数)
|
||||
* @return 返回的结果
|
||||
*/
|
||||
public static AjaxJson request(String url) {
|
||||
Map<String, Object> map = Forest.post(url).executeAsMap();
|
||||
@@ -67,75 +53,28 @@ public class SsoRequestUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数计算签名
|
||||
* @param loginId 账号id
|
||||
* @param timestamp 当前时间戳,13位
|
||||
* @param nonce 随机字符串
|
||||
* @return 签名
|
||||
* 将参数 Map 拼接到 baseUrl 后面(值进行 URL 编码),返回完整 URL
|
||||
* @param baseUrl 基础 URL
|
||||
* @param params 请求参数
|
||||
* @return 拼接后的完整 URL
|
||||
*/
|
||||
public static String getSign(Object loginId, String timestamp, String nonce) {
|
||||
return md5("loginId=" + loginId + "&nonce=" + nonce + "×tamp=" + timestamp + "&key=" + secretKey);
|
||||
}
|
||||
// 单点注销回调时构建签名
|
||||
public static String getSignByLogoutCall(Object loginId, String autoLogout, String timestamp, String nonce) {
|
||||
return md5("autoLogout=" + autoLogout + "&loginId=" + loginId + "&nonce=" + nonce + "×tamp=" + timestamp + "&key=" + secretKey);
|
||||
}
|
||||
// 校验ticket 时构建签名
|
||||
public static String getSignByTicket(String ticket, String ssoLogoutCall, String timestamp, String nonce) {
|
||||
return md5("nonce=" + nonce + "&ssoLogoutCall=" + ssoLogoutCall + "&ticket=" + ticket + "×tamp=" + timestamp + "&key=" + secretKey);
|
||||
public static String buildUrl(String baseUrl, Map<String, String> params) {
|
||||
StringBuilder sb = new StringBuilder(baseUrl).append("?");
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
sb.append(entry.getKey()).append("=").append(encodeUrl(entry.getValue())).append("&");
|
||||
}
|
||||
sb.deleteCharAt(sb.length() - 1);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定元素是否为null或者空字符串
|
||||
* @param str 指定元素
|
||||
* @param str 指定元素
|
||||
* @return 是否为null或者空字符串
|
||||
*/
|
||||
public static boolean isEmpty(Object str) {
|
||||
return str == null || "".equals(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* md5加密
|
||||
* @param str 指定字符串
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
public static String md5(String str) {
|
||||
str = (str == null ? "" : str);
|
||||
char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
try {
|
||||
byte[] btInput = str.getBytes();
|
||||
MessageDigest mdInst = MessageDigest.getInstance("MD5");
|
||||
mdInst.update(btInput);
|
||||
byte[] md = mdInst.digest();
|
||||
int j = md.length;
|
||||
char[] strA = new char[j * 2];
|
||||
int k = 0;
|
||||
for (byte byte0 : md) {
|
||||
strA[k++] = hexDigits[byte0 >>> 4 & 0xf];
|
||||
strA[k++] = hexDigits[byte0 & 0xf];
|
||||
}
|
||||
return new String(strA);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成指定长度的随机字符串
|
||||
*
|
||||
* @param length 字符串的长度
|
||||
* @return 一个随机字符串
|
||||
*/
|
||||
public static String getRandomString(int length) {
|
||||
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
Random random = new Random();
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int number = random.nextInt(62);
|
||||
sb.append(str.charAt(number));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* URL编码
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package com.pj.sso;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* SSO 签名工具类:签名生成、注入与校验
|
||||
*
|
||||
* @author click33
|
||||
*/
|
||||
public class SsoSignUtil {
|
||||
|
||||
|
||||
|
||||
// -------------------------- 签名方法
|
||||
|
||||
/**
|
||||
* 计算签名:将 params(排除 sign 字段)按 key 字典序升序排列,
|
||||
* 拼接为 k=v&k=v 后追加 &key={secretKey},整体 MD5
|
||||
* @param params 请求参数(不含 sign)
|
||||
* @return 签名值
|
||||
*/
|
||||
public static String computeSign(Map<String, String> params) {
|
||||
TreeMap<String, String> sorted = new TreeMap<>(params);
|
||||
sorted.remove("sign");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Map.Entry<String, String> entry : sorted.entrySet()) {
|
||||
if (sb.length() > 0) sb.append("&");
|
||||
sb.append(entry.getKey()).append("=").append(entry.getValue());
|
||||
}
|
||||
sb.append("&key=").append(SsoRequestUtil.secretKey);
|
||||
return md5(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 向参数 Map 中注入 timestamp、nonce、sign 三个签名参数
|
||||
* @param params 请求参数(已填好业务参数,此方法自动追加签名参数)
|
||||
*/
|
||||
public static void addSignParams(Map<String, String> params) {
|
||||
params.put("timestamp", String.valueOf(System.currentTimeMillis()));
|
||||
params.put("nonce", getRandomString(20));
|
||||
params.put("sign", computeSign(params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求中的 sign 参数是否合法
|
||||
* @param params 包含 sign 的请求参数
|
||||
* @return 签名是否合法
|
||||
*/
|
||||
public static boolean verifySign(Map<String, String> params) {
|
||||
String sign = params.get("sign");
|
||||
if (sign == null) return false;
|
||||
return sign.equals(computeSign(params));
|
||||
}
|
||||
|
||||
|
||||
// -------------------------- 基础工具
|
||||
|
||||
/**
|
||||
* MD5 加密
|
||||
* @param str 指定字符串
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
public static String md5(String str) {
|
||||
str = (str == null ? "" : str);
|
||||
char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
try {
|
||||
byte[] btInput = str.getBytes();
|
||||
MessageDigest mdInst = MessageDigest.getInstance("MD5");
|
||||
mdInst.update(btInput);
|
||||
byte[] md = mdInst.digest();
|
||||
int j = md.length;
|
||||
char[] strA = new char[j * 2];
|
||||
int k = 0;
|
||||
for (byte byte0 : md) {
|
||||
strA[k++] = hexDigits[byte0 >>> 4 & 0xf];
|
||||
strA[k++] = hexDigits[byte0 & 0xf];
|
||||
}
|
||||
return new String(strA);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成指定长度的随机字符串
|
||||
* @param length 字符串的长度
|
||||
* @return 一个随机字符串
|
||||
*/
|
||||
public static String getRandomString(int length) {
|
||||
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
Random random = new Random();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append(str.charAt(random.nextInt(62)));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
<java.run.main.class>com.pj.SaTokenApplication</java.run.main.class>
|
||||
</properties>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.44.0</sa-token.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user