Compare commits
197 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 308f81be31 | |||
| f60702186d | |||
| 7a1095c72f | |||
| fab7f200cc | |||
| 49ec4d9690 | |||
| d82f754d09 | |||
| 79a962460a | |||
| 32669cc91e | |||
| 34f8a60b23 | |||
| ec06b8f644 | |||
| fd06bfc801 | |||
| 38fd28c2ab | |||
| 662398af6a | |||
| 713ac4d6e6 | |||
| 9ecaf72e9f | |||
| 14e645a8ac | |||
| 8c5ea5112d | |||
| d08885c209 | |||
| 57fff10aef | |||
| 1925eb4081 | |||
| 6642f96f7e | |||
| 62d70f0027 | |||
| 3acc7bd7af | |||
| c6a081ebf6 | |||
| de1239e9d2 | |||
| 78de70bda6 | |||
| 55f0c94aec | |||
| 36cc99a70c | |||
| 0a7a82fdab | |||
| 601d8b1373 | |||
| 8cff63b0fc | |||
| f75ab31222 | |||
| a4c18c5238 | |||
| 5f1e2bea7e | |||
| 660ac438c2 | |||
| c289ec572d | |||
| bb5ceb1dc0 | |||
| f2e9f7c222 | |||
| a7f178da53 | |||
| 18ab60d4d2 | |||
| cd964d4d9f | |||
| b844312f89 | |||
| 24b49750f3 | |||
| 92f48256e9 | |||
| 68939084a2 | |||
| e0fdfe84ee | |||
| 1685eebac3 | |||
| 5d7a51214c | |||
| 477a5531fb | |||
| e7d4559325 | |||
| 8a9e76f8ca | |||
| 2fbafcdd3e | |||
| a4b84c4df5 | |||
| 0e7e2888b6 | |||
| 48368254e4 | |||
| 07ae1d6921 | |||
| f1155a2206 | |||
| 04c15e81ba | |||
| 5e8a429d37 | |||
| db611b8337 | |||
| 8525cea74c | |||
| 35cbd38b3b | |||
| 9058e7edc4 | |||
| 02c36fe9f8 | |||
| 6e4bdea8c1 | |||
| 4726544b85 | |||
| b12b91e1ad | |||
| f881c1d069 | |||
| 86158b2950 | |||
| 6f4772d3ab | |||
| 05a03b61b7 | |||
| 10b53c3ef0 | |||
| bf42588519 | |||
| 6a09d71911 | |||
| 631db8215f | |||
| c6e1be58c5 | |||
| 4d2eb4e94b | |||
| 12116c106e | |||
| 937844f4f6 | |||
| 75df0597a0 | |||
| 258e379570 | |||
| fb95acc8ce | |||
| b25fc289da | |||
| eaef278426 | |||
| 5b5c031bdd | |||
| a01eef8000 | |||
| bfb34293d6 | |||
| 79016e5ffe | |||
| c7f27e393e | |||
| dded9237a6 | |||
| d806f16acd | |||
| fd623c122e | |||
| 307c6a0619 | |||
| 33c3b90abb | |||
| 3d2cdbbb57 | |||
| aef5e04abe | |||
| 1c4af4cc03 | |||
| abc2b0be69 | |||
| 9095ea3851 | |||
| 469d387f8a | |||
| b2e188fc3f | |||
| 4ecd71bc7e | |||
| c3be6304db | |||
| 850af6c131 | |||
| 7d2fac7d98 | |||
| 2b8489dff7 | |||
| 5352e973e3 | |||
| 4b02b5bb28 | |||
| d853d61bf6 | |||
| f787d5a375 | |||
| bc401492b5 | |||
| a1189c8aaf | |||
| bdf5a0a264 | |||
| 81727ec4cf | |||
| 6c3dd1f222 | |||
| 4e78cc8fee | |||
| b45ccba778 | |||
| e47fdd82d8 | |||
| 2be9b0f173 | |||
| 8c256d893b | |||
| 905f6714e2 | |||
| caeb4eba15 | |||
| fce89c2efa | |||
| a780fa0ad7 | |||
| 25fcd80eb6 | |||
| 42fbb0dde8 | |||
| 0e4d812b8b | |||
| 59659d1c12 | |||
| 0b85c7d094 | |||
| 3bc9e88645 | |||
| 16cf2db334 | |||
| 0743b67cf8 | |||
| ce74d5f41f | |||
| 2d6c371638 | |||
| c830f1fbe3 | |||
| 2c707146ee | |||
| 062572e2ee | |||
| b83927abdf | |||
| d1a79ce55e | |||
| 8f51d1af8d | |||
| 6c55de0ef3 | |||
| 4ba21ffba8 | |||
| c42e5fb34e | |||
| e469b76681 | |||
| 80789607fd | |||
| cce77fbd49 | |||
| 59823fd3cd | |||
| db5e70db6a | |||
| a5d8e071a7 | |||
| 03ad51ef7b | |||
| ca787ec240 | |||
| 273e4c74db | |||
| 028a79abd8 | |||
| 561c40c3ba | |||
| bf4d40cb30 | |||
| cdad8e7245 | |||
| a94fe35e65 | |||
| d1be8365c4 | |||
| 672015adf0 | |||
| c04241d3b3 | |||
| a97e3f9902 | |||
| d65881b59a | |||
| 048dadaff7 | |||
| d551066238 | |||
| e898ad70c5 | |||
| b90754839c | |||
| 5af713fc9d | |||
| 6c2de3c99e | |||
| f3cec926c6 | |||
| 6b273a3181 | |||
| 83be085ffc | |||
| ca64e44fc0 | |||
| d5deb72c9a | |||
| 349213032b | |||
| e5c21b478d | |||
| 5dbaab1966 | |||
| 935a8db3ae | |||
| 362b08b9b8 | |||
| 7c3febda60 | |||
| 096e2bbb63 | |||
| 87d2a1156b | |||
| 0d9fed1558 | |||
| a4c9dc1e68 | |||
| 54bf228ec9 | |||
| e6dd850752 | |||
| ede9aba865 | |||
| ac723f2ddb | |||
| 88e42c868d | |||
| bc5262373a | |||
| 855e13f5f1 | |||
| 3e95c527b7 | |||
| 3892f18a16 | |||
| 14c57c6599 | |||
| 29c1bd9436 | |||
| ba3ad31d47 | |||
| b9a012a056 | |||
| de27d08824 |
@@ -1,12 +1,12 @@
|
||||
<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.40.0</h1>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.42.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/overview"><img src="https://gitcode.com/dromara/Sa-Token/star/badge.svg"></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://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>
|
||||
@@ -23,6 +23,18 @@
|
||||
|
||||
Sa-Token 是一个轻量级 Java 权限认证框架,目前拥有五大核心模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。
|
||||
|
||||
要在 SpringBoot 项目中使用 Sa-Token,你只需要在 pom.xml 中引入依赖:
|
||||
|
||||
``` xml
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.42.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
除了 SpringBoot2、Sa-Token 还为 SpringBoot3、Solon、JFinal 等常见 Web 框架提供集成包,做到真正的开箱即用。
|
||||
|
||||
|
||||
<details>
|
||||
|
||||
@@ -8,17 +8,23 @@ cd sa-token-demo
|
||||
|
||||
cd sa-token-demo-alone-redis & call mvn clean & cd ..
|
||||
cd sa-token-demo-alone-redis-cluster & call mvn clean & cd ..
|
||||
cd sa-token-demo-apikey & call mvn clean & cd ..
|
||||
cd sa-token-demo-async & call mvn clean & cd ..
|
||||
cd sa-token-demo-beetl & call mvn clean & cd ..
|
||||
cd sa-token-demo-bom-import & call mvn clean & cd ..
|
||||
cd sa-token-demo-case & call mvn clean & cd ..
|
||||
cd sa-token-demo-device-lock & call mvn clean & cd ..
|
||||
cd sa-token-demo-grpc & call mvn clean & cd ..
|
||||
cd sa-token-demo-hutool-timed-cache & call mvn clean & cd ..
|
||||
cd sa-token-demo-caffeine & call mvn clean & cd ..
|
||||
cd sa-token-demo-jwt & call mvn clean & cd ..
|
||||
cd sa-token-demo-quick-login & call mvn clean & cd ..
|
||||
cd sa-token-demo-quick-login-sb3 & call mvn clean & cd ..
|
||||
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-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 ..
|
||||
cd sa-token-demo-ssm & call mvn clean & cd ..
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
<!-- 一些属性 -->
|
||||
<properties>
|
||||
<revision>1.40.0</revision>
|
||||
<revision>1.42.0</revision>
|
||||
<jdk.version>1.8</jdk.version>
|
||||
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
:: 运行前需要安装 browser-sync:
|
||||
:: npm install -g browser-sync
|
||||
|
||||
cd sa-token-doc & browser-sync start --server --files ""
|
||||
@@ -1,13 +0,0 @@
|
||||
target/
|
||||
|
||||
node_modules/
|
||||
bin/
|
||||
.settings/
|
||||
unpackage/
|
||||
.classpath
|
||||
.project
|
||||
|
||||
.factorypath
|
||||
|
||||
.idea/
|
||||
.iml
|
||||
+27
-7
@@ -13,7 +13,7 @@
|
||||
<url>https://github.com/dromara/sa-token</url>
|
||||
|
||||
<properties>
|
||||
<revision>1.40.0</revision>
|
||||
<revision>1.42.0</revision>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -111,17 +111,27 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis</artifactId>
|
||||
<artifactId>sa-token-redis-template</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-fastjson</artifactId>
|
||||
<artifactId>sa-token-jackson</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-fastjson2</artifactId>
|
||||
<artifactId>sa-token-fastjson</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-fastjson2</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-snack3</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -131,12 +141,12 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redisson-jackson</artifactId>
|
||||
<artifactId>sa-token-redisson-spring-boot-starter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redisson-jackson2</artifactId>
|
||||
<artifactId>sa-token-redisson</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -151,7 +161,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dialect-thymeleaf</artifactId>
|
||||
<artifactId>sa-token-thymeleaf</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -194,6 +204,16 @@
|
||||
<artifactId>sa-token-temp-jwt</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-template-jdk-serializer</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-serializer-features</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- endregion-->
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
target/
|
||||
|
||||
node_modules/
|
||||
bin/
|
||||
.settings/
|
||||
unpackage/
|
||||
.classpath
|
||||
.project
|
||||
|
||||
.factorypath
|
||||
|
||||
.idea/
|
||||
.iml
|
||||
@@ -15,11 +15,13 @@
|
||||
*/
|
||||
package cn.dev33.satoken;
|
||||
|
||||
import cn.dev33.satoken.apikey.SaApiKeyTemplate;
|
||||
import cn.dev33.satoken.apikey.loader.SaApiKeyDataLoader;
|
||||
import cn.dev33.satoken.apikey.loader.SaApiKeyDataLoaderDefaultImpl;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.config.SaTokenConfigFactory;
|
||||
import cn.dev33.satoken.context.SaTokenContext;
|
||||
import cn.dev33.satoken.context.SaTokenContextDefaultImpl;
|
||||
import cn.dev33.satoken.context.second.SaTokenSecondContext;
|
||||
import cn.dev33.satoken.context.SaTokenContextForThreadLocal;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.dao.SaTokenDaoDefaultImpl;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
@@ -30,14 +32,16 @@ import cn.dev33.satoken.listener.SaTokenEventCenter;
|
||||
import cn.dev33.satoken.log.SaLog;
|
||||
import cn.dev33.satoken.log.SaLogForConsole;
|
||||
import cn.dev33.satoken.same.SaSameTemplate;
|
||||
import cn.dev33.satoken.secure.totp.SaTotpTemplate;
|
||||
import cn.dev33.satoken.serializer.SaSerializerTemplate;
|
||||
import cn.dev33.satoken.serializer.impl.SaSerializerTemplateForJson;
|
||||
import cn.dev33.satoken.sign.SaSignTemplate;
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import cn.dev33.satoken.temp.SaTempDefaultImpl;
|
||||
import cn.dev33.satoken.temp.SaTempInterface;
|
||||
import cn.dev33.satoken.temp.SaTempTemplate;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -141,7 +145,7 @@ public class SaManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 一级上下文 SaTokenContextContext
|
||||
* 上下文 SaTokenContext
|
||||
*/
|
||||
private volatile static SaTokenContext saTokenContext;
|
||||
public static void setSaTokenContext(SaTokenContext saTokenContext) {
|
||||
@@ -149,62 +153,33 @@ public class SaManager {
|
||||
SaTokenEventCenter.doRegisterComponent("SaTokenContext", saTokenContext);
|
||||
}
|
||||
public static SaTokenContext getSaTokenContext() {
|
||||
return saTokenContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 二级上下文 SaTokenSecondContext
|
||||
*/
|
||||
private volatile static SaTokenSecondContext saTokenSecondContext;
|
||||
public static void setSaTokenSecondContext(SaTokenSecondContext saTokenSecondContext) {
|
||||
SaManager.saTokenSecondContext = saTokenSecondContext;
|
||||
SaTokenEventCenter.doRegisterComponent("SaTokenSecondContext", saTokenSecondContext);
|
||||
}
|
||||
public static SaTokenSecondContext getSaTokenSecondContext() {
|
||||
return saTokenSecondContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个可用的 SaTokenContext (按照一级上下文、二级上下文、默认上下文的顺序来判断)
|
||||
* @return /
|
||||
*/
|
||||
public static SaTokenContext getSaTokenContextOrSecond() {
|
||||
|
||||
// s1. 一级Context可用时返回一级Context
|
||||
if(saTokenContext != null) {
|
||||
if(saTokenSecondContext == null || saTokenContext.isValid()) {
|
||||
// 因为 isValid 是一个耗时操作,所以此处假定:二级Context为null的情况下无需验证一级Context有效性
|
||||
// 这样可以提升6倍左右的上下文获取速度
|
||||
return saTokenContext;
|
||||
if (saTokenContext == null) {
|
||||
synchronized (SaManager.class) {
|
||||
if (saTokenContext == null) {
|
||||
SaManager.saTokenContext = new SaTokenContextForThreadLocal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// s2. 一级Context不可用时判断二级Context是否可用
|
||||
if(saTokenSecondContext != null && saTokenSecondContext.isValid()) {
|
||||
return saTokenSecondContext;
|
||||
}
|
||||
|
||||
// s3. 都不行,就返回默认的 Context
|
||||
return SaTokenContextDefaultImpl.defaultContext;
|
||||
return saTokenContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 临时 token 认证模块
|
||||
*/
|
||||
private volatile static SaTempInterface saTemp;
|
||||
public static void setSaTemp(SaTempInterface saTemp) {
|
||||
SaManager.saTemp = saTemp;
|
||||
SaTokenEventCenter.doRegisterComponent("SaTempInterface", saTemp);
|
||||
private volatile static SaTempTemplate saTempTemplate;
|
||||
public static void setSaTempTemplate(SaTempTemplate saTempTemplate) {
|
||||
SaManager.saTempTemplate = saTempTemplate;
|
||||
SaTokenEventCenter.doRegisterComponent("SaTempTemplate", saTempTemplate);
|
||||
}
|
||||
public static SaTempInterface getSaTemp() {
|
||||
if (saTemp == null) {
|
||||
public static SaTempTemplate getSaTempTemplate() {
|
||||
if (saTempTemplate == null) {
|
||||
synchronized (SaManager.class) {
|
||||
if (saTemp == null) {
|
||||
SaManager.saTemp = new SaTempDefaultImpl();
|
||||
if (saTempTemplate == null) {
|
||||
SaManager.saTempTemplate = new SaTempTemplate();
|
||||
}
|
||||
}
|
||||
}
|
||||
return saTemp;
|
||||
return saTempTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,6 +201,25 @@ public class SaManager {
|
||||
return saJsonTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化器
|
||||
*/
|
||||
private volatile static SaSerializerTemplate saSerializerTemplate;
|
||||
public static void setSaSerializerTemplate(SaSerializerTemplate saSerializerTemplate) {
|
||||
SaManager.saSerializerTemplate = saSerializerTemplate;
|
||||
SaTokenEventCenter.doRegisterComponent("SaSerializerTemplate", saSerializerTemplate);
|
||||
}
|
||||
public static SaSerializerTemplate getSaSerializerTemplate() {
|
||||
if (saSerializerTemplate == null) {
|
||||
synchronized (SaManager.class) {
|
||||
if (saSerializerTemplate == null) {
|
||||
SaManager.saSerializerTemplate = new SaSerializerTemplateForJson();
|
||||
}
|
||||
}
|
||||
}
|
||||
return saSerializerTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* API 参数签名
|
||||
*/
|
||||
@@ -275,7 +269,67 @@ public class SaManager {
|
||||
public static SaLog getLog() {
|
||||
return SaManager.log;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TOTP 算法类,支持 生成/验证 动态一次性密码
|
||||
*/
|
||||
private volatile static SaTotpTemplate totpTemplate;
|
||||
public static void setSaTotpTemplate(SaTotpTemplate totpTemplate) {
|
||||
SaManager.totpTemplate = totpTemplate;
|
||||
SaTokenEventCenter.doRegisterComponent("SaTotpTemplate", totpTemplate);
|
||||
}
|
||||
public static SaTotpTemplate getSaTotpTemplate() {
|
||||
if (totpTemplate == null) {
|
||||
synchronized (SaManager.class) {
|
||||
if (totpTemplate == null) {
|
||||
SaManager.totpTemplate = new SaTotpTemplate();
|
||||
}
|
||||
}
|
||||
}
|
||||
return totpTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* ApiKey 数据加载器
|
||||
*/
|
||||
private volatile static SaApiKeyDataLoader apiKeyDataLoader;
|
||||
public static void setSaApiKeyDataLoader(SaApiKeyDataLoader apiKeyDataLoader) {
|
||||
SaManager.apiKeyDataLoader = apiKeyDataLoader;
|
||||
SaTokenEventCenter.doRegisterComponent("SaApiKeyDataLoader", apiKeyDataLoader);
|
||||
}
|
||||
public static SaApiKeyDataLoader getSaApiKeyDataLoader() {
|
||||
if (apiKeyDataLoader == null) {
|
||||
synchronized (SaManager.class) {
|
||||
if (apiKeyDataLoader == null) {
|
||||
SaManager.apiKeyDataLoader = new SaApiKeyDataLoaderDefaultImpl();
|
||||
}
|
||||
}
|
||||
}
|
||||
return apiKeyDataLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* ApiKey 操作类
|
||||
*/
|
||||
private volatile static SaApiKeyTemplate apiKeyTemplate;
|
||||
public static void setSaApiKeyTemplate(SaApiKeyTemplate apiKeyTemplate) {
|
||||
SaManager.apiKeyTemplate = apiKeyTemplate;
|
||||
SaTokenEventCenter.doRegisterComponent("SaApiKeyTemplate", apiKeyTemplate);
|
||||
}
|
||||
public static SaApiKeyTemplate getSaApiKeyTemplate() {
|
||||
if (apiKeyTemplate == null) {
|
||||
synchronized (SaManager.class) {
|
||||
if (apiKeyTemplate == null) {
|
||||
SaManager.apiKeyTemplate = new SaApiKeyTemplate();
|
||||
}
|
||||
}
|
||||
}
|
||||
return apiKeyTemplate;
|
||||
}
|
||||
|
||||
|
||||
// ------------------- StpLogic 相关 -------------------
|
||||
|
||||
/**
|
||||
* StpLogic 集合, 记录框架所有成功初始化的 StpLogic
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* API Key 校验:指定请求中必须包含有效的 ApiKey ,并且包含指定的 scope
|
||||
*
|
||||
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD,ElementType.TYPE})
|
||||
public @interface SaCheckApiKey {
|
||||
|
||||
/**
|
||||
* 指定 API key 必须包含的权限 [ 数组 ]
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
String [] scope() default {};
|
||||
|
||||
/**
|
||||
* 验证模式:AND | OR,默认AND
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
SaMode mode() default SaMode.AND;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 权限认证校验:必须具有正确的参数签名才可以通过校验
|
||||
*
|
||||
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD,ElementType.TYPE})
|
||||
public @interface SaCheckSign {
|
||||
|
||||
/**
|
||||
* 多实例下的 appid 值,用于区分不同的实例,如不填写则代表使用全局默认实例 <br/>
|
||||
* 允许以 #{} 的形式指定为请求参数,如:#{appid}
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
String appid() default "";
|
||||
|
||||
/**
|
||||
* 指定参与签名的参数有哪些,如果不填写则默认为全部参数
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
String [] verifyParams() default {};
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.annotation.handler;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckApiKey;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.dev33.satoken.apikey.SaApiKeyUtil;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 注解 SaCheckApiKey 的处理器
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaCheckApiKeyHandler implements SaAnnotationHandlerInterface<SaCheckApiKey> {
|
||||
|
||||
@Override
|
||||
public Class<SaCheckApiKey> getHandlerAnnotationClass() {
|
||||
return SaCheckApiKey.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkMethod(SaCheckApiKey at, Method method) {
|
||||
_checkMethod(at.scope(), at.mode());
|
||||
}
|
||||
|
||||
public static void _checkMethod(String[] scope, SaMode mode) {
|
||||
String apiKey = SaApiKeyUtil.readApiKeyValue(SaHolder.getRequest());
|
||||
if(mode == SaMode.AND) {
|
||||
SaApiKeyUtil.checkApiKeyScope(apiKey, scope);
|
||||
} else {
|
||||
SaApiKeyUtil.checkApiKeyScopeOr(apiKey, scope);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.annotation.handler;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckSign;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.sign.SaSignMany;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 注解 SaCheckSign 的处理器
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class SaCheckSignHandler implements SaAnnotationHandlerInterface<SaCheckSign> {
|
||||
|
||||
@Override
|
||||
public Class<SaCheckSign> getHandlerAnnotationClass() {
|
||||
return SaCheckSign.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkMethod(SaCheckSign at, Method method) {
|
||||
_checkMethod(at.appid(), at.verifyParams());
|
||||
}
|
||||
|
||||
public static void _checkMethod(String appid, String[] verifyParams) {
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
// 如果 appid 为 #{} 格式,则从请求参数中获取
|
||||
if(appid.startsWith("#{") && appid.endsWith("}")) {
|
||||
String reqParamName = appid.substring(2, appid.length() - 1);
|
||||
appid = req.getParam(reqParamName);
|
||||
}
|
||||
SaSignMany.getSignTemplate(appid).checkRequest(req, verifyParams);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,559 @@
|
||||
/*
|
||||
* 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.apikey;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.apikey.model.ApiKeyModel;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.ApiKeyException;
|
||||
import cn.dev33.satoken.exception.ApiKeyScopeException;
|
||||
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.session.raw.SaRawSessionDelegator;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* API Key 操作类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaApiKeyTemplate {
|
||||
|
||||
/**
|
||||
*默认命名空间
|
||||
*/
|
||||
public static final String DEFAULT_NAMESPACE = "apikey";
|
||||
|
||||
/**
|
||||
* 命名空间
|
||||
*/
|
||||
public String namespace;
|
||||
|
||||
/**
|
||||
* Raw Session 读写委托
|
||||
*/
|
||||
public SaRawSessionDelegator rawSessionDelegator;
|
||||
|
||||
/**
|
||||
* 在 raw-session 中的保存索引列表使用的 key
|
||||
*/
|
||||
public static final String API_KEY_LIST = "__HD_API_KEY_LIST";
|
||||
|
||||
public SaApiKeyTemplate(){
|
||||
this(DEFAULT_NAMESPACE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化
|
||||
* @param namespace 命名空间,用于多实例隔离
|
||||
*/
|
||||
public SaApiKeyTemplate(String namespace){
|
||||
if(SaFoxUtil.isEmpty(namespace)) {
|
||||
throw new ApiKeyException("namespace 不能为空");
|
||||
}
|
||||
this.namespace = namespace;
|
||||
this.rawSessionDelegator = new SaRawSessionDelegator(namespace);
|
||||
}
|
||||
|
||||
// ------------------- ApiKey
|
||||
|
||||
/**
|
||||
* 根据 apiKey 从 Cache 获取 ApiKeyModel 信息
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel getApiKeyModelFromCache(String apiKey) {
|
||||
return getSaTokenDao().getObject(splicingApiKeySaveKey(apiKey), ApiKeyModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 apiKey 从 Database 获取 ApiKeyModel 信息
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel getApiKeyModelFromDatabase(String apiKey) {
|
||||
return SaManager.getSaApiKeyDataLoader().getApiKeyModelFromDatabase(namespace, apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApiKeyModel,无效的 ApiKey 会返回 null
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel getApiKey(String apiKey) {
|
||||
if(apiKey == null) {
|
||||
return null;
|
||||
}
|
||||
// 先从缓存中获取,缓存中找不到就尝试从数据库获取
|
||||
ApiKeyModel apiKeyModel = getApiKeyModelFromCache(apiKey);
|
||||
if(apiKeyModel == null) {
|
||||
apiKeyModel = getApiKeyModelFromDatabase(apiKey);
|
||||
saveApiKey(apiKeyModel);
|
||||
}
|
||||
return apiKeyModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 ApiKey,成功返回 ApiKeyModel,失败则抛出异常
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel checkApiKey(String apiKey) {
|
||||
ApiKeyModel ak = getApiKey(apiKey);
|
||||
if(ak == null) {
|
||||
throw new ApiKeyException("无效 API Key: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12301);
|
||||
}
|
||||
if(ak.timeExpired()) {
|
||||
throw new ApiKeyException("API Key 已过期: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12302);
|
||||
}
|
||||
if(! ak.getIsValid()) {
|
||||
throw new ApiKeyException("API Key 已被禁用: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12303);
|
||||
}
|
||||
return ak;
|
||||
}
|
||||
|
||||
/**
|
||||
* 持久化:ApiKeyModel
|
||||
* @param ak /
|
||||
*/
|
||||
public void saveApiKey(ApiKeyModel ak) {
|
||||
if(ak == null) {
|
||||
return;
|
||||
}
|
||||
// 数据自检
|
||||
ak.checkByCanSaved();
|
||||
|
||||
// 保存 ApiKeyModel
|
||||
String saveKey = splicingApiKeySaveKey(ak.getApiKey());
|
||||
if(ak.timeExpired()) {
|
||||
getSaTokenDao().deleteObject(saveKey);
|
||||
} else {
|
||||
getSaTokenDao().setObject(saveKey, ak, ak.expiresIn());
|
||||
}
|
||||
|
||||
// 记录索引
|
||||
if (getIsRecordIndex()) {
|
||||
// 添加索引
|
||||
SaSession session = rawSessionDelegator.getSessionById(ak.getLoginId());
|
||||
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
|
||||
if(! apiKeyList.contains(ak.getApiKey())) {
|
||||
apiKeyList.add(ak.getApiKey());
|
||||
session.set(API_KEY_LIST, apiKeyList);
|
||||
}
|
||||
|
||||
// 调整 ttl
|
||||
adjustIndex(ak.getLoginId(), session);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApiKey 所代表的 LoginId
|
||||
* @param apiKey ApiKey
|
||||
* @return LoginId
|
||||
*/
|
||||
public Object getLoginIdByApiKey(String apiKey) {
|
||||
return checkApiKey(apiKey).getLoginId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 ApiKey
|
||||
* @param apiKey ApiKey
|
||||
*/
|
||||
public void deleteApiKey(String apiKey) {
|
||||
// 删 ApiKeyModel
|
||||
ApiKeyModel ak = getApiKeyModelFromCache(apiKey);
|
||||
if(ak == null) {
|
||||
return;
|
||||
}
|
||||
getSaTokenDao().deleteObject(splicingApiKeySaveKey(apiKey));
|
||||
|
||||
// 删索引
|
||||
if(getIsRecordIndex()) {
|
||||
// RawSession 中不存在,提前退出
|
||||
SaSession session = rawSessionDelegator.getSessionById(ak.getLoginId(), false);
|
||||
if(session == null) {
|
||||
return;
|
||||
}
|
||||
// 索引无记录,提前退出
|
||||
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
|
||||
if(! apiKeyList.contains(apiKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果只有一个 ApiKey,则整个 RawSession 删掉
|
||||
if (apiKeyList.size() == 1) {
|
||||
rawSessionDelegator.deleteSessionById(ak.getLoginId());
|
||||
} else {
|
||||
// 否则移除此 ApiKey 并保存
|
||||
apiKeyList.remove(apiKey);
|
||||
session.set(API_KEY_LIST, apiKeyList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定 loginId 的所有 ApiKey
|
||||
* @param loginId /
|
||||
*/
|
||||
public void deleteApiKeyByLoginId(Object loginId) {
|
||||
// 先判断是否开启索引
|
||||
if(! getIsRecordIndex()) {
|
||||
SaManager.getLog().warn("当前 API Key 模块未开启索引记录功能,无法执行 deleteApiKeyByLoginId 操作");
|
||||
return;
|
||||
}
|
||||
|
||||
// RawSession 中不存在,提前退出
|
||||
SaSession session = rawSessionDelegator.getSessionById(loginId, false);
|
||||
if(session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 先删 ApiKeyModel
|
||||
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
|
||||
for (String apiKey : apiKeyList) {
|
||||
getSaTokenDao().deleteObject(splicingApiKeySaveKey(apiKey));
|
||||
}
|
||||
|
||||
// 再删索引
|
||||
rawSessionDelegator.deleteSessionById(loginId);
|
||||
}
|
||||
|
||||
// ------- 创建
|
||||
|
||||
/**
|
||||
* 创建一个 ApiKeyModel 对象
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel createApiKeyModel() {
|
||||
String apiKey = SaStrategy.instance.generateUniqueToken.execute(
|
||||
"API Key",
|
||||
SaManager.getConfig().getMaxTryTimes(),
|
||||
this::randomApiKeyValue,
|
||||
_apiKey -> getApiKey(_apiKey) == null
|
||||
);
|
||||
return new ApiKeyModel().setApiKey(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 ApiKeyModel 对象
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel createApiKeyModel(Object loginId) {
|
||||
long timeout = SaManager.getConfig().getApiKey().getTimeout();
|
||||
long expiresTime = (timeout == SaTokenDao.NEVER_EXPIRE) ? SaTokenDao.NEVER_EXPIRE : System.currentTimeMillis() + timeout * 1000;
|
||||
return createApiKeyModel()
|
||||
.setLoginId(loginId)
|
||||
.setIsValid(true)
|
||||
.setExpiresTime(expiresTime)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机一个 ApiKey 码
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public String randomApiKeyValue() {
|
||||
return SaManager.getConfig().getApiKey().getPrefix() + SaFoxUtil.getRandomString(36);
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 校验
|
||||
|
||||
/**
|
||||
* 判断:指定 ApiKey 是否具有指定 Scope 列表 (AND 模式,需要全部具备),返回 true 或 false
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public boolean hasApiKeyScope(String apiKey, String... scopes) {
|
||||
try {
|
||||
checkApiKeyScope(apiKey, scopes);
|
||||
return true;
|
||||
} catch (ApiKeyException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:指定 ApiKey 是否具有指定 Scope 列表 (AND 模式,需要全部具备),如果不具备则抛出异常
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public void checkApiKeyScope(String apiKey, String... scopes) {
|
||||
ApiKeyModel ak = checkApiKey(apiKey);
|
||||
if(SaFoxUtil.isEmptyArray(scopes)) {
|
||||
return;
|
||||
}
|
||||
for (String scope : scopes) {
|
||||
if(! ak.getScopes().contains(scope)) {
|
||||
throw new ApiKeyScopeException("该 API Key 不具备 Scope:" + scope)
|
||||
.setApiKey(apiKey)
|
||||
.setScope(scope)
|
||||
.setCode(SaErrorCode.CODE_12311);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定 ApiKey 是否具有指定 Scope 列表 (OR 模式,具备其一即可),返回 true 或 false
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public boolean hasApiKeyScopeOr(String apiKey, String... scopes) {
|
||||
try {
|
||||
checkApiKeyScopeOr(apiKey, scopes);
|
||||
return true;
|
||||
} catch (ApiKeyException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:指定 ApiKey 是否具有指定 Scope 列表 (OR 模式,具备其一即可),如果不具备则抛出异常
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public void checkApiKeyScopeOr(String apiKey, String... scopes) {
|
||||
ApiKeyModel ak = checkApiKey(apiKey);
|
||||
if(SaFoxUtil.isEmptyArray(scopes)) {
|
||||
return;
|
||||
}
|
||||
for (String scope : scopes) {
|
||||
if(ak.getScopes().contains(scope)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new ApiKeyScopeException("该 API Key 不具备 Scope:" + scopes[0])
|
||||
.setApiKey(apiKey)
|
||||
.setScope(scopes[0])
|
||||
.setCode(SaErrorCode.CODE_12311);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定 ApiKey 是否属于指定 LoginId,返回 true 或 false
|
||||
* @param apiKey /
|
||||
* @param loginId /
|
||||
*/
|
||||
public boolean isApiKeyLoginId(String apiKey, Object loginId) {
|
||||
try {
|
||||
checkApiKeyLoginId(apiKey, loginId);
|
||||
return true;
|
||||
} catch (ApiKeyException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:指定 ApiKey 是否属于指定 LoginId,如果不是则抛出异常
|
||||
*
|
||||
* @param apiKey /
|
||||
* @param loginId /
|
||||
*/
|
||||
public void checkApiKeyLoginId(String apiKey, Object loginId) {
|
||||
ApiKeyModel ak = getApiKey(apiKey);
|
||||
if(ak == null) {
|
||||
throw new ApiKeyException("无效 API Key: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12301);
|
||||
}
|
||||
if (SaFoxUtil.notEquals(String.valueOf(ak.getLoginId()), String.valueOf(loginId))) {
|
||||
throw new ApiKeyException("该 API Key 不属于用户: " + loginId)
|
||||
.setApiKey(apiKey)
|
||||
.setCode(SaErrorCode.CODE_12312);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 索引操作
|
||||
|
||||
/**
|
||||
* 调整指定 SaSession 的 TTL 值,以保证最小化内存占用
|
||||
* @param loginId /
|
||||
* @param session 可填写 null,代表使用 loginId 现场查询
|
||||
*/
|
||||
public void adjustIndex(Object loginId, SaSession session) {
|
||||
// 先判断是否开启索引
|
||||
if(! getIsRecordIndex()) {
|
||||
SaManager.getLog().warn("当前 API Key 模块未开启索引记录功能,无法执行 adjustIndex 操作");
|
||||
return;
|
||||
}
|
||||
|
||||
// 未提供则现场查询
|
||||
if(session == null) {
|
||||
session = rawSessionDelegator.getSessionById(loginId, false);
|
||||
if(session == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 重新整理索引列表
|
||||
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
|
||||
ArrayList<String> apiKeyNewList = new ArrayList<>();
|
||||
ArrayList<ApiKeyModel> apiKeyModelList = new ArrayList<>();
|
||||
for (String apikey : apiKeyList) {
|
||||
ApiKeyModel ak = getApiKeyModelFromCache(apikey);
|
||||
if(ak == null || ak.timeExpired()) {
|
||||
continue;
|
||||
}
|
||||
apiKeyNewList.add(apikey);
|
||||
apiKeyModelList.add(ak);
|
||||
}
|
||||
// 如果队列里已无有效值,则删除该 session
|
||||
if(apiKeyNewList.isEmpty()) {
|
||||
rawSessionDelegator.deleteSessionById(loginId);
|
||||
return;
|
||||
}
|
||||
session.set(API_KEY_LIST, apiKeyNewList);
|
||||
|
||||
// 调整 SaSession TTL
|
||||
long maxTtl = 0;
|
||||
for (ApiKeyModel ak : apiKeyModelList) {
|
||||
long ttl = ak.expiresIn();
|
||||
if(ttl == SaTokenDao.NEVER_EXPIRE) {
|
||||
maxTtl = SaTokenDao.NEVER_EXPIRE;
|
||||
break;
|
||||
}
|
||||
if(ttl > maxTtl) {
|
||||
maxTtl = ttl;
|
||||
}
|
||||
}
|
||||
if(maxTtl != 0) {
|
||||
session.updateTimeout(maxTtl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 loginId 的 ApiKey 列表记录
|
||||
* @param loginId /
|
||||
* @return /
|
||||
*/
|
||||
public List<ApiKeyModel> getApiKeyList(Object loginId) {
|
||||
// 先判断是否开启索引
|
||||
if(! getIsRecordIndex()) {
|
||||
SaManager.getLog().warn("当前 API Key 模块未开启索引记录功能,无法执行 getApiKeyList 操作");
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 先查 RawSession
|
||||
List<ApiKeyModel> apiKeyModelList = new ArrayList<>();
|
||||
SaSession session = rawSessionDelegator.getSessionById(loginId, false);
|
||||
if(session == null) {
|
||||
return apiKeyModelList;
|
||||
}
|
||||
|
||||
// 从 RawSession 遍历查询
|
||||
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
|
||||
for (String apikey : apiKeyList) {
|
||||
ApiKeyModel ak = getApiKeyModelFromCache(apikey);
|
||||
if(ak == null || ak.timeExpired()) {
|
||||
continue;
|
||||
}
|
||||
apiKeyModelList.add(ak);
|
||||
}
|
||||
return apiKeyModelList;
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 请求查询
|
||||
|
||||
/**
|
||||
* 数据读取:从请求对象中读取 ApiKey,获取不到返回 null
|
||||
*/
|
||||
public String readApiKeyValue(SaRequest request) {
|
||||
|
||||
// 优先从请求参数中获取
|
||||
String apiKey = request.getParam(namespace);
|
||||
if(SaFoxUtil.isNotEmpty(apiKey)) {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
// 然后请求头
|
||||
apiKey = request.getHeader(namespace);
|
||||
if(SaFoxUtil.isNotEmpty(apiKey)) {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
// 最后从 Authorization 中获取
|
||||
apiKey = SaHttpBasicUtil.getAuthorizationValue();
|
||||
if(SaFoxUtil.isNotEmpty(apiKey)) {
|
||||
if(apiKey.endsWith(":")) {
|
||||
apiKey = apiKey.substring(0, apiKey.length() - 1);
|
||||
}
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据读取:从请求对象中读取 ApiKey,并查询到 ApiKeyModel 信息
|
||||
*/
|
||||
public ApiKeyModel currentApiKey() {
|
||||
String readApiKeyValue = readApiKeyValue(SaHolder.getRequest());
|
||||
return checkApiKey(readApiKeyValue);
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 拼接key
|
||||
|
||||
/**
|
||||
* 拼接key:ApiKey 持久化
|
||||
* @param apiKey ApiKey
|
||||
* @return key
|
||||
*/
|
||||
public String splicingApiKeySaveKey(String apiKey) {
|
||||
return getSaTokenConfig().getTokenName() + ":" + namespace + ":" + apiKey;
|
||||
}
|
||||
|
||||
|
||||
// -------- bean 对象代理
|
||||
|
||||
/**
|
||||
* 获取使用的 getSaTokenDao 实例
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public SaTokenDao getSaTokenDao() {
|
||||
return SaManager.getSaTokenDao();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取使用的 SaTokenConfig 实例
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public SaTokenConfig getSaTokenConfig() {
|
||||
return SaManager.getConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否保存索引信息
|
||||
*/
|
||||
public boolean getIsRecordIndex() {
|
||||
return SaManager.getSaApiKeyDataLoader().getIsRecordIndex();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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.apikey;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.apikey.model.ApiKeyModel;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* API Key 操作工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaApiKeyUtil {
|
||||
|
||||
/**
|
||||
* 获取 ApiKeyModel,无效的 ApiKey 会返回 null
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public static ApiKeyModel getApiKey(String apiKey) {
|
||||
return SaManager.getSaApiKeyTemplate().getApiKey(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 ApiKey,成功返回 ApiKeyModel,失败则抛出异常
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public static ApiKeyModel checkApiKey(String apiKey) {
|
||||
return SaManager.getSaApiKeyTemplate().checkApiKey(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 持久化:ApiKeyModel
|
||||
* @param ak /
|
||||
*/
|
||||
public static void saveApiKey(ApiKeyModel ak) {
|
||||
SaManager.getSaApiKeyTemplate().saveApiKey(ak);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApiKey 所代表的 LoginId
|
||||
* @param apiKey ApiKey
|
||||
* @return LoginId
|
||||
*/
|
||||
public static Object getLoginIdByApiKey(String apiKey) {
|
||||
return SaManager.getSaApiKeyTemplate().getLoginIdByApiKey(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 ApiKey
|
||||
* @param apiKey ApiKey
|
||||
*/
|
||||
public static void deleteApiKey(String apiKey) {
|
||||
SaManager.getSaApiKeyTemplate().deleteApiKey(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定 loginId 的所有 ApiKey
|
||||
* @param loginId /
|
||||
*/
|
||||
public static void deleteApiKeyByLoginId(Object loginId) {
|
||||
SaManager.getSaApiKeyTemplate().deleteApiKeyByLoginId(loginId);
|
||||
}
|
||||
|
||||
// ------- 创建
|
||||
|
||||
/**
|
||||
* 创建一个 ApiKeyModel 对象
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public static ApiKeyModel createApiKeyModel() {
|
||||
return SaManager.getSaApiKeyTemplate().createApiKeyModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 ApiKeyModel 对象
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public static ApiKeyModel createApiKeyModel(Object loginId) {
|
||||
return SaManager.getSaApiKeyTemplate().createApiKeyModel(loginId);
|
||||
}
|
||||
|
||||
|
||||
// ------------------- Scope
|
||||
|
||||
/**
|
||||
* 判断:指定 ApiKey 是否具有指定 Scope 列表 (AND 模式,需要全部具备),返回 true 或 false
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public static boolean hasApiKeyScope(String apiKey, String... scopes) {
|
||||
return SaManager.getSaApiKeyTemplate().hasApiKeyScope(apiKey, scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:指定 ApiKey 是否具有指定 Scope 列表 (AND 模式,需要全部具备),如果不具备则抛出异常
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public static void checkApiKeyScope(String apiKey, String... scopes) {
|
||||
SaManager.getSaApiKeyTemplate().checkApiKeyScope(apiKey, scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定 ApiKey 是否具有指定 Scope 列表 (OR 模式,具备其一即可),返回 true 或 false
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public static boolean hasApiKeyScopeOr(String apiKey, String... scopes) {
|
||||
return SaManager.getSaApiKeyTemplate().hasApiKeyScopeOr(apiKey, scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:指定 ApiKey 是否具有指定 Scope 列表 (OR 模式,具备其一即可),如果不具备则抛出异常
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public static void checkApiKeyScopeOr(String apiKey, String... scopes) {
|
||||
SaManager.getSaApiKeyTemplate().checkApiKeyScopeOr(apiKey, scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定 ApiKey 是否属于指定 LoginId,返回 true 或 false
|
||||
* @param apiKey /
|
||||
* @param loginId /
|
||||
*/
|
||||
public static boolean isApiKeyLoginId(String apiKey, Object loginId) {
|
||||
return SaManager.getSaApiKeyTemplate().isApiKeyLoginId(apiKey, loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:指定 ApiKey 是否属于指定 LoginId,如果不是则抛出异常
|
||||
*
|
||||
* @param apiKey /
|
||||
* @param loginId /
|
||||
*/
|
||||
public static void checkApiKeyLoginId(String apiKey, Object loginId) {
|
||||
SaManager.getSaApiKeyTemplate().checkApiKeyLoginId(apiKey, loginId);
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 请求查询
|
||||
|
||||
/**
|
||||
* 数据读取:从请求对象中读取 ApiKey,获取不到返回 null
|
||||
*/
|
||||
public static String readApiKeyValue(SaRequest request) {
|
||||
return SaManager.getSaApiKeyTemplate().readApiKeyValue(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据读取:从请求对象中读取 ApiKey,并查询到 ApiKeyModel 信息
|
||||
*/
|
||||
public static ApiKeyModel currentApiKey() {
|
||||
return SaManager.getSaApiKeyTemplate().currentApiKey();
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 索引操作
|
||||
|
||||
/**
|
||||
* 调整指定 SaSession 的 TTL 值,以保证最小化内存占用
|
||||
* @param loginId /
|
||||
* @param session 可填写 null,代表使用 loginId 现场查询
|
||||
*/
|
||||
public static void adjustIndex(Object loginId, SaSession session) {
|
||||
SaManager.getSaApiKeyTemplate().adjustIndex(loginId, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 loginId 的 ApiKey 列表记录
|
||||
* @param loginId /
|
||||
* @return /
|
||||
*/
|
||||
public static List<ApiKeyModel> getApiKeyList(Object loginId) {
|
||||
return SaManager.getSaApiKeyTemplate().getApiKeyList(loginId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.apikey.loader;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.apikey.model.ApiKeyModel;
|
||||
|
||||
/**
|
||||
* ApiKey 数据加载器
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public interface SaApiKeyDataLoader {
|
||||
|
||||
/**
|
||||
* 获取:框架是否保存索引信息
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
default Boolean getIsRecordIndex() {
|
||||
return SaManager.getConfig().getApiKey().getIsRecordIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 apiKey 从数据库获取 ApiKeyModel 信息 (实现此方法无需为数据做缓存处理,框架内部已包含缓存逻辑)
|
||||
*
|
||||
* @param namespace /
|
||||
* @param apiKey /
|
||||
* @return ApiKeyModel
|
||||
*/
|
||||
default ApiKeyModel getApiKeyModelFromDatabase(String namespace, String apiKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+7
-5
@@ -13,14 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.temp;
|
||||
package cn.dev33.satoken.apikey.loader;
|
||||
|
||||
/**
|
||||
* Sa-Token 临时令牌验证模块 默认实现类
|
||||
* ApiKey 数据加载器 默认实现类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.20.0
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaTempDefaultImpl implements SaTempInterface {
|
||||
|
||||
public class SaApiKeyDataLoaderDefaultImpl implements SaApiKeyDataLoader {
|
||||
|
||||
// be empty of
|
||||
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* 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.apikey.model;
|
||||
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.ApiKeyException;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Model: API Key
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class ApiKeyModel implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -6541180061782004705L;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 介绍
|
||||
*/
|
||||
private String intro;
|
||||
|
||||
/**
|
||||
* ApiKey 值
|
||||
*/
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* 账号 id
|
||||
*/
|
||||
private Object loginId;
|
||||
|
||||
/**
|
||||
* ApiKey 创建时间,13位时间戳
|
||||
*/
|
||||
private long createTime;
|
||||
|
||||
/**
|
||||
* ApiKey 到期时间,13位时间戳 (-1=永不过期)
|
||||
*/
|
||||
private long expiresTime;
|
||||
|
||||
/**
|
||||
* 是否有效 (true=生效, false=禁用)
|
||||
*/
|
||||
private Boolean isValid = true;
|
||||
|
||||
/**
|
||||
* 授权范围
|
||||
*/
|
||||
private List<String> scopes = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 扩展数据
|
||||
*/
|
||||
private Map<String, Object> extraData;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public ApiKeyModel() {
|
||||
this.createTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
// method
|
||||
|
||||
/**
|
||||
* 添加 Scope
|
||||
* @param scope /
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel addScope(String ...scope) {
|
||||
if (this.scopes == null) {
|
||||
this.scopes = new ArrayList<>();
|
||||
}
|
||||
this.scopes.addAll(Arrays.asList(scope));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 扩展数据
|
||||
* @param key /
|
||||
* @param value /
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel addExtra(String key, Object value) {
|
||||
if (this.extraData == null) {
|
||||
this.extraData = new LinkedHashMap<>();
|
||||
}
|
||||
this.extraData.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询扩展数据
|
||||
*/
|
||||
public Object getExtra(String key) {
|
||||
if (this.extraData == null) {
|
||||
return null;
|
||||
}
|
||||
return this.extraData.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除扩展数据
|
||||
*/
|
||||
public Object removeExtra(String key) {
|
||||
if (this.extraData == null) {
|
||||
return null;
|
||||
}
|
||||
return this.extraData.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据自检,判断是否可以保存入库
|
||||
*/
|
||||
public void checkByCanSaved() {
|
||||
if (SaFoxUtil.isEmpty(this.apiKey)) {
|
||||
throw new ApiKeyException("ApiKey 值不可为空").setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
|
||||
}
|
||||
if (this.loginId == null) {
|
||||
throw new ApiKeyException("无效 ApiKey: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
|
||||
}
|
||||
if (this.createTime == 0) {
|
||||
throw new ApiKeyException("请指定 createTime 创建时间").setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
|
||||
}
|
||||
if (this.expiresTime == 0) {
|
||||
throw new ApiKeyException("请指定 expiresTime 过期时间").setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
|
||||
}
|
||||
if (this.isValid == null) {
|
||||
throw new ApiKeyException("请指定 isValid 是否生效").setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:此 ApiKey 的剩余有效期(秒), -1=永不过期
|
||||
* @return /
|
||||
*/
|
||||
public long expiresIn() {
|
||||
if (expiresTime == SaTokenDao.NEVER_EXPIRE) {
|
||||
return SaTokenDao.NEVER_EXPIRE;
|
||||
}
|
||||
long s = (expiresTime - System.currentTimeMillis()) / 1000;
|
||||
return s < 1 ? -2 : s;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:此 ApiKey 是否已超时
|
||||
* @return /
|
||||
*/
|
||||
public boolean timeExpired() {
|
||||
if (expiresTime == SaTokenDao.NEVER_EXPIRE) {
|
||||
return false;
|
||||
}
|
||||
return System.currentTimeMillis() > expiresTime;
|
||||
}
|
||||
|
||||
|
||||
// get and set
|
||||
|
||||
/**
|
||||
* 获取 名称
|
||||
*
|
||||
* @return title 名称
|
||||
*/
|
||||
public String getTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 名称
|
||||
*
|
||||
* @param title 名称
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setTitle(String title) {
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 介绍
|
||||
*
|
||||
* @return intro 介绍
|
||||
*/
|
||||
public String getIntro() {
|
||||
return this.intro;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 介绍
|
||||
*
|
||||
* @param intro 介绍
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setIntro(String intro) {
|
||||
this.intro = intro;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApiKey 值
|
||||
*
|
||||
* @return apiKey ApiKey 值
|
||||
*/
|
||||
public String getApiKey() {
|
||||
return this.apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 ApiKey 值
|
||||
*
|
||||
* @param apiKey ApiKey 值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 账号 id
|
||||
*
|
||||
* @return loginId 账号 id
|
||||
*/
|
||||
public Object getLoginId() {
|
||||
return this.loginId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 账号 id
|
||||
*
|
||||
* @param loginId 账号 id
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setLoginId(Object loginId) {
|
||||
this.loginId = loginId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApiKey 创建时间,13位时间戳
|
||||
*
|
||||
* @return createTime ApiKey 创建时间,13位时间戳
|
||||
*/
|
||||
public long getCreateTime() {
|
||||
return this.createTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 ApiKey 创建时间,13位时间戳
|
||||
*
|
||||
* @param createTime ApiKey 创建时间,13位时间戳
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setCreateTime(long createTime) {
|
||||
this.createTime = createTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApiKey 到期时间,13位时间戳 (-1=永不过期)
|
||||
*
|
||||
* @return expiresTime ApiKey 到期时间,13位时间戳 (-1=永不过期)
|
||||
*/
|
||||
public long getExpiresTime() {
|
||||
return this.expiresTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 ApiKey 到期时间,13位时间戳 (-1=永不过期)
|
||||
*
|
||||
* @param expiresTime ApiKey 到期时间,13位时间戳 (-1=永不过期)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setExpiresTime(long expiresTime) {
|
||||
this.expiresTime = expiresTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 是否有效 (true=生效 false=禁用)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public Boolean getIsValid() {
|
||||
return this.isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 是否有效 (true=生效 false=禁用)
|
||||
*
|
||||
* @param isValid /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setIsValid(Boolean isValid) {
|
||||
this.isValid = isValid;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 授权范围
|
||||
*
|
||||
* @return scopes 授权范围
|
||||
*/
|
||||
public List<String> getScopes() {
|
||||
return this.scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 授权范围
|
||||
*
|
||||
* @param scopes 授权范围
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setScopes(List<String> scopes) {
|
||||
this.scopes = scopes;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 扩展数据
|
||||
*
|
||||
* @return extraData 扩展数据
|
||||
*/
|
||||
public Map<String, Object> getExtraData() {
|
||||
return this.extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 扩展数据
|
||||
*
|
||||
* @param extraData 扩展数据
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setExtraData(Map<String, Object> extraData) {
|
||||
this.extraData = extraData;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ApiKeyModel{" +
|
||||
"title='" + title +
|
||||
", intro='" + intro +
|
||||
", apiKey='" + apiKey +
|
||||
", loginId=" + loginId +
|
||||
", createTime=" + createTime +
|
||||
", expiresTime=" + expiresTime +
|
||||
", isValid=" + isValid +
|
||||
", scopes=" + scopes +
|
||||
", extraData=" + extraData +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -120,11 +120,8 @@ public interface SaGetValueInterface {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T> T getModel(String key, Class<T> cs, Object defaultValue) {
|
||||
Object value = get(key);
|
||||
if(valueIsNull(value)) {
|
||||
return (T)defaultValue;
|
||||
}
|
||||
return SaFoxUtil.getValueByType(value, cs);
|
||||
T model = getModel(key, cs);
|
||||
return valueIsNull(model) ? (T)defaultValue : model;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package cn.dev33.satoken.application;
|
||||
|
||||
import cn.dev33.satoken.fun.SaRetFunction;
|
||||
import cn.dev33.satoken.fun.SaRetGenericFunction;
|
||||
|
||||
/**
|
||||
* 对写值的一组方法封装
|
||||
@@ -55,7 +56,7 @@ public interface SaSetValueInterface extends SaGetValueInterface {
|
||||
* @return 值
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T> T get(String key, SaRetFunction fun) {
|
||||
default <T> T get(String key, SaRetGenericFunction<T> fun) {
|
||||
Object value = get(key);
|
||||
if(value == null) {
|
||||
value = fun.run();
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
/**
|
||||
* Sa-Token API Key 相关配置
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaApiKeyConfig {
|
||||
|
||||
/**
|
||||
* API Key 前缀
|
||||
*/
|
||||
private String prefix = "AK-";
|
||||
|
||||
/**
|
||||
* API Key 有效期,-1=永久有效,默认30天 (修改此配置项不会影响到已创建的 API Key)
|
||||
*/
|
||||
private long timeout = 2592000;
|
||||
|
||||
/**
|
||||
* 框架是否记录索引信息
|
||||
*/
|
||||
private Boolean isRecordIndex = true;
|
||||
|
||||
/**
|
||||
* 获取 API Key 前缀
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public String getPrefix() {
|
||||
return this.prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 API Key 前缀
|
||||
*
|
||||
* @param prefix /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaApiKeyConfig setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 API Key 有效期,-1=永久有效,默认30天 (修改此配置项不会影响到已创建的 API Key)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public long getTimeout() {
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 API Key 有效期,-1=永久有效,默认30天 (修改此配置项不会影响到已创建的 API Key)
|
||||
*
|
||||
* @param timeout /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaApiKeyConfig setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 框架是否保存索引信息
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public Boolean getIsRecordIndex() {
|
||||
return this.isRecordIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 框架是否保存索引信息
|
||||
*
|
||||
* @param isRecordIndex /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaApiKeyConfig setIsRecordIndex(Boolean isRecordIndex) {
|
||||
this.isRecordIndex = isRecordIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaApiKeyConfig{" +
|
||||
"prefix='" + prefix + '\'' +
|
||||
", timeout=" + timeout +
|
||||
", isRecordIndex=" + isRecordIndex +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
package cn.dev33.satoken.config;
|
||||
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.fun.SaParamRetFunction;
|
||||
import cn.dev33.satoken.secure.SaSecureUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token API 接口签名/验签 相关配置类
|
||||
*
|
||||
@@ -36,6 +40,10 @@ public class SaSignConfig {
|
||||
*/
|
||||
private long timestampDisparity = 1000 * 60 * 15;
|
||||
|
||||
/**
|
||||
* 对 fullStr 的摘要算法
|
||||
*/
|
||||
private String digestAlgo = "md5";
|
||||
|
||||
public SaSignConfig() {
|
||||
}
|
||||
@@ -48,6 +56,70 @@ public class SaSignConfig {
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
|
||||
// -------------- 扩展方法
|
||||
|
||||
/**
|
||||
* 计算保存 nonce 时应该使用的 ttl,单位:秒
|
||||
* @return /
|
||||
*/
|
||||
public long getSaveNonceExpire() {
|
||||
// 如果 timestampDisparity >= 0,则 nonceTtl 的值等于 timestampDisparity 的值,单位转秒
|
||||
if(timestampDisparity >= 0) {
|
||||
return timestampDisparity / 1000;
|
||||
}
|
||||
// 否则,nonceTtl 的值为 24 小时
|
||||
else {
|
||||
return 60 * 60 * 24;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -------------- 策略函数
|
||||
|
||||
/**
|
||||
* 对 fullStr 的摘要算法函数
|
||||
*/
|
||||
public SaParamRetFunction<String, String> digestMethod = (fullStr) -> {
|
||||
// md5
|
||||
if(digestAlgo.equalsIgnoreCase("md5")) {
|
||||
return SaSecureUtil.md5(fullStr);
|
||||
}
|
||||
// sha1
|
||||
if(digestAlgo.equalsIgnoreCase("sha1")) {
|
||||
return SaSecureUtil.sha1(fullStr);
|
||||
}
|
||||
// sha256
|
||||
if(digestAlgo.equalsIgnoreCase("sha256")) {
|
||||
return SaSecureUtil.sha256(fullStr);
|
||||
}
|
||||
// sha384
|
||||
if(digestAlgo.equalsIgnoreCase("sha384")) {
|
||||
return SaSecureUtil.sha384(fullStr);
|
||||
}
|
||||
// sha512
|
||||
if(digestAlgo.equalsIgnoreCase("sha512")) {
|
||||
return SaSecureUtil.sha512(fullStr);
|
||||
}
|
||||
// 未知
|
||||
throw new SaTokenException("不支持的摘要算法:" + digestAlgo + ",你可以自定义摘要算法函数实现");
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置: 对 fullStr 的摘要算法函数
|
||||
*
|
||||
* @param digestMethod /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSignConfig setDigestMethod(SaParamRetFunction<String, String> digestMethod) {
|
||||
this.digestMethod = digestMethod;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -------------- get/set
|
||||
|
||||
/**
|
||||
* 获取 API 调用签名秘钥
|
||||
*
|
||||
@@ -95,18 +167,22 @@ public class SaSignConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算保存 nonce 时应该使用的 ttl,单位:秒
|
||||
* 获取 对 fullStr 的摘要算法
|
||||
*
|
||||
* @return digestAlgo 对 fullStr 的摘要算法
|
||||
*/
|
||||
public String getDigestAlgo() {
|
||||
return this.digestAlgo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 对 fullStr 的摘要算法
|
||||
* @param digestAlgo /
|
||||
* @return /
|
||||
*/
|
||||
public long getSaveNonceExpire() {
|
||||
// 如果 timestampDisparity >= 0,则 nonceTtl 的值等于 timestampDisparity 的值,单位转秒
|
||||
if(timestampDisparity >= 0) {
|
||||
return timestampDisparity / 1000;
|
||||
}
|
||||
// 否则,nonceTtl 的值为 24 小时
|
||||
else {
|
||||
return 60 * 60 * 24;
|
||||
}
|
||||
public SaSignConfig setDigestAlgo(String digestAlgo) {
|
||||
this.digestAlgo = digestAlgo;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,15 +193,4 @@ public class SaSignConfig {
|
||||
+ "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置:是否校验 nonce 随机字符串
|
||||
* <h2> isCheckNonce 方案已废弃,不再提供此配置项 </h2>
|
||||
*
|
||||
* @param isCheckNonce /
|
||||
*/
|
||||
@Deprecated
|
||||
public void setIsCheckNonce(Boolean isCheckNonce) {
|
||||
System.err.println("--------- isCheckNonce 方案已废弃,不再提供此配置项 ---------");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,9 +15,14 @@
|
||||
*/
|
||||
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.SaReplacedRange;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Sa-Token 配置类 Model
|
||||
@@ -59,13 +64,23 @@ public class SaTokenConfig implements Serializable {
|
||||
/**
|
||||
* 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
|
||||
*/
|
||||
private Boolean isShare = true;
|
||||
private Boolean isShare = false;
|
||||
|
||||
/**
|
||||
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
|
||||
*/
|
||||
private SaReplacedRange replacedRange = SaReplacedRange.CURR_DEVICE_TYPE;
|
||||
|
||||
/**
|
||||
* 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置项才有意义)
|
||||
*/
|
||||
private int maxLoginCount = 12;
|
||||
|
||||
/**
|
||||
* 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
|
||||
*/
|
||||
private SaLogoutMode overflowLogoutMode = SaLogoutMode.LOGOUT;
|
||||
|
||||
/**
|
||||
* 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用)
|
||||
*/
|
||||
@@ -86,11 +101,38 @@ public class SaTokenConfig implements Serializable {
|
||||
*/
|
||||
private Boolean isReadCookie = true;
|
||||
|
||||
/**
|
||||
* 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
*/
|
||||
private Boolean isLastingCookie = true;
|
||||
|
||||
/**
|
||||
* 是否在登录后将 token 写入到响应头
|
||||
*/
|
||||
private Boolean isWriteHeader = false;
|
||||
|
||||
/**
|
||||
* 注销范围 (TOKEN=只注销当前 token 的会话,ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话)
|
||||
* <br/> (此参数只在调用 StpUtil.logout() 时有效)
|
||||
*/
|
||||
private SaLogoutRange logoutRange = SaLogoutRange.TOKEN;
|
||||
|
||||
/**
|
||||
* 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API)
|
||||
* <br/> (此参数只在调用 StpUtil.[logout/kickout/replaced]ByTokenValue("token") 时有效)
|
||||
*/
|
||||
private Boolean isLogoutKeepFreezeOps = false;
|
||||
|
||||
/**
|
||||
* 在注销 token 后,是否保留其对应的 Token-Session
|
||||
*/
|
||||
private Boolean isLogoutKeepTokenSession = false;
|
||||
|
||||
/**
|
||||
* 在登录时,是否立即创建对应的 Token-Session (true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建)
|
||||
*/
|
||||
private Boolean rightNowCreateTokenSession = false;
|
||||
|
||||
/**
|
||||
* token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
|
||||
*/
|
||||
@@ -116,6 +158,11 @@ public class SaTokenConfig implements Serializable {
|
||||
*/
|
||||
private String tokenPrefix;
|
||||
|
||||
/**
|
||||
* cookie 模式是否自动填充 token 前缀
|
||||
*/
|
||||
private Boolean cookieAutoFillPrefix = false;
|
||||
|
||||
/**
|
||||
* 是否在初始化配置时在控制台打印版本字符画
|
||||
*/
|
||||
@@ -181,6 +228,15 @@ public class SaTokenConfig implements Serializable {
|
||||
*/
|
||||
public SaSignConfig sign = new SaSignConfig();
|
||||
|
||||
/**
|
||||
* API 签名配置 多实例
|
||||
*/
|
||||
public Map<String, SaSignConfig> signMany = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* API Key 相关配置
|
||||
*/
|
||||
public SaApiKeyConfig apiKey = new SaApiKeyConfig();
|
||||
|
||||
/**
|
||||
* @return token 名称 (同时也是: cookie 名称、提交 token 时参数的名称、存储 token 时的 key 前缀)
|
||||
@@ -360,6 +416,26 @@ public class SaTokenConfig implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
*
|
||||
* @return isLastingCookie /
|
||||
*/
|
||||
public Boolean getIsLastingCookie() {
|
||||
return this.isLastingCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
*
|
||||
* @param isLastingCookie /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setIsLastingCookie(Boolean isLastingCookie) {
|
||||
this.isLastingCookie = isLastingCookie;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否在登录后将 token 写入到响应头
|
||||
*/
|
||||
@@ -455,7 +531,23 @@ public class SaTokenConfig implements Serializable {
|
||||
this.tokenPrefix = tokenPrefix;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return cookie 模式是否自动填充 token 前缀
|
||||
*/
|
||||
public Boolean getCookieAutoFillPrefix() {
|
||||
return cookieAutoFillPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cookieAutoFillPrefix cookie 模式是否自动填充 token 前缀
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setCookieAutoFillPrefix(Boolean cookieAutoFillPrefix) {
|
||||
this.cookieAutoFillPrefix = cookieAutoFillPrefix;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否在初始化配置时在控制台打印版本字符画
|
||||
*/
|
||||
@@ -637,7 +729,127 @@ public class SaTokenConfig implements Serializable {
|
||||
this.checkSameToken = checkSameToken;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端 ALL_DEVICE_TYPE=所有设备类型端)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public SaReplacedRange getReplacedRange() {
|
||||
return this.replacedRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端 ALL_DEVICE_TYPE=所有设备类型端)
|
||||
*
|
||||
* @param replacedRange /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setReplacedRange(SaReplacedRange replacedRange) {
|
||||
this.replacedRange = replacedRange;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public SaLogoutMode getOverflowLogoutMode() {
|
||||
return this.overflowLogoutMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
|
||||
*
|
||||
* @param overflowLogoutMode /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setOverflowLogoutMode(SaLogoutMode overflowLogoutMode) {
|
||||
this.overflowLogoutMode = overflowLogoutMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 注销范围 (TOKEN=只注销当前 token 的会话,ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话) <br> (此参数只在调用 StpUtil.logout() 时有效)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public SaLogoutRange getLogoutRange() {
|
||||
return this.logoutRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 注销范围 (TOKEN=只注销当前 token 的会话,ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话) <br> (此参数只在调用 StpUtil.logout() 时有效)
|
||||
*
|
||||
* @param logoutRange /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setLogoutRange(SaLogoutRange logoutRange) {
|
||||
this.logoutRange = logoutRange;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API) <br> (此参数只在调用 StpUtil.[logoutkickoutreplaced]ByTokenValue("token") 时有效)
|
||||
*
|
||||
* @return isLogoutKeepFreezeOps /
|
||||
*/
|
||||
public Boolean getIsLogoutKeepFreezeOps() {
|
||||
return this.isLogoutKeepFreezeOps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API) <br> (此参数只在调用 StpUtil.[logoutkickoutreplaced]ByTokenValue("token") 时有效)
|
||||
*
|
||||
* @param isLogoutKeepFreezeOps /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setIsLogoutKeepFreezeOps(Boolean isLogoutKeepFreezeOps) {
|
||||
this.isLogoutKeepFreezeOps = isLogoutKeepFreezeOps;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 在注销 token 后,是否保留其对应的 Token-Session
|
||||
*
|
||||
* @return isLogoutKeepTokenSession /
|
||||
*/
|
||||
public Boolean getIsLogoutKeepTokenSession() {
|
||||
return this.isLogoutKeepTokenSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 在注销 token 后,是否保留其对应的 Token-Session
|
||||
*
|
||||
* @param isLogoutKeepTokenSession /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setIsLogoutKeepTokenSession(Boolean isLogoutKeepTokenSession) {
|
||||
this.isLogoutKeepTokenSession = isLogoutKeepTokenSession;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 在登录时,是否立即创建对应的 Token-Session (true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public Boolean getRightNowCreateTokenSession() {
|
||||
return this.rightNowCreateTokenSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 在登录时,是否立即创建对应的 Token-Session (true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建)
|
||||
*
|
||||
* @param rightNowCreateTokenSession /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setRightNowCreateTokenSession(Boolean rightNowCreateTokenSession) {
|
||||
this.rightNowCreateTokenSession = rightNowCreateTokenSession;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Cookie 全局配置对象
|
||||
*/
|
||||
@@ -670,6 +882,47 @@ public class SaTokenConfig implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 API 签名配置 多实例
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public Map<String, SaSignConfig> getSignMany() {
|
||||
return this.signMany;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 API 签名配置 多实例
|
||||
*
|
||||
* @param signMany /
|
||||
* @return /
|
||||
*/
|
||||
public SaTokenConfig setSignMany(Map<String, SaSignConfig> signMany) {
|
||||
this.signMany = signMany;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Key 相关配置
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public SaApiKeyConfig getApiKey() {
|
||||
return this.apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 API Key 相关配置
|
||||
*
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public SaTokenConfig setApiKey(SaApiKeyConfig apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaTokenConfig ["
|
||||
@@ -677,19 +930,27 @@ public class SaTokenConfig implements Serializable {
|
||||
+ ", timeout=" + timeout
|
||||
+ ", activeTimeout=" + activeTimeout
|
||||
+ ", dynamicActiveTimeout=" + dynamicActiveTimeout
|
||||
+ ", isConcurrent=" + isConcurrent
|
||||
+ ", isShare=" + isShare
|
||||
+ ", isConcurrent=" + isConcurrent
|
||||
+ ", isShare=" + isShare
|
||||
+ ", replacedRange=" + replacedRange
|
||||
+ ", maxLoginCount=" + maxLoginCount
|
||||
+ ", overflowLogoutMode=" + overflowLogoutMode
|
||||
+ ", maxTryTimes=" + maxTryTimes
|
||||
+ ", isReadBody=" + isReadBody
|
||||
+ ", isReadHeader=" + isReadHeader
|
||||
+ ", isReadCookie=" + isReadCookie
|
||||
+ ", isLastingCookie=" + isLastingCookie
|
||||
+ ", isWriteHeader=" + isWriteHeader
|
||||
+ ", logoutRange=" + logoutRange
|
||||
+ ", isLogoutKeepFreezeOps=" + isLogoutKeepFreezeOps
|
||||
+ ", isLogoutKeepTokenSession=" + isLogoutKeepTokenSession
|
||||
+ ", rightNowCreateTokenSession=" + rightNowCreateTokenSession
|
||||
+ ", tokenStyle=" + tokenStyle
|
||||
+ ", dataRefreshPeriod=" + dataRefreshPeriod
|
||||
+ ", tokenSessionCheckLogin=" + tokenSessionCheckLogin
|
||||
+ ", autoRenew=" + autoRenew
|
||||
+ ", autoRenew=" + autoRenew
|
||||
+ ", tokenPrefix=" + tokenPrefix
|
||||
+ ", cookieAutoFillPrefix=" + cookieAutoFillPrefix
|
||||
+ ", isPrint=" + isPrint
|
||||
+ ", isLog=" + isLog
|
||||
+ ", logLevel=" + logLevel
|
||||
@@ -703,6 +964,8 @@ public class SaTokenConfig implements Serializable {
|
||||
+ ", checkSameToken=" + checkSameToken
|
||||
+ ", cookie=" + cookie
|
||||
+ ", sign=" + sign
|
||||
+ ", signMany=" + signMany
|
||||
+ ", apiKey=" + apiKey
|
||||
+ "]";
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public class SaHolder {
|
||||
* @return /
|
||||
*/
|
||||
public static SaTokenContext getContext() {
|
||||
return SaManager.getSaTokenContextOrSecond();
|
||||
return SaManager.getSaTokenContext();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +46,7 @@ public class SaHolder {
|
||||
* @return /
|
||||
*/
|
||||
public static SaRequest getRequest() {
|
||||
return SaManager.getSaTokenContextOrSecond().getRequest();
|
||||
return SaManager.getSaTokenContext().getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +56,7 @@ public class SaHolder {
|
||||
* @return /
|
||||
*/
|
||||
public static SaResponse getResponse() {
|
||||
return SaManager.getSaTokenContextOrSecond().getResponse();
|
||||
return SaManager.getSaTokenContext().getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +66,7 @@ public class SaHolder {
|
||||
* @return /
|
||||
*/
|
||||
public static SaStorage getStorage() {
|
||||
return SaManager.getSaTokenContextOrSecond().getStorage();
|
||||
return SaManager.getSaTokenContext().getStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
package cn.dev33.satoken.context;
|
||||
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.context.model.SaTokenContextModelBox;
|
||||
|
||||
/**
|
||||
* Sa-Token 上下文处理器
|
||||
@@ -30,51 +31,59 @@ import cn.dev33.satoken.context.model.SaResponse;
|
||||
public interface SaTokenContext {
|
||||
|
||||
/**
|
||||
* 获取当前请求的 Request 包装对象
|
||||
* 初始化上下文
|
||||
*
|
||||
* @param req /
|
||||
* @param res /
|
||||
* @param stg /
|
||||
*/
|
||||
void setContext(SaRequest req, SaResponse res, SaStorage stg);
|
||||
|
||||
/**
|
||||
* 清除化上下文
|
||||
*/
|
||||
void clearContext();
|
||||
|
||||
/**
|
||||
* 判断当前上下文是否可用
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
boolean isValid();
|
||||
|
||||
/**
|
||||
* 获取 Box 对象
|
||||
*/
|
||||
SaTokenContextModelBox getModelBox();
|
||||
|
||||
/**
|
||||
* 获取当前上下文的 Request 包装对象
|
||||
* @see SaRequest
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
SaRequest getRequest();
|
||||
default SaRequest getRequest() {
|
||||
return getModelBox().getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求的 Response 包装对象
|
||||
* 获取当前上下文的 Response 包装对象
|
||||
* @see SaResponse
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
SaResponse getResponse();
|
||||
default SaResponse getResponse(){
|
||||
return getModelBox().getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求的 Storage 包装对象
|
||||
* 获取当前上下文的 Storage 包装对象
|
||||
* @see SaStorage
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
SaStorage getStorage();
|
||||
|
||||
/**
|
||||
* 判断:指定路由匹配符是否可以匹配成功指定路径
|
||||
* <pre>
|
||||
* 判断规则由底层 web 框架决定,例如在 springboot 中:
|
||||
* - matchPath("/user/*", "/user/login") 返回: true
|
||||
* - matchPath("/user/*", "/article/edit") 返回: false
|
||||
* </pre>
|
||||
*
|
||||
* @param pattern 路由匹配符
|
||||
* @param path 需要匹配的路径
|
||||
* @return /
|
||||
*/
|
||||
boolean matchPath(String pattern, String path);
|
||||
|
||||
/**
|
||||
* 判断:在本次请求中,此上下文是否可用。
|
||||
* <p> 例如在部分 rpc 调用时, 一级上下文会返回 false,这时候框架就会选择使用二级上下文来处理请求 </p>
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
default boolean isValid() {
|
||||
return false;
|
||||
default SaStorage getStorage(){
|
||||
return getModelBox().getStorage();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+21
-5
@@ -18,6 +18,7 @@ package cn.dev33.satoken.context;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.context.model.SaTokenContextModelBox;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.SaTokenContextException;
|
||||
|
||||
@@ -44,6 +45,26 @@ public class SaTokenContextDefaultImpl implements SaTokenContext {
|
||||
*/
|
||||
public static final String ERROR_MESSAGE = "未能获取有效的上下文处理器";
|
||||
|
||||
@Override
|
||||
public void setContext(SaRequest req, SaResponse res, SaStorage stg) {
|
||||
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearContext() {
|
||||
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SaTokenContextModelBox getModelBox() {
|
||||
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SaRequest getRequest() {
|
||||
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
@@ -59,9 +80,4 @@ public class SaTokenContextDefaultImpl implements SaTokenContext {
|
||||
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+19
-21
@@ -13,36 +13,34 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.dao;
|
||||
package cn.dev33.satoken.context;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.context.model.SaTokenContextModelBox;
|
||||
|
||||
/**
|
||||
* Jackson 定制版 SaSession,忽略 timeout 等属性的序列化
|
||||
*
|
||||
* Sa-Token 上下文处理器次级实现:只读上下文
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.34.0
|
||||
* @since 1.42.0
|
||||
*/
|
||||
@JsonIgnoreProperties({"timeout"})
|
||||
public class SaSessionForJacksonCustomized extends SaSession {
|
||||
public interface SaTokenContextForReadOnly extends SaTokenContext {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -7600983549653130681L;
|
||||
@Override
|
||||
default void setContext(SaRequest req, SaResponse res, SaStorage stg) {
|
||||
|
||||
public SaSessionForJacksonCustomized() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建一个Session对象
|
||||
* @param id Session的id
|
||||
*/
|
||||
public SaSessionForJacksonCustomized(String id) {
|
||||
super(id);
|
||||
@Override
|
||||
default void clearContext() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
default SaTokenContextModelBox getModelBox() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+11
-15
@@ -18,6 +18,7 @@ package cn.dev33.satoken.context;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.context.model.SaTokenContextModelBox;
|
||||
|
||||
/**
|
||||
* Sa-Token 上下文处理器 [ ThreadLocal 版本 ]
|
||||
@@ -35,28 +36,23 @@ import cn.dev33.satoken.context.model.SaStorage;
|
||||
public class SaTokenContextForThreadLocal implements SaTokenContext {
|
||||
|
||||
@Override
|
||||
public SaRequest getRequest() {
|
||||
return SaTokenContextForThreadLocalStorage.getRequest();
|
||||
public void setContext(SaRequest req, SaResponse res, SaStorage stg) {
|
||||
SaTokenContextForThreadLocalStaff.setModelBox(req, res, stg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SaResponse getResponse() {
|
||||
return SaTokenContextForThreadLocalStorage.getResponse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SaStorage getStorage() {
|
||||
return SaTokenContextForThreadLocalStorage.getStorage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
return false;
|
||||
public void clearContext() {
|
||||
SaTokenContextForThreadLocalStaff.clearModelBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return SaTokenContextForThreadLocalStorage.getBox() != null;
|
||||
return SaTokenContextForThreadLocalStaff.getModelBoxOrNull() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SaTokenContextModelBox getModelBox() {
|
||||
return SaTokenContextForThreadLocalStaff.getModelBox();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
-66
@@ -18,6 +18,7 @@ package cn.dev33.satoken.context;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.context.model.SaTokenContextModelBox;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.SaTokenContextException;
|
||||
|
||||
@@ -29,12 +30,12 @@ import cn.dev33.satoken.exception.SaTokenContextException;
|
||||
* @author click33
|
||||
* @since 1.16.0
|
||||
*/
|
||||
public class SaTokenContextForThreadLocalStorage {
|
||||
public class SaTokenContextForThreadLocalStaff {
|
||||
|
||||
/**
|
||||
* 基于 ThreadLocal 的 [ Box 存储器 ]
|
||||
*/
|
||||
public static ThreadLocal<Box> boxThreadLocal = new InheritableThreadLocal<>();
|
||||
public static ThreadLocal<SaTokenContextModelBox> modelBoxThreadLocal = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 初始化当前线程的 [ Box 存储器 ]
|
||||
@@ -42,34 +43,34 @@ public class SaTokenContextForThreadLocalStorage {
|
||||
* @param response {@link SaResponse}
|
||||
* @param storage {@link SaStorage}
|
||||
*/
|
||||
public static void setBox(SaRequest request, SaResponse response, SaStorage storage) {
|
||||
Box bok = new Box(request, response, storage);
|
||||
boxThreadLocal.set(bok);
|
||||
public static void setModelBox(SaRequest request, SaResponse response, SaStorage storage) {
|
||||
SaTokenContextModelBox bok = new SaTokenContextModelBox(request, response, storage);
|
||||
modelBoxThreadLocal.set(bok);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前线程的 [ Box 存储器 ]
|
||||
*/
|
||||
public static void clearBox() {
|
||||
boxThreadLocal.remove();
|
||||
public static void clearModelBox() {
|
||||
modelBoxThreadLocal.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前线程的 [ Box 存储器 ]
|
||||
* @return /
|
||||
*/
|
||||
public static Box getBox() {
|
||||
return boxThreadLocal.get();
|
||||
public static SaTokenContextModelBox getModelBoxOrNull() {
|
||||
return modelBoxThreadLocal.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前线程的 [ Box 存储器 ], 如果为空则抛出异常
|
||||
* @return /
|
||||
*/
|
||||
public static Box getBoxNotNull() {
|
||||
Box box = boxThreadLocal.get();
|
||||
public static SaTokenContextModelBox getModelBox() {
|
||||
SaTokenContextModelBox box = modelBoxThreadLocal.get();
|
||||
if(box == null) {
|
||||
throw new SaTokenContextException("未能获取有效的上下文").setCode(SaErrorCode.CODE_10002);
|
||||
throw new SaTokenContextException("SaTokenContext 上下文尚未初始化").setCode(SaErrorCode.CODE_10002);
|
||||
}
|
||||
return box;
|
||||
}
|
||||
@@ -80,7 +81,7 @@ public class SaTokenContextForThreadLocalStorage {
|
||||
* @return /
|
||||
*/
|
||||
public static SaRequest getRequest() {
|
||||
return getBoxNotNull().getRequest();
|
||||
return getModelBox().getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,7 +90,7 @@ public class SaTokenContextForThreadLocalStorage {
|
||||
* @return /
|
||||
*/
|
||||
public static SaResponse getResponse() {
|
||||
return getBoxNotNull().getResponse();
|
||||
return getModelBox().getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,59 +99,8 @@ public class SaTokenContextForThreadLocalStorage {
|
||||
* @return /
|
||||
*/
|
||||
public static SaStorage getStorage() {
|
||||
return getBoxNotNull().getStorage();
|
||||
return getModelBox().getStorage();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Box 临时内部类,用于存储 [ SaRequest、SaResponse、SaStorage ] 三个包装对象
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.16.0
|
||||
*/
|
||||
public static class Box {
|
||||
|
||||
public SaRequest request;
|
||||
|
||||
public SaResponse response;
|
||||
|
||||
public SaStorage storage;
|
||||
|
||||
public Box(SaRequest request, SaResponse response, SaStorage storage){
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
public SaRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public void setRequest(SaRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public SaResponse getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public void setResponse(SaResponse response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public SaStorage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
public void setStorage(SaStorage storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Box [request=" + request + ", response=" + response + ", storage=" + storage + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.context.mock;
|
||||
|
||||
import cn.dev33.satoken.application.ApplicationInfo;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 对 SaRequest 包装类的实现(Mock 版)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaRequestForMock implements SaRequest {
|
||||
|
||||
public Map<String, String> parameterMap = new LinkedHashMap<>();
|
||||
public Map<String, String> headerMap = new LinkedHashMap<>();
|
||||
public Map<String, String> cookieMap = new LinkedHashMap<>();
|
||||
public String requestPath;
|
||||
public String url;
|
||||
public String method;
|
||||
public String host;
|
||||
public String forwardTo;
|
||||
|
||||
/**
|
||||
* 获取底层源对象
|
||||
*/
|
||||
@Override
|
||||
public Object getSource() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [请求体] 里获取一个值
|
||||
*/
|
||||
@Override
|
||||
public String getParam(String name) {
|
||||
return parameterMap.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 [请求体] 里提交的所有参数名称
|
||||
* @return 参数名称列表
|
||||
*/
|
||||
@Override
|
||||
public Collection<String> getParamNames(){
|
||||
return parameterMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 [请求体] 里提交的所有参数
|
||||
* @return 参数列表
|
||||
*/
|
||||
@Override
|
||||
public Map<String, String> getParamMap(){
|
||||
return parameterMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [请求头] 里获取一个值
|
||||
*/
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
return headerMap.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Cookie作用域] 里获取一个值
|
||||
*/
|
||||
@Override
|
||||
public String getCookieValue(String name) {
|
||||
return getCookieLastValue(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的)
|
||||
*/
|
||||
@Override
|
||||
public String getCookieFirstValue(String name){
|
||||
return cookieMap.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的)
|
||||
* @param name 键
|
||||
* @return 值
|
||||
*/
|
||||
@Override
|
||||
public String getCookieLastValue(String name){
|
||||
return cookieMap.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前请求path (不包括上下文名称)
|
||||
*/
|
||||
@Override
|
||||
public String getRequestPath() {
|
||||
return ApplicationInfo.cutPathPrefix(requestPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前请求的url,例:http://xxx.com/test
|
||||
* @return see note
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前请求的类型
|
||||
*/
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询请求 host
|
||||
*/
|
||||
@Override
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转发请求
|
||||
*/
|
||||
@Override
|
||||
public Object forward(String path) {
|
||||
this.forwardTo = path;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.context.mock;
|
||||
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 对 SaResponse 包装类的实现(Mock 版)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaResponseForMock implements SaResponse {
|
||||
|
||||
public int status;
|
||||
public Map<String, String> headerMap = new LinkedHashMap<>();
|
||||
public String redirectTo;
|
||||
|
||||
|
||||
/**
|
||||
* 获取底层源对象
|
||||
*/
|
||||
@Override
|
||||
public Object getSource() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置响应状态码
|
||||
*/
|
||||
@Override
|
||||
public SaResponse setStatus(int sc) {
|
||||
this.status = sc;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在响应头里写入一个值
|
||||
*/
|
||||
@Override
|
||||
public SaResponse setHeader(String name, String value) {
|
||||
headerMap.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在响应头里添加一个值
|
||||
* @param name 名字
|
||||
* @param value 值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaResponse addHeader(String name, String value) {
|
||||
headerMap.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重定向
|
||||
*/
|
||||
@Override
|
||||
public Object redirect(String url) {
|
||||
this.redirectTo = url;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.context.mock;
|
||||
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 对 SaStorage 包装类的实现(Mock 版)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaStorageForMock implements SaStorage {
|
||||
|
||||
public Map<String, Object> dataMap = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取底层源对象
|
||||
*/
|
||||
@Override
|
||||
public Object getSource() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Request作用域] 里写入一个值
|
||||
*/
|
||||
@Override
|
||||
public SaStorageForMock set(String key, Object value) {
|
||||
dataMap.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Request作用域] 里获取一个值
|
||||
*/
|
||||
@Override
|
||||
public Object get(String key) {
|
||||
return dataMap.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Request作用域] 里删除一个值
|
||||
*/
|
||||
@Override
|
||||
public SaStorageForMock delete(String key) {
|
||||
dataMap.remove(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.context.mock;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.fun.SaFunction;
|
||||
import cn.dev33.satoken.fun.SaRetGenericFunction;
|
||||
|
||||
/**
|
||||
* Sa-Token Mock 上下文 操作工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaTokenContextMockUtil {
|
||||
|
||||
/**
|
||||
* 写入 Mock 上下文
|
||||
*/
|
||||
public static void setMockContext() {
|
||||
SaRequestForMock request = new SaRequestForMock();
|
||||
SaResponseForMock response = new SaResponseForMock();
|
||||
SaStorageForMock storage = new SaStorageForMock();
|
||||
SaManager.getSaTokenContext().setContext(request, response, storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入 Mock 上下文,并执行一段代码,执行完毕后清除上下文
|
||||
*
|
||||
* @param fun /
|
||||
*/
|
||||
public static void setMockContext(SaFunction fun) {
|
||||
try {
|
||||
setMockContext();
|
||||
fun.run();
|
||||
} finally {
|
||||
clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入 Mock 上下文,并执行一段代码,执行完毕后清除上下文
|
||||
*
|
||||
* @param fun /
|
||||
*/
|
||||
public static <T> T setMockContext(SaRetGenericFunction<T> fun) {
|
||||
try {
|
||||
setMockContext();
|
||||
return fun.run();
|
||||
} finally {
|
||||
clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除上下文
|
||||
*/
|
||||
public static void clearContext() {
|
||||
SaManager.getSaTokenContext().clearContext();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import cn.dev33.satoken.router.SaHttpMethod;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -191,6 +190,12 @@ public interface SaRequest {
|
||||
return getMethod().equals(method.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询请求 host
|
||||
* @return /
|
||||
*/
|
||||
String getHost();
|
||||
|
||||
/**
|
||||
* 判断此请求是否为 Ajax 异步请求
|
||||
* @return /
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package cn.dev33.satoken.context.model;
|
||||
|
||||
/**
|
||||
* Box 盒子类,用于存储 [ SaRequest、SaResponse、SaStorage ] 三个包装对象
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.16.0
|
||||
*/
|
||||
public class SaTokenContextModelBox {
|
||||
|
||||
public SaRequest request;
|
||||
|
||||
public SaResponse response;
|
||||
|
||||
public SaStorage storage;
|
||||
|
||||
public SaTokenContextModelBox(SaRequest request, SaResponse response, SaStorage storage) {
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
public SaRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public void setRequest(SaRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public SaResponse getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public void setResponse(SaResponse response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public SaStorage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
public void setStorage(SaStorage storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Box [request=" + request + ", response=" + response + ", storage=" + storage + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -90,15 +90,25 @@ public interface SaTokenDao {
|
||||
|
||||
/**
|
||||
* 获取 Object,如无返空
|
||||
* @param key 键名称
|
||||
*
|
||||
* @param key 键名称
|
||||
* @return object
|
||||
*/
|
||||
Object getObject(String key);
|
||||
|
||||
/**
|
||||
* 获取 Object (指定反序列化类型),如无返空
|
||||
*
|
||||
* @param key 键名称
|
||||
* @return object
|
||||
*/
|
||||
<T> T getObject(String key, Class<T> classType);
|
||||
|
||||
/**
|
||||
* 写入 Object,并设定存活时间 (单位: 秒)
|
||||
* @param key 键名称
|
||||
* @param object 值
|
||||
*
|
||||
* @param key 键名称
|
||||
* @param object 值
|
||||
* @param timeout 存活时间(值大于0时限时存储,值=-1时永久存储,值=0或小于-2时不存储)
|
||||
*/
|
||||
void setObject(String key, Object object, long timeout);
|
||||
@@ -138,52 +148,40 @@ public interface SaTokenDao {
|
||||
* @param sessionId sessionId
|
||||
* @return SaSession
|
||||
*/
|
||||
default SaSession getSession(String sessionId) {
|
||||
return (SaSession)getObject(sessionId);
|
||||
}
|
||||
SaSession getSession(String sessionId);
|
||||
|
||||
/**
|
||||
* 写入 SaSession,并设定存活时间(单位: 秒)
|
||||
* @param session 要保存的 SaSession 对象
|
||||
* @param timeout 过期时间(单位: 秒)
|
||||
*/
|
||||
default void setSession(SaSession session, long timeout) {
|
||||
setObject(session.getId(), session, timeout);
|
||||
}
|
||||
void setSession(SaSession session, long timeout);
|
||||
|
||||
/**
|
||||
* 更新 SaSession
|
||||
* @param session 要更新的 SaSession 对象
|
||||
*/
|
||||
default void updateSession(SaSession session) {
|
||||
updateObject(session.getId(), session);
|
||||
}
|
||||
void updateSession(SaSession session);
|
||||
|
||||
/**
|
||||
* 删除 SaSession
|
||||
* @param sessionId sessionId
|
||||
*/
|
||||
default void deleteSession(String sessionId) {
|
||||
deleteObject(sessionId);
|
||||
}
|
||||
void deleteSession(String sessionId);
|
||||
|
||||
/**
|
||||
* 获取 SaSession 剩余存活时间(单位: 秒)
|
||||
* @param sessionId 指定 SaSession
|
||||
* @return 这个 SaSession 的剩余存活时间
|
||||
*/
|
||||
default long getSessionTimeout(String sessionId) {
|
||||
return getObjectTimeout(sessionId);
|
||||
}
|
||||
long getSessionTimeout(String sessionId);
|
||||
|
||||
/**
|
||||
* 修改 SaSession 剩余存活时间(单位: 秒)
|
||||
* @param sessionId 指定 SaSession
|
||||
* @param timeout 剩余存活时间
|
||||
*/
|
||||
default void updateSessionTimeout(String sessionId, long timeout) {
|
||||
updateObjectTimeout(sessionId, timeout);
|
||||
}
|
||||
void updateSessionTimeout(String sessionId, long timeout);
|
||||
|
||||
|
||||
// --------------------- 会话管理 ---------------------
|
||||
|
||||
@@ -16,250 +16,81 @@
|
||||
package cn.dev33.satoken.dao;
|
||||
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.dao.auto.SaTokenDaoByStringFollowObject;
|
||||
import cn.dev33.satoken.dao.timedcache.SaMapPackageForConcurrentHashMap;
|
||||
import cn.dev33.satoken.dao.timedcache.SaTimedCache;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Sa-Token 持久层接口,默认实现类(基于内存 Map,系统重启后数据丢失)
|
||||
* Sa-Token 持久层接口,默认实现类,基于 SaTimedCache - ConcurrentHashMap (内存缓存,系统重启后数据丢失)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.10.0
|
||||
*/
|
||||
public class SaTokenDaoDefaultImpl implements SaTokenDao {
|
||||
|
||||
/**
|
||||
* 存储数据的集合
|
||||
*/
|
||||
public Map<String, Object> dataMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 存储数据过期时间的集合(单位: 毫秒), 记录所有 key 的到期时间 (注意存储的是到期时间,不是剩余存活时间)
|
||||
*/
|
||||
public Map<String, Long> expireMap = new ConcurrentHashMap<>();
|
||||
|
||||
// ------------------------ String 读写操作
|
||||
|
||||
@Override
|
||||
public String get(String key) {
|
||||
clearKeyByTimeout(key);
|
||||
return (String)dataMap.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(String key, String value, long timeout) {
|
||||
if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
dataMap.put(key, value);
|
||||
expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(String key, String value) {
|
||||
if(getKeyTimeout(key) == SaTokenDao.NOT_VALUE_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
dataMap.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String key) {
|
||||
dataMap.remove(key);
|
||||
expireMap.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeout(String key) {
|
||||
return getKeyTimeout(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTimeout(String key, long timeout) {
|
||||
expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
|
||||
}
|
||||
public class SaTokenDaoDefaultImpl implements SaTokenDaoByStringFollowObject {
|
||||
|
||||
public SaTimedCache timedCache = new SaTimedCache(
|
||||
new SaMapPackageForConcurrentHashMap<>(),
|
||||
new SaMapPackageForConcurrentHashMap<>()
|
||||
);
|
||||
|
||||
// ------------------------ Object 读写操作
|
||||
|
||||
@Override
|
||||
public Object getObject(String key) {
|
||||
clearKeyByTimeout(key);
|
||||
return dataMap.get(key);
|
||||
return timedCache.getObject(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getObject(String key, Class<T> classType){
|
||||
return (T) getObject(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setObject(String key, Object object, long timeout) {
|
||||
if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
dataMap.put(key, object);
|
||||
expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
|
||||
timedCache.setObject(key, object, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObject(String key, Object object) {
|
||||
if(getKeyTimeout(key) == SaTokenDao.NOT_VALUE_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
dataMap.put(key, object);
|
||||
timedCache.updateObject(key, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteObject(String key) {
|
||||
dataMap.remove(key);
|
||||
expireMap.remove(key);
|
||||
timedCache.deleteObject(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getObjectTimeout(String key) {
|
||||
return getKeyTimeout(key);
|
||||
return timedCache.getObjectTimeout(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjectTimeout(String key, long timeout) {
|
||||
expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
|
||||
timedCache.updateObjectTimeout(key, timeout);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------ Session 读写操作
|
||||
// 使用接口默认实现
|
||||
|
||||
|
||||
// --------- 会话管理
|
||||
|
||||
@Override
|
||||
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
|
||||
return SaFoxUtil.searchList(expireMap.keySet(), prefix, keyword, start, size, sortType);
|
||||
return SaFoxUtil.searchList(timedCache.keySet(), prefix, keyword, start, size, sortType);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------ 以下是一个定时缓存的简单实现,采用:惰性检查 + 异步循环扫描
|
||||
|
||||
// --------- 过期时间相关操作
|
||||
|
||||
/**
|
||||
* 如果指定的 key 已经过期,则立即清除它
|
||||
* @param key 指定 key
|
||||
*/
|
||||
void clearKeyByTimeout(String key) {
|
||||
Long expirationTime = expireMap.get(key);
|
||||
// 清除条件:
|
||||
// 1、数据存在。
|
||||
// 2、不是 [ 永不过期 ]。
|
||||
// 3、已经超过过期时间。
|
||||
if(expirationTime != null && expirationTime != SaTokenDao.NEVER_EXPIRE && expirationTime < System.currentTimeMillis()) {
|
||||
dataMap.remove(key);
|
||||
expireMap.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 key 的剩余存活时间 (单位:秒)
|
||||
* @param key 指定 key
|
||||
* @return 这个 key 的剩余存活时间
|
||||
*/
|
||||
long getKeyTimeout(String key) {
|
||||
// 由于数据过期检测属于惰性扫描,很可能此时这个 key 已经是过期状态了,所以这里需要先检查一下
|
||||
clearKeyByTimeout(key);
|
||||
|
||||
// 获取这个 key 的过期时间
|
||||
Long expire = expireMap.get(key);
|
||||
|
||||
// 如果 expire 数据不存在,说明框架没有存储这个 key,此时返回 NOT_VALUE_EXPIRE
|
||||
if(expire == null) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
|
||||
// 如果 expire 被标注为永不过期,则返回 NEVER_EXPIRE
|
||||
if(expire == SaTokenDao.NEVER_EXPIRE) {
|
||||
return SaTokenDao.NEVER_EXPIRE;
|
||||
}
|
||||
|
||||
// ---- 代码至此,说明这个 key 是有过期时间的,且未过期,那么:
|
||||
|
||||
// 计算剩余时间并返回 (过期时间戳 - 当前时间戳) / 1000 转秒
|
||||
long timeout = (expire - System.currentTimeMillis()) / 1000;
|
||||
|
||||
// 小于零时,视为不存在
|
||||
if(timeout < 0) {
|
||||
dataMap.remove(key);
|
||||
expireMap.remove(key);
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
return timeout;
|
||||
}
|
||||
|
||||
// --------- 定时清理过期数据
|
||||
|
||||
/**
|
||||
* 执行数据清理的线程引用
|
||||
*/
|
||||
public Thread refreshThread;
|
||||
|
||||
/**
|
||||
* 是否继续执行数据清理的线程标记
|
||||
*/
|
||||
public volatile boolean refreshFlag;
|
||||
|
||||
/**
|
||||
* 清理所有已经过期的 key
|
||||
*/
|
||||
public void refreshDataMap() {
|
||||
for (String s : expireMap.keySet()) {
|
||||
clearKeyByTimeout(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化定时任务,定时清理过期数据
|
||||
*/
|
||||
public void initRefreshThread() {
|
||||
|
||||
// 如果开发者配置了 <=0 的值,则不启动定时清理
|
||||
if(SaManager.getConfig().getDataRefreshPeriod() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动定时刷新
|
||||
this.refreshFlag = true;
|
||||
this.refreshThread = new Thread(() -> {
|
||||
for (;;) {
|
||||
try {
|
||||
try {
|
||||
// 如果已经被标记为结束
|
||||
if( ! refreshFlag) {
|
||||
return;
|
||||
}
|
||||
// 执行清理
|
||||
refreshDataMap();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// 休眠N秒
|
||||
int dataRefreshPeriod = SaManager.getConfig().getDataRefreshPeriod();
|
||||
if(dataRefreshPeriod <= 0) {
|
||||
dataRefreshPeriod = 1;
|
||||
}
|
||||
Thread.sleep(dataRefreshPeriod * 1000L);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.refreshThread.start();
|
||||
}
|
||||
|
||||
// --------- 组件生命周期
|
||||
|
||||
/**
|
||||
* 组件被安装时,开始刷新数据线程
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
initRefreshThread();
|
||||
timedCache.initRefreshThread();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,6 +98,6 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
|
||||
*/
|
||||
@Override
|
||||
public void destroy() {
|
||||
this.refreshFlag = false;
|
||||
timedCache.endRefreshThread();
|
||||
}
|
||||
}
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.dao.auto;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
|
||||
/**
|
||||
* SaTokenDao 次级实现,Object 读写跟随 String 读写 (推荐中间件型缓存实现 implements 此接口)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public interface SaTokenDaoByObjectFollowString extends SaTokenDaoBySessionFollowObject {
|
||||
|
||||
// --------------------- Object 读写 ---------------------
|
||||
|
||||
/**
|
||||
* 获取 Object,如无返空
|
||||
*
|
||||
* @param key 键名称
|
||||
* @return object
|
||||
*/
|
||||
@Override
|
||||
default Object getObject(String key) {
|
||||
String jsonString = get(key);
|
||||
return SaManager.getSaSerializerTemplate().stringToObject(jsonString);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Object (指定反序列化类型),如无返空
|
||||
*
|
||||
* @param key 键名称
|
||||
* @return object
|
||||
*/
|
||||
default <T> T getObject(String key, Class<T> classType) {
|
||||
String jsonString = get(key);
|
||||
return SaManager.getSaSerializerTemplate().stringToObject(jsonString, classType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入 Object,并设定存活时间 (单位: 秒)
|
||||
*
|
||||
* @param key 键名称
|
||||
* @param object 值
|
||||
* @param timeout 存活时间(值大于0时限时存储,值=-1时永久存储,值=0或小于-2时不存储)
|
||||
*/
|
||||
@Override
|
||||
default void setObject(String key, Object object, long timeout) {
|
||||
String jsonString = SaManager.getSaSerializerTemplate().objectToString(object);
|
||||
set(key, jsonString, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Object (过期时间不变)
|
||||
* @param key 键名称
|
||||
* @param object 值
|
||||
*/
|
||||
@Override
|
||||
default void updateObject(String key, Object object) {
|
||||
String jsonString = SaManager.getSaSerializerTemplate().objectToString(object);
|
||||
update(key, jsonString);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Object
|
||||
* @param key 键名称
|
||||
*/
|
||||
@Override
|
||||
default void deleteObject(String key) {
|
||||
delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Object 的剩余存活时间 (单位: 秒)
|
||||
* @param key 指定 key
|
||||
* @return 这个 key 的剩余存活时间
|
||||
*/
|
||||
@Override
|
||||
default long getObjectTimeout(String key) {
|
||||
return getTimeout(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 Object 的剩余存活时间(单位: 秒)
|
||||
* @param key 指定 key
|
||||
* @param timeout 剩余存活时间
|
||||
*/
|
||||
@Override
|
||||
default void updateObjectTimeout(String key, long timeout) {
|
||||
updateTimeout(key, timeout);
|
||||
}
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.dao.auto;
|
||||
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
|
||||
/**
|
||||
* SaTokenDao 次级实现:SaSession 读写跟随 Object 读写
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public interface SaTokenDaoBySessionFollowObject extends SaTokenDao {
|
||||
|
||||
// --------------------- SaSession 读写 (默认复用 Object 读写方法) ---------------------
|
||||
|
||||
/**
|
||||
* 获取 SaSession,如无返空
|
||||
* @param sessionId sessionId
|
||||
* @return SaSession
|
||||
*/
|
||||
default SaSession getSession(String sessionId) {
|
||||
return getObject(sessionId, SaStrategy.instance.sessionClassType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入 SaSession,并设定存活时间(单位: 秒)
|
||||
* @param session 要保存的 SaSession 对象
|
||||
* @param timeout 过期时间(单位: 秒)
|
||||
*/
|
||||
default void setSession(SaSession session, long timeout) {
|
||||
setObject(session.getId(), session, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 SaSession
|
||||
* @param session 要更新的 SaSession 对象
|
||||
*/
|
||||
default void updateSession(SaSession session) {
|
||||
updateObject(session.getId(), session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 SaSession
|
||||
* @param sessionId sessionId
|
||||
*/
|
||||
default void deleteSession(String sessionId) {
|
||||
deleteObject(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SaSession 剩余存活时间(单位: 秒)
|
||||
* @param sessionId 指定 SaSession
|
||||
* @return 这个 SaSession 的剩余存活时间
|
||||
*/
|
||||
default long getSessionTimeout(String sessionId) {
|
||||
return getObjectTimeout(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 SaSession 剩余存活时间(单位: 秒)
|
||||
* @param sessionId 指定 SaSession
|
||||
* @param timeout 剩余存活时间
|
||||
*/
|
||||
default void updateSessionTimeout(String sessionId, long timeout) {
|
||||
updateObjectTimeout(sessionId, timeout);
|
||||
}
|
||||
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.dao.auto;
|
||||
|
||||
/**
|
||||
* SaTokenDao 次级实现:String 读写跟随 Object 读写 (推荐内存型缓存实现 implements 此接口)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public interface SaTokenDaoByStringFollowObject extends SaTokenDaoBySessionFollowObject {
|
||||
|
||||
// --------------------- String 读写 ---------------------
|
||||
|
||||
@Override
|
||||
default String get(String key) {
|
||||
return (String) getObject(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void set(String key, String value, long timeout) {
|
||||
setObject(key, value, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void update(String key, String value) {
|
||||
updateObject(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void delete(String key) {
|
||||
deleteObject(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
default long getTimeout(String key) {
|
||||
return getObjectTimeout(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void updateTimeout(String key, long timeout) {
|
||||
updateObjectTimeout(key, timeout);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.dao.timedcache;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Map 包装类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public interface SaMapPackage<V> {
|
||||
|
||||
/**
|
||||
* 获取底层被包装的源对象
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
Object getSource();
|
||||
|
||||
|
||||
/**
|
||||
* 读
|
||||
*
|
||||
* @param key /
|
||||
* @return /
|
||||
*/
|
||||
V get(String key);
|
||||
|
||||
/**
|
||||
* 写
|
||||
*
|
||||
* @param key /
|
||||
* @param value /
|
||||
*/
|
||||
void put(String key, V value);
|
||||
|
||||
/**
|
||||
* 删
|
||||
* @param key /
|
||||
*/
|
||||
void remove(String key);
|
||||
|
||||
/**
|
||||
* 所有 key
|
||||
*/
|
||||
Set<String> keySet();
|
||||
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.dao.timedcache;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Map 包装类 (ConcurrentHashMap 版)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class SaMapPackageForConcurrentHashMap<V> implements SaMapPackage<V> {
|
||||
|
||||
private final ConcurrentHashMap<String, V> map = new ConcurrentHashMap<String, V>();
|
||||
|
||||
@Override
|
||||
public Object getSource() {
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(String key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String key, V value) {
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String key) {
|
||||
map.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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.dao.timedcache;
|
||||
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 一个定时缓存的简单实现,采用:惰性检查 + 异步循环扫描
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class SaTimedCache {
|
||||
|
||||
/**
|
||||
* 存储数据的集合
|
||||
*/
|
||||
public SaMapPackage<Object> dataMap;
|
||||
|
||||
/**
|
||||
* 存储数据过期时间的集合(单位: 毫秒), 记录所有 key 的到期时间 (注意存储的是到期时间,不是剩余存活时间)
|
||||
*/
|
||||
public SaMapPackage<Long> expireMap;
|
||||
|
||||
public SaTimedCache(SaMapPackage<Object> dataMap, SaMapPackage<Long> expireMap) {
|
||||
this.dataMap = dataMap;
|
||||
this.expireMap = expireMap;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------ 基础 API 读写操作
|
||||
|
||||
public Object getObject(String key) {
|
||||
clearKeyByTimeout(key);
|
||||
return dataMap.get(key);
|
||||
}
|
||||
|
||||
public void setObject(String key, Object object, long timeout) {
|
||||
if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
dataMap.put(key, object);
|
||||
expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
|
||||
}
|
||||
|
||||
public void updateObject(String key, Object object) {
|
||||
if(getKeyTimeout(key) == SaTokenDao.NOT_VALUE_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
dataMap.put(key, object);
|
||||
}
|
||||
|
||||
public void deleteObject(String key) {
|
||||
dataMap.remove(key);
|
||||
expireMap.remove(key);
|
||||
}
|
||||
|
||||
public long getObjectTimeout(String key) {
|
||||
return getKeyTimeout(key);
|
||||
}
|
||||
|
||||
public void updateObjectTimeout(String key, long timeout) {
|
||||
expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
|
||||
}
|
||||
|
||||
public Set<String> keySet() {
|
||||
return dataMap.keySet();
|
||||
}
|
||||
|
||||
|
||||
// --------- 过期时间相关操作
|
||||
|
||||
/**
|
||||
* 如果指定的 key 已经过期,则立即清除它
|
||||
* @param key 指定 key
|
||||
*/
|
||||
void clearKeyByTimeout(String key) {
|
||||
Long expirationTime = expireMap.get(key);
|
||||
// 清除条件:
|
||||
// 1、数据存在。
|
||||
// 2、不是 [ 永不过期 ]。
|
||||
// 3、已经超过过期时间。
|
||||
if(expirationTime != null && expirationTime != SaTokenDao.NEVER_EXPIRE && expirationTime < System.currentTimeMillis()) {
|
||||
dataMap.remove(key);
|
||||
expireMap.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 key 的剩余存活时间 (单位:秒)
|
||||
* @param key 指定 key
|
||||
* @return 这个 key 的剩余存活时间
|
||||
*/
|
||||
long getKeyTimeout(String key) {
|
||||
// 由于数据过期检测属于惰性扫描,很可能此时这个 key 已经是过期状态了,所以这里需要先检查一下
|
||||
clearKeyByTimeout(key);
|
||||
|
||||
// 获取这个 key 的过期时间
|
||||
Long expire = expireMap.get(key);
|
||||
|
||||
// 如果 expire 数据不存在,说明框架没有存储这个 key,此时返回 NOT_VALUE_EXPIRE
|
||||
if(expire == null) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
|
||||
// 如果 expire 被标注为永不过期,则返回 NEVER_EXPIRE
|
||||
if(expire == SaTokenDao.NEVER_EXPIRE) {
|
||||
return SaTokenDao.NEVER_EXPIRE;
|
||||
}
|
||||
|
||||
// ---- 代码至此,说明这个 key 是有过期时间的,且未过期,那么:
|
||||
|
||||
// 计算剩余时间并返回 (过期时间戳 - 当前时间戳) / 1000 转秒
|
||||
long timeout = (expire - System.currentTimeMillis()) / 1000;
|
||||
|
||||
// 小于零时,视为不存在
|
||||
if(timeout < 0) {
|
||||
dataMap.remove(key);
|
||||
expireMap.remove(key);
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
return timeout;
|
||||
}
|
||||
|
||||
// --------- 定时清理过期数据
|
||||
|
||||
/**
|
||||
* 执行数据清理的线程引用
|
||||
*/
|
||||
public Thread refreshThread;
|
||||
|
||||
/**
|
||||
* 是否继续执行数据清理的线程标记
|
||||
*/
|
||||
public volatile boolean refreshFlag;
|
||||
|
||||
/**
|
||||
* 清理所有已经过期的 key
|
||||
*/
|
||||
public void refreshDataMap() {
|
||||
for (String s : expireMap.keySet()) {
|
||||
clearKeyByTimeout(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化定时任务,定时清理过期数据
|
||||
*/
|
||||
public void initRefreshThread() {
|
||||
|
||||
// 如果开发者配置了 <=0 的值,则不启动定时清理
|
||||
if(SaManager.getConfig().getDataRefreshPeriod() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动定时刷新
|
||||
this.refreshFlag = true;
|
||||
this.refreshThread = new Thread(() -> {
|
||||
for (;;) {
|
||||
try {
|
||||
try {
|
||||
// 如果已经被标记为结束
|
||||
if( ! refreshFlag) {
|
||||
return;
|
||||
}
|
||||
// 执行清理
|
||||
refreshDataMap();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// 休眠N秒
|
||||
int dataRefreshPeriod = SaManager.getConfig().getDataRefreshPeriod();
|
||||
if(dataRefreshPeriod <= 0) {
|
||||
dataRefreshPeriod = 1;
|
||||
}
|
||||
Thread.sleep(dataRefreshPeriod * 1000L);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.refreshThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束定时任务
|
||||
*/
|
||||
public void endRefreshThread() {
|
||||
this.refreshFlag = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -128,6 +128,9 @@ public interface SaErrorCode {
|
||||
/** 获取 Token-Session 时提供的 token 为空 */
|
||||
int CODE_11073 = 11073;
|
||||
|
||||
/** 获取 Token-Session 时提供的 token 为无效 token */
|
||||
int CODE_11074 = 11074;
|
||||
|
||||
|
||||
// ------------
|
||||
|
||||
@@ -198,4 +201,35 @@ public interface SaErrorCode {
|
||||
/** timestamp 超出允许的范围 */
|
||||
int CODE_12203 = 12203;
|
||||
|
||||
/** 未找到对应 appid 的 SaSignConfig */
|
||||
int CODE_12211 = 12211;
|
||||
|
||||
// ------------
|
||||
|
||||
/** 无效 API Key */
|
||||
int CODE_12301 = 12301;
|
||||
|
||||
/** API Key 已过期 */
|
||||
int CODE_12302 = 12302;
|
||||
|
||||
/** API Key 已被禁用 */
|
||||
int CODE_12303 = 12303;
|
||||
|
||||
/** API Key 字段自检未通过 */
|
||||
int CODE_12304 = 12304;
|
||||
|
||||
/** 未开启索引记录功能却调用了相关 API */
|
||||
int CODE_12305 = 12305;
|
||||
|
||||
/** API Key 不具有指定 Scope */
|
||||
int CODE_12311 = 12311;
|
||||
|
||||
/** API Key 不属于指定用户 */
|
||||
int CODE_12312 = 12312;
|
||||
|
||||
// ------------
|
||||
|
||||
/** 未实现具体的路由匹配策略 */
|
||||
int CODE_12401 = 12401;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
/**
|
||||
* 一个异常:代表 ApiKey 相关错误
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class ApiKeyException extends SaTokenException {
|
||||
|
||||
/**
|
||||
* 序列化版本号
|
||||
*/
|
||||
private static final long serialVersionUID = 6806129545290130114L;
|
||||
|
||||
/**
|
||||
* 一个异常:代表 ApiKey 相关错误
|
||||
* @param cause 根异常原因
|
||||
*/
|
||||
public ApiKeyException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个异常:代表 ApiKey 相关错误
|
||||
* @param message 异常描述
|
||||
*/
|
||||
public ApiKeyException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 具体引起异常的 ApiKey 值
|
||||
*/
|
||||
public String apiKey;
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public ApiKeyException setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果 flag==true,则抛出 message 异常
|
||||
* @param flag 标记
|
||||
* @param message 异常信息
|
||||
* @param code 异常细分码
|
||||
*/
|
||||
public static void throwBy(boolean flag, String message, int code) {
|
||||
if(flag) {
|
||||
throw new ApiKeyException(message).setCode(code);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
/**
|
||||
* 一个异常:代表 ApiKey Scope 相关错误
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class ApiKeyScopeException extends ApiKeyException {
|
||||
|
||||
/**
|
||||
* 序列化版本号
|
||||
*/
|
||||
private static final long serialVersionUID = 6806129545290130114L;
|
||||
|
||||
/**
|
||||
* 一个异常:代表 ApiKey Scope 相关错误
|
||||
* @param cause 根异常原因
|
||||
*/
|
||||
public ApiKeyScopeException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个异常:代表 ApiKey Scope 相关错误
|
||||
* @param message 异常描述
|
||||
*/
|
||||
public ApiKeyScopeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 具体引起异常的 ApiKey 值
|
||||
*/
|
||||
public String apiKey;
|
||||
|
||||
/**
|
||||
* 具体引起异常的 scope 值
|
||||
*/
|
||||
public String scope;
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public ApiKeyScopeException setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public ApiKeyScopeException setScope(String scope) {
|
||||
this.scope = scope;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果 flag==true,则抛出 message 异常
|
||||
* @param flag 标记
|
||||
* @param message 异常信息
|
||||
* @param code 异常细分码
|
||||
*/
|
||||
public static void throwBy(boolean flag, String message, int code) {
|
||||
if(flag) {
|
||||
throw new ApiKeyScopeException(message).setCode(code);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
/**
|
||||
* 一个异常:代表防火墙检验未通过
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class FirewallCheckException extends SaTokenException {
|
||||
|
||||
/**
|
||||
* 序列化版本号
|
||||
*/
|
||||
private static final long serialVersionUID = 8243974276159004739L;
|
||||
|
||||
public FirewallCheckException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public FirewallCheckException(Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public FirewallCheckException(String message, Throwable e) {
|
||||
super(message, e);
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -21,7 +21,7 @@ package cn.dev33.satoken.exception;
|
||||
* @author click33
|
||||
* @since 1.37.0
|
||||
*/
|
||||
public class RequestPathInvalidException extends SaTokenException {
|
||||
public class RequestPathInvalidException extends FirewallCheckException {
|
||||
|
||||
/**
|
||||
* 序列化版本号
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
/**
|
||||
* 一个异常:代表插件安装过程中出现异常
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.28.0
|
||||
*/
|
||||
public class SaTokenPluginException extends SaTokenException {
|
||||
|
||||
/**
|
||||
* 序列化版本号
|
||||
*/
|
||||
private static final long serialVersionUID = 6806129545290130131L;
|
||||
|
||||
/**
|
||||
* 一个异常:代表插件安装过程中出现异常
|
||||
* @param message 异常描述
|
||||
*/
|
||||
public SaTokenPluginException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个异常:代表插件安装过程中出现异常
|
||||
*
|
||||
* @param cause 异常对象
|
||||
*/
|
||||
public SaTokenPluginException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个异常:代表插件安装过程中出现异常
|
||||
*
|
||||
* @param message 异常描述
|
||||
* @param cause 异常对象
|
||||
*/
|
||||
public SaTokenPluginException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
/**
|
||||
* 一个异常:代表 TOTP 校验未通过
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class TotpAuthException extends SaTokenException {
|
||||
|
||||
/**
|
||||
* 序列化版本号
|
||||
*/
|
||||
private static final long serialVersionUID = 6806129545290130144L;
|
||||
|
||||
/** 异常提示语 */
|
||||
public static final String BE_MESSAGE = "totp check fail";
|
||||
|
||||
/**
|
||||
* 一个异常:代表会话未通过 Totp 校验
|
||||
*/
|
||||
public TotpAuthException() {
|
||||
super(BE_MESSAGE);
|
||||
}
|
||||
|
||||
}
|
||||
+8
-8
@@ -13,21 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.context.second;
|
||||
package cn.dev33.satoken.fun;
|
||||
|
||||
/**
|
||||
* Sa-Token 二级Context - 创建器
|
||||
*
|
||||
* 无形参、有返回值(泛型)的函数式接口,方便开发者进行 lambda 表达式风格调用
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.28.0
|
||||
* @since 1.42.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SaTokenSecondContextCreator {
|
||||
public interface SaRetGenericFunction<T> {
|
||||
|
||||
/**
|
||||
* 创建一个二级 Context 处理器
|
||||
* @return /
|
||||
* 执行的方法
|
||||
* @return 返回值
|
||||
*/
|
||||
SaTokenSecondContext create();
|
||||
T run();
|
||||
|
||||
}
|
||||
+12
-10
@@ -13,20 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.context.second;
|
||||
|
||||
import cn.dev33.satoken.context.SaTokenContext;
|
||||
package cn.dev33.satoken.fun;
|
||||
|
||||
/**
|
||||
* Sa-Token 二级Context - 基础接口
|
||||
*
|
||||
* <p> (利用继承机制实现区别 [ 一级Context ] 与 [ 二级Context ] 的目的)
|
||||
* 双形参、无返回值的函数式接口,方便开发者进行 lambda 表达式风格调用
|
||||
*
|
||||
* @see SaTokenContext SaTokenContext 上下文处理器
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.28.0
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public interface SaTokenSecondContext extends SaTokenContext {
|
||||
@FunctionalInterface
|
||||
public interface SaTwoParamFunction<T, T2> {
|
||||
|
||||
/**
|
||||
* 执行的方法
|
||||
* @param r 传入的参数
|
||||
* @param r2 传入的参数 2
|
||||
*/
|
||||
void run(T r, T2 r2);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.fun.hooks;
|
||||
|
||||
import cn.dev33.satoken.plugin.SaTokenPlugin;
|
||||
|
||||
/**
|
||||
* SaTokenPlugin 钩子函数
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SaTokenPluginHookFunction<T extends SaTokenPlugin> {
|
||||
|
||||
/**
|
||||
* 执行的方法
|
||||
* @param plugin 插件实例
|
||||
*/
|
||||
void execute(SaTokenPlugin plugin);
|
||||
}
|
||||
+12
-16
@@ -13,25 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.dao;
|
||||
package cn.dev33.satoken.fun.strategy;
|
||||
|
||||
import org.noear.redisx.RedisClient;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* SaTokenDao 的 redis 适配
|
||||
* 函数式接口:自定义自动续期条件
|
||||
*
|
||||
* @author noear
|
||||
* @since 1.34.0
|
||||
* <p> 参数:StpLogic 实例 </p>
|
||||
* <p> 返回:Boolean 是否续期 </p>
|
||||
*
|
||||
* @author fangzhengjin
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class SaTokenDaoOfRedis extends SaTokenDaoOfRedisBase64 {
|
||||
|
||||
public SaTokenDaoOfRedis(Properties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public SaTokenDaoOfRedis(RedisClient redisClient) {
|
||||
super(redisClient);
|
||||
}
|
||||
}
|
||||
@FunctionalInterface
|
||||
public interface SaAutoRenewFunction extends Function<StpLogic, Boolean> {
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.fun.strategy;
|
||||
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
|
||||
/**
|
||||
* CORS 跨域策略处理函数
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SaCorsHandleFunction {
|
||||
|
||||
/**
|
||||
* CORS 策略处理函数
|
||||
*
|
||||
* @param req 请求包装对象
|
||||
* @param res 响应包装对象
|
||||
* @param sto 数据读写对象
|
||||
*/
|
||||
void execute(
|
||||
SaRequest req,
|
||||
SaResponse res,
|
||||
SaStorage sto
|
||||
);
|
||||
|
||||
}
|
||||
+10
-9
@@ -15,25 +15,26 @@
|
||||
*/
|
||||
package cn.dev33.satoken.fun.strategy;
|
||||
|
||||
import cn.dev33.satoken.exception.RequestPathInvalidException;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.exception.FirewallCheckException;
|
||||
|
||||
/**
|
||||
* 函数式接口:校验请求 path 的算法
|
||||
*
|
||||
* <p> 如果属于无效请求 path,则抛出异常 RequestPathInvalidException </p>
|
||||
* 函数式接口:当防火墙校验不通过时执行的函数
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.37.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SaCheckRequestPathFunction {
|
||||
public interface SaFirewallCheckFailHandleFunction {
|
||||
|
||||
/**
|
||||
* 执行函数
|
||||
* @param path 请求 path
|
||||
* @param extArg1 扩展参数1
|
||||
* @param extArg2 扩展参数2
|
||||
* @param e 防火墙校验异常
|
||||
* @param req 请求对象
|
||||
* @param res 响应对象
|
||||
* @param extArg 预留扩展参数
|
||||
*/
|
||||
void run(String path, Object extArg1, Object extArg2);
|
||||
void run(FirewallCheckException e, SaRequest req, SaResponse res, Object extArg);
|
||||
|
||||
}
|
||||
+9
-7
@@ -15,23 +15,25 @@
|
||||
*/
|
||||
package cn.dev33.satoken.fun.strategy;
|
||||
|
||||
import cn.dev33.satoken.exception.RequestPathInvalidException;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
|
||||
/**
|
||||
* 函数式接口:当请求 path 校验不通过时处理方案的算法
|
||||
* 函数式接口:防火墙校验函数
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.37.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SaRequestPathInvalidHandleFunction {
|
||||
public interface SaFirewallCheckFunction {
|
||||
|
||||
/**
|
||||
* 执行函数
|
||||
* @param e 请求 path 无效的异常对象
|
||||
* @param extArg1 扩展参数1
|
||||
* @param extArg2 扩展参数2
|
||||
*
|
||||
* @param req 请求对象
|
||||
* @param res 响应对象
|
||||
* @param extArg 预留扩展参数
|
||||
*/
|
||||
void run(RequestPathInvalidException e, Object extArg1, Object extArg2);
|
||||
void execute(SaRequest req, SaResponse res, Object extArg);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.fun.strategy;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* 函数式接口:路由匹配策略
|
||||
*
|
||||
* <p> 参数:pattern, path </p>
|
||||
* <p> 返回:是否匹配 </p>
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SaRouteMatchFunction extends BiFunction<String, String, Boolean> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.httpauth.basic;
|
||||
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token Http Basic 账号
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class SaHttpBasicAccount {
|
||||
|
||||
/**
|
||||
* 账号
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param username 账号
|
||||
* @param password 密码
|
||||
*/
|
||||
public SaHttpBasicAccount(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param usernameAndPassword 账号和密码,冒号隔开
|
||||
*/
|
||||
public SaHttpBasicAccount(String usernameAndPassword) {
|
||||
if(SaFoxUtil.isEmpty(usernameAndPassword)) {
|
||||
throw new SaTokenException("UsernameAndPassword 不能为空");
|
||||
}
|
||||
String[] arr = usernameAndPassword.split(":");
|
||||
if(arr.length != 2) {
|
||||
throw new SaTokenException("UsernameAndPassword 格式错误,正确格式为:username:password");
|
||||
}
|
||||
this.username = arr[0];
|
||||
this.password = arr[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 账号
|
||||
*
|
||||
* @return username 账号
|
||||
*/
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 账号
|
||||
*
|
||||
* @param username 账号
|
||||
*/
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 密码
|
||||
*
|
||||
* @return password 密码
|
||||
*/
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 密码
|
||||
*
|
||||
* @param password 密码
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaHttpBasicAccount{" +
|
||||
"username='" + username + '\'' +
|
||||
", password='" + password + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
+14
-2
@@ -45,7 +45,7 @@ public class SaHttpBasicTemplate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器提交的 Basic 参数 (裁剪掉前缀并解码)
|
||||
* 获取浏览器提交的 Http Basic 参数 (裁剪掉前缀并解码)
|
||||
* @return 值
|
||||
*/
|
||||
public String getAuthorizationValue() {
|
||||
@@ -61,7 +61,19 @@ public class SaHttpBasicTemplate {
|
||||
// 裁剪前缀并解码
|
||||
return SaBase64Util.decode(authorization.substring(6));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 Http Basic 账号密码对象
|
||||
* @return /
|
||||
*/
|
||||
public SaHttpBasicAccount getHttpBasicAccount() {
|
||||
String authorizationValue = getAuthorizationValue();
|
||||
if(authorizationValue == null) {
|
||||
return null;
|
||||
}
|
||||
return new SaHttpBasicAccount(authorizationValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(使用全局配置的账号密码),校验不通过则抛出异常
|
||||
*/
|
||||
|
||||
@@ -32,13 +32,21 @@ public class SaHttpBasicUtil {
|
||||
public static SaHttpBasicTemplate saHttpBasicTemplate = new SaHttpBasicTemplate();
|
||||
|
||||
/**
|
||||
* 获取浏览器提交的 Basic 参数 (裁剪掉前缀并解码)
|
||||
* 获取浏览器提交的 Http Basic 参数 (裁剪掉前缀并解码)
|
||||
* @return 值
|
||||
*/
|
||||
public static String getAuthorizationValue() {
|
||||
return saHttpBasicTemplate.getAuthorizationValue();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 Http Basic 账号密码对象
|
||||
* @return /
|
||||
*/
|
||||
public static SaHttpBasicAccount getHttpBasicAccount() {
|
||||
return saHttpBasicTemplate.getHttpBasicAccount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(使用全局配置的账号密码),校验不通过则抛出异常
|
||||
*/
|
||||
|
||||
@@ -26,18 +26,41 @@ import java.util.Map;
|
||||
public interface SaJsonTemplate {
|
||||
|
||||
/**
|
||||
* 将任意对象序列化为 json 字符串
|
||||
* 序列化:对象 -> json 字符串
|
||||
*
|
||||
* @param obj 对象
|
||||
* @return 转换后的 json 字符串
|
||||
* @param obj /
|
||||
* @return /
|
||||
*/
|
||||
String toJsonString(Object obj);
|
||||
String objectToJson(Object obj);
|
||||
|
||||
/**
|
||||
* 解析 json 字符串为 map 对象
|
||||
* @param jsonStr json 字符串
|
||||
* @return map 对象
|
||||
* 反序列化:json 字符串 → 对象
|
||||
*
|
||||
* @param jsonStr /
|
||||
* @param type /
|
||||
* @return /
|
||||
* @param <T> /
|
||||
*/
|
||||
Map<String, Object> parseJsonToMap(String jsonStr);
|
||||
<T>T jsonToObject(String jsonStr, Class<T> type);
|
||||
|
||||
/**
|
||||
* 反序列化:json 字符串 → 对象 (自动判断类型)
|
||||
*
|
||||
* @param jsonStr /
|
||||
* @return /
|
||||
*/
|
||||
default Object jsonToObject(String jsonStr) {
|
||||
return jsonToObject(jsonStr, Object.class);
|
||||
};
|
||||
|
||||
/**
|
||||
* 反序列化:json 字符串 → Map
|
||||
*
|
||||
* @param jsonStr /
|
||||
* @return /
|
||||
*/
|
||||
default Map<String, Object> jsonToMap(String jsonStr) {
|
||||
return jsonToObject(jsonStr, Map.class);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
package cn.dev33.satoken.json;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.NotImplException;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JSON 转换器,默认实现类
|
||||
*
|
||||
@@ -33,12 +33,22 @@ public class SaJsonTemplateDefaultImpl implements SaJsonTemplate {
|
||||
public static final String ERROR_MESSAGE = "未实现具体的 json 转换器";
|
||||
|
||||
@Override
|
||||
public String toJsonString(Object obj) {
|
||||
public String objectToJson(Object obj) {
|
||||
throw new NotImplException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10003);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> parseJsonToMap(String jsonStr) {
|
||||
public Object jsonToObject(String jsonStr) {
|
||||
throw new NotImplException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10003);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T jsonToObject(String jsonStr, Class<T> type) {
|
||||
throw new NotImplException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10003);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> jsonToMap(String jsonStr) {
|
||||
throw new NotImplException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10003);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
|
||||
/**
|
||||
@@ -149,11 +149,11 @@ public class SaTokenEventCenter {
|
||||
* @param loginType 账号类别
|
||||
* @param loginId 账号id
|
||||
* @param tokenValue 本次登录产生的 token 值
|
||||
* @param loginModel 登录参数
|
||||
* @param loginParameter 登录参数
|
||||
*/
|
||||
public static void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
|
||||
public static void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
|
||||
for (SaTokenListener listener : listenerList) {
|
||||
listener.doLogin(loginType, loginId, tokenValue, loginModel);
|
||||
listener.doLogin(loginType, loginId, tokenValue, loginParameter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ package cn.dev33.satoken.listener;
|
||||
|
||||
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
|
||||
/**
|
||||
@@ -35,9 +35,9 @@ public interface SaTokenListener {
|
||||
* @param loginType 账号类别
|
||||
* @param loginId 账号id
|
||||
* @param tokenValue 本次登录产生的 token 值
|
||||
* @param loginModel 登录参数
|
||||
* @param loginParameter 登录参数
|
||||
*/
|
||||
void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel);
|
||||
void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter);
|
||||
|
||||
/**
|
||||
* 每次注销时触发
|
||||
|
||||
@@ -17,7 +17,7 @@ package cn.dev33.satoken.listener;
|
||||
|
||||
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
@@ -35,7 +35,7 @@ public class SaTokenListenerForLog implements SaTokenListener {
|
||||
* 每次登录时触发
|
||||
*/
|
||||
@Override
|
||||
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
|
||||
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
|
||||
log.info("账号 {} 登录成功 (loginType={}), 会话凭证 token={}", loginId, loginType, tokenValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package cn.dev33.satoken.listener;
|
||||
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
|
||||
/**
|
||||
* Sa-Token 侦听器,默认空实现
|
||||
@@ -28,7 +28,7 @@ import cn.dev33.satoken.stp.SaLoginModel;
|
||||
public class SaTokenListenerForSimple implements SaTokenListener {
|
||||
|
||||
@Override
|
||||
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
|
||||
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package cn.dev33.satoken.model.wrapperInfo;
|
||||
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
|
||||
/**
|
||||
* 返回值包装类:描述一个账号是否已被封禁等信息
|
||||
*
|
||||
@@ -66,7 +68,7 @@ public class SaDisableWrapperInfo {
|
||||
* @return /
|
||||
*/
|
||||
public static SaDisableWrapperInfo createNotDisabled() {
|
||||
return new SaDisableWrapperInfo(false, 0, 0);
|
||||
return new SaDisableWrapperInfo(false, 0, SaTokenConsts.NOT_DISABLE_LEVEL);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +77,7 @@ public class SaDisableWrapperInfo {
|
||||
* @return /
|
||||
*/
|
||||
public static SaDisableWrapperInfo createNotDisabled(long cacheTime) {
|
||||
return new SaDisableWrapperInfo(false, cacheTime, 0);
|
||||
return new SaDisableWrapperInfo(false, cacheTime, SaTokenConsts.NOT_DISABLE_LEVEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.plugin;
|
||||
|
||||
/**
|
||||
* Sa-Token 插件总接口
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public interface SaTokenPlugin {
|
||||
|
||||
/**
|
||||
* 安装插件
|
||||
*/
|
||||
void install();
|
||||
|
||||
/**
|
||||
* 卸载插件
|
||||
*/
|
||||
default void destroy(){
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
* 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.plugin;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.exception.SaTokenPluginException;
|
||||
import cn.dev33.satoken.fun.hooks.SaTokenPluginHookFunction;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Sa-Token 插件管理器,管理所有插件的加载与卸载
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class SaTokenPluginHolder {
|
||||
|
||||
/**
|
||||
* 默认实例,非单例模式,可替换
|
||||
*/
|
||||
public static SaTokenPluginHolder instance = new SaTokenPluginHolder();
|
||||
|
||||
|
||||
// ------------------- 插件管理器初始化相关 -------------------
|
||||
|
||||
/**
|
||||
* 是否已经加载过插件
|
||||
*/
|
||||
public boolean isLoader = false;
|
||||
|
||||
/**
|
||||
* SPI 文件所在目录名称
|
||||
*/
|
||||
public String spiDir = "satoken";
|
||||
|
||||
/**
|
||||
* 初始化加载所有插件(多次调用只会执行一次)
|
||||
*/
|
||||
public synchronized void init() {
|
||||
if(isLoader) {
|
||||
return;
|
||||
}
|
||||
loaderPlugins();
|
||||
isLoader = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 SPI 机制加载所有插件
|
||||
* <p>
|
||||
* 加载所有 jar 下 /META-INF/satoken/ 目录下 cn.dev33.satoken.plugin.SaTokenPlugin 文件指定的实现类
|
||||
* </p>
|
||||
*/
|
||||
public synchronized void loaderPlugins() {
|
||||
SaManager.getLog().info("SPI plugin loading start ...");
|
||||
List<SaTokenPlugin> plugins = _loaderPluginsBySpi(SaTokenPlugin.class, spiDir);
|
||||
for (SaTokenPlugin plugin : plugins) {
|
||||
installPlugin(plugin);
|
||||
}
|
||||
SaManager.getLog().info("SPI plugin loading end ...");
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义 SPI 读取策略 (无状态函数)
|
||||
* @param serviceInterface SPI 接口
|
||||
* @param dirName 目录名称
|
||||
* @return /
|
||||
* @param <T> /
|
||||
*/
|
||||
protected <T> List<T> _loaderPluginsBySpi(Class<T> serviceInterface, String dirName) {
|
||||
String path = "META-INF/" + dirName + "/" + serviceInterface.getName();
|
||||
List<T> providers = new ArrayList<>();
|
||||
try {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
Enumeration<URL> resources = classLoader.getResources(path);
|
||||
while (resources.hasMoreElements()) {
|
||||
URL url = resources.nextElement();
|
||||
try (InputStream is = url.openStream()) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
// 忽略空行和注释行
|
||||
if (!line.isEmpty() && !line.startsWith("#")) {
|
||||
Class<?> clazz = Class.forName(line, true, classLoader);
|
||||
T instance = serviceInterface.cast(clazz.getDeclaredConstructor().newInstance());
|
||||
providers.add(instance);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenPluginException("SPI 插件加载失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenPluginException("SPI 插件加载失败: " + e.getMessage(), e);
|
||||
}
|
||||
return providers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------- 插件管理 -------------------
|
||||
|
||||
/**
|
||||
* 所有插件的集合
|
||||
*/
|
||||
private final List<SaTokenPlugin> pluginList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 获取插件集合副本 (拷贝插件集合,而非每个插件实例)
|
||||
* @return /
|
||||
*/
|
||||
public synchronized List<SaTokenPlugin> getPluginListCopy() {
|
||||
return new ArrayList<>(pluginList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否已经安装了指定插件
|
||||
*
|
||||
* @param pluginClass 插件类型
|
||||
* @return /
|
||||
*/
|
||||
public synchronized<T extends SaTokenPlugin> boolean isInstalledPlugin(Class<T> pluginClass) {
|
||||
for (SaTokenPlugin plugin : pluginList) {
|
||||
if (plugin.getClass().equals(pluginClass)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的插件
|
||||
* @param pluginClass /
|
||||
* @return /
|
||||
* @param <T> /
|
||||
*/
|
||||
public synchronized<T extends SaTokenPlugin> T getPlugin(Class<T> pluginClass) {
|
||||
for (SaTokenPlugin plugin : pluginList) {
|
||||
if (plugin.getClass().equals(pluginClass)) {
|
||||
return (T) plugin;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消费指定集合的钩子函数,返回消费的数量
|
||||
* @param pluginClass /
|
||||
* @param hooks /
|
||||
* @param <T> /
|
||||
*/
|
||||
protected synchronized <T extends SaTokenPlugin> int _consumeHooks(List<SaTokenPluginHookModel<? extends SaTokenPlugin>> hooks, Class<T> pluginClass) {
|
||||
int consumeCount = 0;
|
||||
for (int i = 0; i < hooks.size(); i++) {
|
||||
SaTokenPluginHookModel<? extends SaTokenPlugin> model = hooks.get(i);
|
||||
if(model.listenerClass.equals(pluginClass)) {
|
||||
model.executeFunction.execute(getPlugin(pluginClass));
|
||||
hooks.remove(i);
|
||||
i--;
|
||||
consumeCount++;
|
||||
}
|
||||
}
|
||||
return consumeCount;
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 插件 Install 与 Destroy -------------------
|
||||
|
||||
/**
|
||||
* 安装指定插件
|
||||
* @param plugin /
|
||||
*/
|
||||
public synchronized SaTokenPluginHolder installPlugin(SaTokenPlugin plugin) {
|
||||
|
||||
// 插件为空,拒绝安装
|
||||
if (plugin == null) {
|
||||
throw new SaTokenPluginException("插件不可为空");
|
||||
}
|
||||
|
||||
// 插件已经被安装过了,拒绝再次安装
|
||||
if (isInstalledPlugin(plugin.getClass())) {
|
||||
throw new SaTokenPluginException("插件 [ " + plugin.getClass().getCanonicalName() + " ] 已安装,不可重复安装");
|
||||
}
|
||||
|
||||
// 执行该插件的 install 前置钩子
|
||||
_consumeHooks(beforeInstallHooks, plugin.getClass());
|
||||
|
||||
// 插件安装
|
||||
int consumeCount = _consumeHooks(installHooks, plugin.getClass());
|
||||
if (consumeCount == 0) {
|
||||
plugin.install();
|
||||
}
|
||||
|
||||
// 执行该插件的 install 后置钩子
|
||||
_consumeHooks(afterInstallHooks, plugin.getClass());
|
||||
|
||||
// 添加到插件集合
|
||||
pluginList.add(plugin);
|
||||
|
||||
// 返回对象自身,支持连缀风格调用
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装指定插件,根据插件类型
|
||||
* @param pluginClass /
|
||||
*/
|
||||
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder installPlugin(Class<T> pluginClass) {
|
||||
try {
|
||||
T plugin = pluginClass.getDeclaredConstructor().newInstance();
|
||||
return installPlugin(plugin);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
||||
throw new SaTokenPluginException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载指定插件
|
||||
* @param plugin /
|
||||
*/
|
||||
public synchronized SaTokenPluginHolder destroyPlugin(SaTokenPlugin plugin) {
|
||||
|
||||
// 插件为空,拒绝卸载
|
||||
if (plugin == null) {
|
||||
throw new SaTokenPluginException("插件不可为空");
|
||||
}
|
||||
|
||||
// 插件未被安装,拒绝卸载
|
||||
if (!isInstalledPlugin(plugin.getClass())) {
|
||||
throw new SaTokenPluginException("插件 [ " + plugin.getClass().getCanonicalName() + " ] 未安装,无法卸载");
|
||||
}
|
||||
|
||||
// 执行该插件的 destroy 前置钩子
|
||||
_consumeHooks(beforeDestroyHooks, plugin.getClass());
|
||||
|
||||
// 插件卸载
|
||||
int consumeCount = _consumeHooks(destroyHooks, plugin.getClass());
|
||||
if (consumeCount == 0) {
|
||||
plugin.destroy();
|
||||
}
|
||||
|
||||
// 执行该插件的 destroy 后置钩子
|
||||
_consumeHooks(afterDestroyHooks, plugin.getClass());
|
||||
|
||||
// 返回对象自身,支持连缀风格调用
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载指定插件,根据插件类型
|
||||
* @param pluginClass /
|
||||
*/
|
||||
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder destroyPlugin(Class<T> pluginClass) {
|
||||
return destroyPlugin(getPlugin(pluginClass));
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 插件 Install 钩子 -------------------
|
||||
|
||||
/**
|
||||
* 插件 [ Install 钩子 ] 集合
|
||||
*/
|
||||
private final List<SaTokenPluginHookModel<? extends SaTokenPlugin>> installHooks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 插件 [ Install 前置钩子 ] 集合
|
||||
*/
|
||||
private final List<SaTokenPluginHookModel<? extends SaTokenPlugin>> beforeInstallHooks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 插件 [ Install 后置钩子 ] 集合
|
||||
*/
|
||||
private final List<SaTokenPluginHookModel<? extends SaTokenPlugin>> afterInstallHooks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 注册指定插件的 [ Install 钩子 ],1、同插件支持多次注册。2、如果插件已经安装完毕,则抛出异常。3、注册 Install 钩子的插件默认安装行为将不再执行
|
||||
* @param listenerClass /
|
||||
* @param executeFunction /
|
||||
* @param <T> /
|
||||
*/
|
||||
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder onInstall(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
|
||||
// 如果指定的插件已经安装完毕,则不再允许注册前置钩子函数
|
||||
if(isInstalledPlugin(listenerClass)) {
|
||||
throw new SaTokenPluginException("插件 [ " + listenerClass.getCanonicalName() + " ] 已安装完毕,不允许再注册 Install 钩子函数");
|
||||
}
|
||||
|
||||
// 堆积到钩子函数集合
|
||||
installHooks.add(new SaTokenPluginHookModel<T>(listenerClass, executeFunction));
|
||||
|
||||
// 返回对象自身,支持连缀风格调用
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册指定插件的 [ Install 前置钩子 ],1、同插件支持多次注册。2、如果插件已经安装完毕,则抛出异常
|
||||
* @param listenerClass /
|
||||
* @param executeFunction /
|
||||
* @param <T> /
|
||||
*/
|
||||
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder onBeforeInstall(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
|
||||
// 如果指定的插件已经安装完毕,则不再允许注册前置钩子函数
|
||||
if(isInstalledPlugin(listenerClass)) {
|
||||
throw new SaTokenPluginException("插件 [ " + listenerClass.getCanonicalName() + " ] 已安装完毕,不允许再注册 Install 前置钩子函数");
|
||||
}
|
||||
|
||||
// 堆积到钩子函数集合
|
||||
beforeInstallHooks.add(new SaTokenPluginHookModel<T>(listenerClass, executeFunction));
|
||||
|
||||
// 返回对象自身,支持连缀风格调用
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册指定插件的 [ Install 后置钩子 ],1、同插件支持多次注册。2、如果插件已经安装完毕,则立即执行该钩子函数
|
||||
* @param listenerClass /
|
||||
* @param executeFunction /
|
||||
* @param <T> /
|
||||
*/
|
||||
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder onAfterInstall(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
|
||||
// 如果指定的插件已经安装完毕,则立即执行该钩子函数
|
||||
if(isInstalledPlugin(listenerClass)) {
|
||||
executeFunction.execute(getPlugin(listenerClass));
|
||||
return this;
|
||||
}
|
||||
|
||||
// 堆积到钩子函数集合
|
||||
afterInstallHooks.add(new SaTokenPluginHookModel<T>(listenerClass, executeFunction));
|
||||
|
||||
// 返回对象自身,支持连缀风格调用
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 插件 Destroy 钩子 -------------------
|
||||
|
||||
/**
|
||||
* 插件 [ Destroy 钩子 ] 集合
|
||||
*/
|
||||
private final List<SaTokenPluginHookModel<? extends SaTokenPlugin>> destroyHooks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 插件 [ Destroy 前置钩子 ] 集合
|
||||
*/
|
||||
private final List<SaTokenPluginHookModel<? extends SaTokenPlugin>> beforeDestroyHooks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 插件 [ Destroy 后置钩子 ] 集合
|
||||
*/
|
||||
private final List<SaTokenPluginHookModel<? extends SaTokenPlugin>> afterDestroyHooks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 注册指定插件的 [ Destroy 钩子 ],1、同插件支持多次注册。2、注册 Destroy 钩子的插件默认卸载行为将不再执行
|
||||
* @param listenerClass /
|
||||
* @param executeFunction /
|
||||
* @param <T> /
|
||||
*/
|
||||
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder onDestroy(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
|
||||
destroyHooks.add(new SaTokenPluginHookModel<T>(listenerClass, executeFunction));
|
||||
|
||||
// 返回对象自身,支持连缀风格调用
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册指定插件的 [ Destroy 前置钩子 ],同插件支持多次注册
|
||||
* @param listenerClass /
|
||||
* @param executeFunction /
|
||||
* @param <T> /
|
||||
*/
|
||||
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder onBeforeDestroy(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
|
||||
beforeDestroyHooks.add(new SaTokenPluginHookModel<T>(listenerClass, executeFunction));
|
||||
|
||||
// 返回对象自身,支持连缀风格调用
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册指定插件的 [ Destroy 后置钩子 ],同插件支持多次注册
|
||||
* @param listenerClass /
|
||||
* @param executeFunction /
|
||||
* @param <T> /
|
||||
*/
|
||||
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder onAfterDestroy(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
|
||||
afterDestroyHooks.add(new SaTokenPluginHookModel<T>(listenerClass, executeFunction));
|
||||
|
||||
// 返回对象自身,支持连缀风格调用
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
+19
-18
@@ -13,35 +13,36 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.solon.dao;
|
||||
package cn.dev33.satoken.plugin;
|
||||
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import cn.dev33.satoken.fun.hooks.SaTokenPluginHookFunction;
|
||||
|
||||
/**
|
||||
* Jackson定制版SaSession,忽略 timeout 等属性的序列化
|
||||
*
|
||||
* Sa-Token 插件 Hook Model
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.34.0
|
||||
* @since 1.41.0
|
||||
*/
|
||||
@JsonIgnoreProperties({"timeout"})
|
||||
public class SaSessionForJacksonCustomized extends SaSession {
|
||||
public class SaTokenPluginHookModel<T extends SaTokenPlugin> {
|
||||
|
||||
/**
|
||||
*
|
||||
* 监听插件类型
|
||||
*/
|
||||
private static final long serialVersionUID = -7600983549653130681L;
|
||||
|
||||
public SaSessionForJacksonCustomized() {
|
||||
super();
|
||||
}
|
||||
public Class<T> listenerClass;
|
||||
|
||||
/**
|
||||
* 构建一个Session对象
|
||||
* @param id Session的id
|
||||
* 执行的方法
|
||||
*/
|
||||
public SaSessionForJacksonCustomized(String id) {
|
||||
super(id);
|
||||
public SaTokenPluginHookFunction<T> executeFunction;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param listenerClass /
|
||||
* @param executeFunction /
|
||||
*/
|
||||
public SaTokenPluginHookModel(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
|
||||
this.listenerClass = listenerClass;
|
||||
this.executeFunction = executeFunction;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,15 +15,15 @@
|
||||
*/
|
||||
package cn.dev33.satoken.router;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.exception.BackResultException;
|
||||
import cn.dev33.satoken.exception.StopMatchException;
|
||||
import cn.dev33.satoken.fun.SaFunction;
|
||||
import cn.dev33.satoken.fun.SaParamFunction;
|
||||
import cn.dev33.satoken.fun.SaParamRetFunction;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 路由匹配操作工具类
|
||||
@@ -55,7 +55,7 @@ public class SaRouter {
|
||||
* @return 是否匹配成功
|
||||
*/
|
||||
public static boolean isMatch(String pattern, String path) {
|
||||
return SaManager.getSaTokenContextOrSecond().matchPath(pattern, path);
|
||||
return SaStrategy.instance.routeMatcher.apply(pattern, path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,6 +46,7 @@ import java.security.SecureRandom;
|
||||
* @author Damien Miller
|
||||
* @since 1.29.0
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("all")
|
||||
public class BCrypt {
|
||||
// BCrypt parameters
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.secure;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Sa-Token Base32 工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaBase32Util {
|
||||
|
||||
private static final String BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
private static final int[] BASE32_LOOKUP = new int[256];
|
||||
|
||||
static {
|
||||
// 初始化解码查找表
|
||||
for (int i = 0; i < BASE32_CHARS.length(); i++) {
|
||||
char c = BASE32_CHARS.charAt(i);
|
||||
BASE32_LOOKUP[c] = i;
|
||||
// 支持小写字母解码
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
BASE32_LOOKUP[Character.toLowerCase(c)] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base32 编码(byte[] 转 String)
|
||||
*/
|
||||
public static String encodeBytesToString(byte[] bytes) {
|
||||
if (bytes == null) return null;
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
int buffer = 0;
|
||||
int bufferSize = 0;
|
||||
|
||||
for (byte b : bytes) {
|
||||
buffer = (buffer << 8) | (b & 0xFF);
|
||||
bufferSize += 8;
|
||||
|
||||
while (bufferSize >= 5) {
|
||||
bufferSize -= 5;
|
||||
int index = (buffer >> bufferSize) & 0x1F;
|
||||
result.append(BASE32_CHARS.charAt(index));
|
||||
}
|
||||
}
|
||||
|
||||
// 处理剩余位
|
||||
if (bufferSize > 0) {
|
||||
int index = (buffer << (5 - bufferSize)) & 0x1F;
|
||||
result.append(BASE32_CHARS.charAt(index));
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Base32 解码(String 转 byte[])
|
||||
*/
|
||||
public static byte[] decodeStringToBytes(String text) {
|
||||
if (text == null) return null;
|
||||
|
||||
text = text.replaceAll("=", "").trim();
|
||||
if (text.isEmpty()) return new byte[0];
|
||||
|
||||
int buffer = 0;
|
||||
int bufferSize = 0;
|
||||
int byteCount = (text.length() * 5 + 7) / 8;
|
||||
byte[] bytes = new byte[byteCount];
|
||||
int byteIndex = 0;
|
||||
|
||||
for (char c : text.toCharArray()) {
|
||||
int value = BASE32_LOOKUP[c];
|
||||
if (value == 0 && c != 'A') continue; // 跳过非法字符
|
||||
|
||||
buffer = (buffer << 5) | value;
|
||||
bufferSize += 5;
|
||||
|
||||
if (bufferSize >= 8) {
|
||||
bufferSize -= 8;
|
||||
bytes[byteIndex++] = (byte) ((buffer >> bufferSize) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理最后一个字节
|
||||
if (bufferSize > 0) {
|
||||
bytes[byteIndex] = (byte) ((buffer << (8 - bufferSize)) & 0xFF);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base32 编码(String 转 String)
|
||||
*/
|
||||
public static String encode(String text) {
|
||||
if (text == null) return null;
|
||||
return encodeBytesToString(text.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base32 解码(String 转 String)
|
||||
*/
|
||||
public static String decode(String base32Text) {
|
||||
if (base32Text == null) return null;
|
||||
byte[] bytes = decodeStringToBytes(base32Text);
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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.secure.totp;
|
||||
|
||||
import cn.dev33.satoken.exception.TotpAuthException;
|
||||
import cn.dev33.satoken.secure.SaBase32Util;
|
||||
import cn.dev33.satoken.util.StrFormatter;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* TOTP 算法类,支持 生成/验证 动态一次性密码
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaTotpTemplate {
|
||||
|
||||
/**
|
||||
* 时间窗口步长(秒)
|
||||
*/
|
||||
public int timeStep = 30;
|
||||
|
||||
/**
|
||||
* 生成的验证码位数
|
||||
*/
|
||||
public int codeDigits = 6;
|
||||
|
||||
/**
|
||||
* 哈希算法(HmacSHA1、HmacSHA256等)
|
||||
*/
|
||||
public String hmacAlgorithm = "HmacSHA1";
|
||||
|
||||
/**
|
||||
* 密钥长度(字节,推荐16或32)
|
||||
*/
|
||||
public int secretKeyLength = 16;
|
||||
|
||||
/**
|
||||
* 构造函数 (使用默认参数)
|
||||
*/
|
||||
public SaTotpTemplate() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数 (使用自定义参数)
|
||||
*
|
||||
* @param timeStep 时间窗口步长(秒)
|
||||
* @param codeDigits 生成的验证码位数
|
||||
* @param hmacAlgorithm 哈希算法(HmacSHA1、HmacSHA256等)
|
||||
* @param secretKeyLength 密钥长度(字节,推荐16或32)
|
||||
*/
|
||||
public SaTotpTemplate(int timeStep, int codeDigits, String hmacAlgorithm, int secretKeyLength) {
|
||||
this.timeStep = timeStep;
|
||||
this.codeDigits = codeDigits;
|
||||
this.hmacAlgorithm = hmacAlgorithm;
|
||||
this.secretKeyLength = secretKeyLength;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成随机密钥(Base32编码)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public String generateSecretKey() {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] bytes = new byte[secretKeyLength];
|
||||
random.nextBytes(bytes);
|
||||
return SaBase32Util.encodeBytesToString(bytes).replace("=", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成当前时间的 TOTP 验证码
|
||||
*
|
||||
* @param secretKey Base32 编码的密钥
|
||||
* @return /
|
||||
*/
|
||||
public String _generateTOTP(String secretKey) {
|
||||
return _generateTOTP(secretKey, Instant.now().getEpochSecond());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户输入的 TOTP 是否有效
|
||||
*
|
||||
* @param secretKey Base32编码的密钥
|
||||
* @param code 用户输入的验证码
|
||||
* @param timeWindowOffset 允许的时间窗口偏移量(如1表示允许前后各1个时间窗口)
|
||||
* @return /
|
||||
*/
|
||||
public boolean validateTOTP(String secretKey, String code, int timeWindowOffset) {
|
||||
long currentWindow = Instant.now().getEpochSecond() / timeStep;
|
||||
for (int i = -timeWindowOffset; i <= timeWindowOffset; i++) {
|
||||
String calculatedCode = _generateTOTP(secretKey, (currentWindow + i) * timeStep);
|
||||
if (calculatedCode.equals(code)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验用户输入的TOTP是否有效,如果无效则抛出异常
|
||||
*
|
||||
* @param secretKey Base32编码的密钥
|
||||
* @param code 用户输入的验证码
|
||||
* @param timeWindowOffset 允许的时间窗口偏移量(如1表示允许前后各1个时间窗口)
|
||||
*/
|
||||
public void checkTOTP(String secretKey, String code, int timeWindowOffset) {
|
||||
if (!validateTOTP(secretKey, code, timeWindowOffset)) {
|
||||
throw new TotpAuthException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成谷歌认证器的扫码字符串 (形如:otpauth://totp/{account}?secret={secretKey})
|
||||
*
|
||||
* @param account 账户名
|
||||
* @return /
|
||||
*/
|
||||
public String generateGoogleSecretKey(String account) {
|
||||
return generateGoogleSecretKey(account, generateSecretKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成谷歌认证器的扫码字符串 (形如:otpauth://totp/{account}?secret={secretKey})
|
||||
*
|
||||
* @param account 账户名
|
||||
* @param secretKey TOTP 秘钥
|
||||
* @return /
|
||||
*/
|
||||
public String generateGoogleSecretKey(String account, String secretKey) {
|
||||
return StrFormatter.format("otpauth://totp/{}?secret={}", account, secretKey);
|
||||
}
|
||||
|
||||
protected String _generateTOTP(String secretKey, long time) {
|
||||
// Base32解码密钥
|
||||
byte[] keyBytes = SaBase32Util.decodeStringToBytes(secretKey);
|
||||
byte[] counterBytes = ByteBuffer.allocate(8).putLong(time / timeStep).array();
|
||||
|
||||
try {
|
||||
// 计算HMAC哈希
|
||||
Mac hmac = Mac.getInstance(hmacAlgorithm);
|
||||
hmac.init(new SecretKeySpec(keyBytes, hmacAlgorithm));
|
||||
byte[] hash = hmac.doFinal(counterBytes);
|
||||
|
||||
// 动态截断(RFC 6238)
|
||||
int offset = hash[hash.length - 1] & 0xF;
|
||||
int binary = ((hash[offset] & 0x7F) << 24)
|
||||
| ((hash[offset + 1] & 0xFF) << 16)
|
||||
| ((hash[offset + 2] & 0xFF) << 8)
|
||||
| (hash[offset + 3] & 0xFF);
|
||||
|
||||
// 生成指定位数的验证码
|
||||
int otp = binary % (int) Math.pow(10, codeDigits);
|
||||
return String.format("%0" + codeDigits + "d", otp);
|
||||
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException("TOTP生成失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.secure.totp;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
|
||||
/**
|
||||
* TOTP 工具类,支持 生成/验证 动态一次性密码
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaTotpUtil {
|
||||
|
||||
/**
|
||||
* 生成随机密钥(Base32编码)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public static String generateSecretKey() {
|
||||
return SaManager.getSaTotpTemplate().generateSecretKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成当前时间的TOTP验证码
|
||||
*
|
||||
* @param secretKey Base32编码的密钥
|
||||
* @return /
|
||||
*/
|
||||
public static String generateTOTP(String secretKey) {
|
||||
return SaManager.getSaTotpTemplate()._generateTOTP(secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户输入的TOTP是否有效
|
||||
*
|
||||
* @param secretKey Base32编码的密钥
|
||||
* @param code 用户输入的验证码
|
||||
* @param timeWindowOffset 允许的时间窗口偏移量(如1表示允许前后各1个时间窗口)
|
||||
* @return /
|
||||
*/
|
||||
public static boolean validateTOTP(String secretKey, String code, int timeWindowOffset) {
|
||||
return SaManager.getSaTotpTemplate().validateTOTP(secretKey, code, timeWindowOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验用户输入的TOTP是否有效,如果无效则抛出异常
|
||||
*
|
||||
* @param secretKey Base32编码的密钥
|
||||
* @param code 用户输入的验证码
|
||||
* @param timeWindowOffset 允许的时间窗口偏移量(如1表示允许前后各1个时间窗口)
|
||||
*/
|
||||
public static void checkTOTP(String secretKey, String code, int timeWindowOffset) {
|
||||
SaManager.getSaTotpTemplate().checkTOTP(secretKey, code, timeWindowOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成谷歌认证器的扫码字符串 (形如:otpauth://totp/{account}?secret={secretKey})
|
||||
*
|
||||
* @param account 账户名
|
||||
* @return /
|
||||
*/
|
||||
public static String generateGoogleSecretKey(String account) {
|
||||
return SaManager.getSaTotpTemplate().generateGoogleSecretKey(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成谷歌认证器的扫码字符串 (形如:otpauth://totp/{account}?secret={secretKey})
|
||||
*
|
||||
* @param account 账户名
|
||||
* @param secretKey TOTP 秘钥
|
||||
* @return /
|
||||
*/
|
||||
public static String generateGoogleSecretKey(String account, String secretKey) {
|
||||
return SaManager.getSaTotpTemplate().generateGoogleSecretKey(account, secretKey);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.serializer;
|
||||
|
||||
/**
|
||||
* 序列化器
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public interface SaSerializerTemplate {
|
||||
|
||||
/**
|
||||
* 序列化:对象 -> 字符串
|
||||
*
|
||||
* @param obj /
|
||||
* @return /
|
||||
*/
|
||||
String objectToString(Object obj);
|
||||
|
||||
/**
|
||||
* 反序列化:字符串 → 对象
|
||||
*
|
||||
* @param str /
|
||||
* @return /
|
||||
*/
|
||||
Object stringToObject(String str);
|
||||
|
||||
/**
|
||||
* 反序列化:字符串 → 对象 (指定类型)
|
||||
* <p>
|
||||
* 此方法目前仅为 json 序列化实现类 在 反序列化对象 传递类型信息
|
||||
* </p>
|
||||
*
|
||||
* @param str /
|
||||
* @return /
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T> T stringToObject(String str, Class<T> type) {
|
||||
return (T)stringToObject(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 序列化:对象 -> 字节数组
|
||||
*
|
||||
* @param obj /
|
||||
* @return /
|
||||
*/
|
||||
byte[] objectToBytes(Object obj);
|
||||
|
||||
/**
|
||||
* 反序列化:字节数组 → 对象
|
||||
*
|
||||
* @param bytes /
|
||||
* @return /
|
||||
*/
|
||||
Object bytesToObject(byte[] bytes);
|
||||
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.serializer.impl;
|
||||
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.serializer.SaSerializerTemplate;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* 序列化器次级实现: jdk序列化
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public interface SaSerializerTemplateForJdk extends SaSerializerTemplate {
|
||||
|
||||
@Override
|
||||
default String objectToString(Object obj) {
|
||||
byte[] bytes = objectToBytes(obj);
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
return bytesToString(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
default Object stringToObject(String str) {
|
||||
if(str == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] bytes = stringToBytes(str);
|
||||
return bytesToObject(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
default byte[] objectToBytes(Object obj) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
try (
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(baos)
|
||||
) {
|
||||
oos.writeObject(obj);
|
||||
return baos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default Object bytesToObject(byte[] bytes) {
|
||||
if(bytes == null) {
|
||||
return null;
|
||||
}
|
||||
try (
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||
ObjectInputStream ois = new ObjectInputStream(bais)
|
||||
) {
|
||||
return ois.readObject();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* byte[] 转换为 String
|
||||
* @param bytes /
|
||||
* @return /
|
||||
*/
|
||||
String bytesToString(byte[] bytes);
|
||||
|
||||
/**
|
||||
* String 转换为 byte[]
|
||||
* @param str /
|
||||
* @return /
|
||||
*/
|
||||
byte[] stringToBytes(String str);
|
||||
|
||||
}
|
||||
+13
-9
@@ -13,22 +13,26 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.context.dubbo;
|
||||
package cn.dev33.satoken.serializer.impl;
|
||||
|
||||
import cn.dev33.satoken.context.second.SaTokenSecondContext;
|
||||
import cn.dev33.satoken.context.second.SaTokenSecondContextCreator;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Sa-Token 二级上下文 - 创建器 [ Dubbo版 ]
|
||||
*
|
||||
* 序列化器: jdk序列化、Base64 编码 (体积+33%)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.34.0
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class SaTokenSecondContextCreatorForDubbo implements SaTokenSecondContextCreator {
|
||||
public class SaSerializerTemplateForJdkUseBase64 implements SaSerializerTemplateForJdk {
|
||||
|
||||
@Override
|
||||
public SaTokenSecondContext create() {
|
||||
return new SaTokenSecondContextForDubbo();
|
||||
public String bytesToString(byte[] bytes) {
|
||||
return Base64.getEncoder().encodeToString(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] stringToBytes(String str) {
|
||||
return Base64.getDecoder().decode(str);
|
||||
}
|
||||
|
||||
}
|
||||
+13
-10
@@ -13,23 +13,26 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.context.dubbo3;
|
||||
package cn.dev33.satoken.serializer.impl;
|
||||
|
||||
|
||||
import cn.dev33.satoken.context.second.SaTokenSecondContext;
|
||||
import cn.dev33.satoken.context.second.SaTokenSecondContextCreator;
|
||||
import cn.dev33.satoken.util.SaHexUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token 二级上下文 - 创建器 [Dubbo3版]
|
||||
*
|
||||
* 序列化器: jdk序列化、16 进制编码 (体积+100%)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.34.0
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class SaTokenSecondContextCreatorForDubbo3 implements SaTokenSecondContextCreator {
|
||||
public class SaSerializerTemplateForJdkUseHex implements SaSerializerTemplateForJdk {
|
||||
|
||||
@Override
|
||||
public SaTokenSecondContext create() {
|
||||
return new SaTokenSecondContextForDubbo3();
|
||||
public String bytesToString(byte[] bytes) {
|
||||
return SaHexUtil.bytesToHex(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] stringToBytes(String str) {
|
||||
return SaHexUtil.hexToBytes(str);
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.serializer.impl;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 序列化器: jdk序列化、ISO-8859-1 编码 (体积无变化)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class SaSerializerTemplateForJdkUseISO_8859_1 implements SaSerializerTemplateForJdk {
|
||||
|
||||
@Override
|
||||
public String bytesToString(byte[] bytes) {
|
||||
return new String(bytes, StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] stringToBytes(String str) {
|
||||
return str.getBytes(StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.serializer.impl;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.exception.ApiDisabledException;
|
||||
import cn.dev33.satoken.serializer.SaSerializerTemplate;
|
||||
|
||||
/**
|
||||
* 序列化器: 使用 json 转换器
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class SaSerializerTemplateForJson implements SaSerializerTemplate {
|
||||
|
||||
@Override
|
||||
public String objectToString(Object obj) {
|
||||
return SaManager.getSaJsonTemplate().objectToJson(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object stringToObject(String str) {
|
||||
return SaManager.getSaJsonTemplate().jsonToObject(str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T>T stringToObject(String str, Class<T> type) {
|
||||
return SaManager.getSaJsonTemplate().jsonToObject(str, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] objectToBytes(Object obj) {
|
||||
throw new ApiDisabledException("json 序列化器不支持 Object -> byte[]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object bytesToObject(byte[] bytes) {
|
||||
throw new ApiDisabledException("json 序列化器不支持 byte[] -> Object");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package cn.dev33.satoken.session;
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.application.SaSetValueInterface;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.fun.SaTwoParamFunction;
|
||||
import cn.dev33.satoken.listener.SaTokenEventCenter;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
@@ -88,6 +89,11 @@ public class SaSession implements SaSetValueInterface, Serializable {
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 当前账号历史总计登录设备数量 (当此 SaSession 属于 Account-Session 时,此值有效)
|
||||
*/
|
||||
private int historyTerminalCount;
|
||||
|
||||
/**
|
||||
* 此 SaSession 的创建时间(13位时间戳)
|
||||
*/
|
||||
@@ -234,137 +240,167 @@ public class SaSession implements SaSetValueInterface, Serializable {
|
||||
}
|
||||
|
||||
|
||||
// ----------------------- TokenSign 相关
|
||||
// ----------------------- SaTerminalInfo 相关
|
||||
|
||||
/**
|
||||
* 此 Session 绑定的 Token 签名列表
|
||||
* 登录终端信息列表
|
||||
*/
|
||||
private List<TokenSign> tokenSignList = new Vector<>();
|
||||
private List<SaTerminalInfo> terminalList = new Vector<>();
|
||||
|
||||
/**
|
||||
* 写入此 Session 绑定的 Token 签名列表
|
||||
* @param tokenSignList Token 签名列表
|
||||
* 写入登录终端信息列表
|
||||
* @param terminalList /
|
||||
*/
|
||||
public void setTokenSignList(List<TokenSign> tokenSignList) {
|
||||
this.tokenSignList = tokenSignList;
|
||||
public void setTerminalList(List<SaTerminalInfo> terminalList) {
|
||||
this.terminalList = terminalList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取此 Session 绑定的 Token 签名列表
|
||||
* 获取登录终端信息列表
|
||||
*
|
||||
* @return Token 签名列表
|
||||
* @return /
|
||||
*/
|
||||
public List<TokenSign> getTokenSignList() {
|
||||
return tokenSignList;
|
||||
public List<SaTerminalInfo> getTerminalList() {
|
||||
return terminalList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Token 签名列表 的拷贝副本
|
||||
* 获取 登录终端信息列表 (拷贝副本)
|
||||
*
|
||||
* @return token签名列表
|
||||
* @return /
|
||||
*/
|
||||
public List<TokenSign> tokenSignListCopy() {
|
||||
return new ArrayList<>(tokenSignList);
|
||||
public List<SaTerminalInfo> terminalListCopy() {
|
||||
return new ArrayList<>(terminalList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 Token 签名列表 的拷贝副本,根据 device 筛选
|
||||
* 获取 登录终端信息列表 (拷贝副本),根据 deviceType 筛选
|
||||
*
|
||||
* @param device 设备类型,填 null 代表不限设备类型
|
||||
* @return token签名列表
|
||||
* @param deviceType 设备类型,填 null 代表不限设备类型
|
||||
* @return /
|
||||
*/
|
||||
public List<TokenSign> getTokenSignListByDevice(String device) {
|
||||
public List<SaTerminalInfo> getTerminalListByDeviceType(String deviceType) {
|
||||
// 返回全部
|
||||
if(device == null) {
|
||||
return tokenSignListCopy();
|
||||
if(deviceType == null) {
|
||||
return terminalListCopy();
|
||||
}
|
||||
// 返回筛选后的
|
||||
List<TokenSign> tokenSignList = tokenSignListCopy();
|
||||
List<TokenSign> list = new ArrayList<>();
|
||||
for (TokenSign tokenSign : tokenSignList) {
|
||||
if(SaFoxUtil.equals(tokenSign.getDevice(), device)) {
|
||||
list.add(tokenSign);
|
||||
List<SaTerminalInfo> copyList = terminalListCopy();
|
||||
List<SaTerminalInfo> newList = new ArrayList<>();
|
||||
for (SaTerminalInfo terminal : copyList) {
|
||||
if(SaFoxUtil.equals(terminal.getDeviceType(), deviceType)) {
|
||||
newList.add(terminal);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
return newList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Session 上的所有 token 列表
|
||||
* 获取 登录终端 token 列表
|
||||
*
|
||||
* @param device 设备类型,填 null 代表不限设备类型
|
||||
* @param deviceType 设备类型,填 null 代表不限设备类型
|
||||
* @return 此 loginId 的所有登录 token
|
||||
*/
|
||||
public List<String> getTokenValueListByDevice(String device) {
|
||||
// 遍历解析,按照设备类型进行筛选
|
||||
List<TokenSign> tokenSignList = tokenSignListCopy();
|
||||
public List<String> getTokenValueListByDeviceType(String deviceType) {
|
||||
List<String> tokenValueList = new ArrayList<>();
|
||||
for (TokenSign tokenSign : tokenSignList) {
|
||||
if(device == null || tokenSign.getDevice().equals(device)) {
|
||||
tokenValueList.add(tokenSign.getValue());
|
||||
}
|
||||
for (SaTerminalInfo terminal : getTerminalListByDeviceType(deviceType)) {
|
||||
tokenValueList.add(terminal.getTokenValue());
|
||||
}
|
||||
return tokenValueList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找一个 Token 签名
|
||||
* 查找一个终端信息,根据 tokenValue
|
||||
*
|
||||
* @param tokenValue token值
|
||||
* @return 查找到的 TokenSign
|
||||
* @param tokenValue /
|
||||
* @return /
|
||||
*/
|
||||
public TokenSign getTokenSign(String tokenValue) {
|
||||
for (TokenSign tokenSign : tokenSignListCopy()) {
|
||||
if (SaFoxUtil.equals(tokenSign.getValue(), tokenValue)) {
|
||||
return tokenSign;
|
||||
public SaTerminalInfo getTerminal(String tokenValue) {
|
||||
for (SaTerminalInfo terminal : terminalListCopy()) {
|
||||
if (SaFoxUtil.equals(terminal.getTokenValue(), tokenValue)) {
|
||||
return terminal;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个 Token 签名
|
||||
* 添加一个终端信息
|
||||
*
|
||||
* @param tokenSign Token 签名
|
||||
* @param terminalInfo /
|
||||
*/
|
||||
public void addTokenSign(TokenSign tokenSign) {
|
||||
// 根据 tokenValue 值查重,如果不存在,则添加
|
||||
TokenSign oldTokenSign = getTokenSign(tokenSign.getValue());
|
||||
if(oldTokenSign == null) {
|
||||
tokenSignList.add(tokenSign);
|
||||
update();
|
||||
} else {
|
||||
// 如果存在,则更新
|
||||
oldTokenSign.setValue(tokenSign.getValue());
|
||||
oldTokenSign.setDevice(tokenSign.getDevice());
|
||||
oldTokenSign.setTag(tokenSign.getTag());
|
||||
update();
|
||||
public void addTerminal(SaTerminalInfo terminalInfo) {
|
||||
// 根据 tokenValue 值查重,如果存在旧的,则先删除
|
||||
SaTerminalInfo oldTerminal = getTerminal(terminalInfo.getTokenValue());
|
||||
if(oldTerminal != null) {
|
||||
terminalList.remove(oldTerminal);
|
||||
}
|
||||
// 然后添加新的
|
||||
this.historyTerminalCount++;
|
||||
terminalInfo.setIndex(this.historyTerminalCount);
|
||||
terminalList.add(terminalInfo);
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个 Token 签名
|
||||
*
|
||||
* @param tokenValue token值
|
||||
* @param device 设备类型
|
||||
*/
|
||||
@Deprecated
|
||||
public void addTokenSign(String tokenValue, String device) {
|
||||
addTokenSign(new TokenSign(tokenValue, device, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除一个 Token 签名
|
||||
* 移除一个终端信息
|
||||
*
|
||||
* @param tokenValue token值
|
||||
*/
|
||||
public void removeTokenSign(String tokenValue) {
|
||||
TokenSign tokenSign = getTokenSign(tokenValue);
|
||||
if (tokenSignList.remove(tokenSign)) {
|
||||
public void removeTerminal(String tokenValue) {
|
||||
SaTerminalInfo terminalInfo = getTerminal(tokenValue);
|
||||
if (terminalList.remove(terminalInfo)) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 当前账号历史总计登录设备数量 (当此 SaSession 属于 Account-Session 时,此值有效)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public int getHistoryTerminalCount() {
|
||||
return this.historyTerminalCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 当前账号历史总计登录设备数量 (当此 SaSession 属于 Account-Session 时,此值有效)
|
||||
*
|
||||
* @param historyTerminalCount /
|
||||
*/
|
||||
public void setHistoryTerminalCount(int historyTerminalCount) {
|
||||
this.historyTerminalCount = historyTerminalCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历 terminalList 列表,执行特定函数
|
||||
*
|
||||
* @param function 需要执行的函数
|
||||
*/
|
||||
public void forEachTerminalList(SaTwoParamFunction<SaSession, SaTerminalInfo> function) {
|
||||
for (SaTerminalInfo terminalInfo: terminalListCopy()) {
|
||||
function.run(this, terminalInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断指定设备 id 是否为可信任设备
|
||||
* @param deviceId /
|
||||
* @return /
|
||||
*/
|
||||
public boolean isTrustDeviceId(String deviceId) {
|
||||
if(SaFoxUtil.isEmpty(deviceId)) {
|
||||
return false;
|
||||
}
|
||||
for (SaTerminalInfo terminal : terminalListCopy()) {
|
||||
if (SaFoxUtil.equals(terminal.getDeviceId(), deviceId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------- 一些操作
|
||||
|
||||
@@ -382,9 +418,9 @@ public class SaSession implements SaSetValueInterface, Serializable {
|
||||
SaTokenEventCenter.doLogoutSession(id);
|
||||
}
|
||||
|
||||
/** 当Session上的tokenSign数量为零时,注销会话 */
|
||||
public void logoutByTokenSignCountToZero() {
|
||||
if (tokenSignList.size() == 0) {
|
||||
/** 当 Session 上的 SaTerminalInfo 数量为零时,注销会话 */
|
||||
public void logoutByTerminalCountToZero() {
|
||||
if (terminalList.isEmpty()) {
|
||||
logout();
|
||||
}
|
||||
}
|
||||
@@ -393,7 +429,7 @@ public class SaSession implements SaSetValueInterface, Serializable {
|
||||
* 获取此Session的剩余存活时间 (单位: 秒)
|
||||
* @return 此Session的剩余存活时间 (单位: 秒)
|
||||
*/
|
||||
public long getTimeout() {
|
||||
public long timeout() {
|
||||
return SaManager.getSaTokenDao().getSessionTimeout(this.id);
|
||||
}
|
||||
|
||||
@@ -411,7 +447,7 @@ public class SaSession implements SaSetValueInterface, Serializable {
|
||||
*/
|
||||
public void updateMinTimeout(long minTimeout) {
|
||||
long min = trans(minTimeout);
|
||||
long curr = trans(getTimeout());
|
||||
long curr = trans(timeout());
|
||||
if(curr < min) {
|
||||
updateTimeout(minTimeout);
|
||||
}
|
||||
@@ -423,7 +459,7 @@ public class SaSession implements SaSetValueInterface, Serializable {
|
||||
*/
|
||||
public void updateMaxTimeout(long maxTimeout) {
|
||||
long max = trans(maxTimeout);
|
||||
long curr = trans(getTimeout());
|
||||
long curr = trans(timeout());
|
||||
if(curr > max) {
|
||||
updateTimeout(maxTimeout);
|
||||
}
|
||||
@@ -546,16 +582,4 @@ public class SaSession implements SaSetValueInterface, Serializable {
|
||||
|
||||
//
|
||||
|
||||
|
||||
/**
|
||||
* 请更换为:getTokenSignListByDevice(device)
|
||||
*
|
||||
* @param device 设备类型,填 null 代表不限设备类型
|
||||
* @return token签名列表
|
||||
*/
|
||||
@Deprecated
|
||||
public List<TokenSign> tokenSignListCopyByDevice(String device) {
|
||||
return getTokenSignListByDevice(device);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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.session;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 登录设备终端信息 Model
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class SaTerminalInfo implements Serializable {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1406115065849845073L;
|
||||
|
||||
/**
|
||||
* 登录会话索引值 (该账号第几个登录的设备, 从 1 开始)
|
||||
*/
|
||||
private int index;
|
||||
|
||||
/**
|
||||
* Token 值
|
||||
*/
|
||||
private String tokenValue;
|
||||
|
||||
/**
|
||||
* 所属设备类型,例如:PC、WEB、HD、MOBILE、APP
|
||||
*/
|
||||
private String deviceType;
|
||||
|
||||
/**
|
||||
* 登录设备唯一标识,例如:kQwIOrYvnXmSDkwEiFngrKidMcdrgKorXmSDkwEiFngrKidM
|
||||
*/
|
||||
private String deviceId;
|
||||
|
||||
/**
|
||||
* 此次登录的自定义扩展数据 (只允许在登录前设定,登录后不建议更改)
|
||||
*/
|
||||
private Map<String, Object> extraData;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private long createTime;
|
||||
|
||||
/**
|
||||
* 构建一个
|
||||
*/
|
||||
public SaTerminalInfo() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建一个
|
||||
*
|
||||
* @param index 登录会话索引值 (该账号第几个登录的设备)
|
||||
* @param tokenValue Token 值
|
||||
* @param deviceType 所属设备类型
|
||||
* @param extraData 此客户端登录的挂载数据
|
||||
*/
|
||||
public SaTerminalInfo(int index, String tokenValue, String deviceType, Map<String, Object> extraData) {
|
||||
this.index = index;
|
||||
this.tokenValue = tokenValue;
|
||||
this.deviceType = deviceType;
|
||||
this.extraData = extraData;
|
||||
this.createTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
// 扩展方法
|
||||
|
||||
/**
|
||||
* 此次登录的自定义扩展数据 (只允许在登录前设定,登录后不建议更改)
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTerminalInfo setExtra(String key, Object value) {
|
||||
if(this.extraData == null) {
|
||||
this.extraData = new LinkedHashMap<>();
|
||||
}
|
||||
this.extraData.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 此次登录的自定义扩展数据
|
||||
* @param key 键
|
||||
* @return 扩展数据的值
|
||||
*/
|
||||
public Object getExtra(String key) {
|
||||
if(this.extraData == null) {
|
||||
return null;
|
||||
}
|
||||
return this.extraData.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否设置了扩展数据
|
||||
* @return /
|
||||
*/
|
||||
public boolean haveExtraData() {
|
||||
return extraData != null && !extraData.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -------------------- get/set --------------------
|
||||
|
||||
/**
|
||||
* 获取 登录会话索引值 (该账号第几个登录的设备)
|
||||
*
|
||||
* @return index 登录会话索引值 (该账号第几个登录的设备)
|
||||
*/
|
||||
public int getIndex() {
|
||||
return this.index;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 登录会话索引值 (该账号第几个登录的设备)
|
||||
*
|
||||
* @param index 登录会话索引值 (该账号第几个登录的设备)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTerminalInfo setIndex(int index) {
|
||||
this.index = index;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Token 值
|
||||
*/
|
||||
public String getTokenValue() {
|
||||
return tokenValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入 Token 值
|
||||
*
|
||||
* @param tokenValue /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTerminalInfo setTokenValue(String tokenValue) {
|
||||
this.tokenValue = tokenValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 所属设备类型
|
||||
*/
|
||||
public String getDeviceType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入所属设备类型
|
||||
*
|
||||
* @param deviceType /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTerminalInfo setDeviceType(String deviceType) {
|
||||
this.deviceType = deviceType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 登录设备唯一标识
|
||||
*
|
||||
* @return deviceId 登录设备唯一标识
|
||||
*/
|
||||
public String getDeviceId() {
|
||||
return this.deviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 登录设备唯一标识,例如:kQwIOrYvnXmSDkwEiFngrKidMcdrgKorXmSDkwEiFngrKidM
|
||||
*
|
||||
* @param deviceId 登录设备唯一标识,例如:kQwIOrYvnXmSDkwEiFngrKidMcdrgKorXmSDkwEiFngrKidM
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTerminalInfo setDeviceId(String deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 此客户端登录的挂载数据
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public Map<String, Object> getExtraData() {
|
||||
return this.extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 此客户端登录的挂载数据
|
||||
*
|
||||
* @param extraData /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTerminalInfo setExtraData(Map<String, Object> extraData) {
|
||||
this.extraData = extraData;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 创建时间
|
||||
*
|
||||
* @return createTime 创建时间
|
||||
*/
|
||||
public long getCreateTime() {
|
||||
return this.createTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 创建时间
|
||||
*
|
||||
* @param createTime 创建时间
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTerminalInfo setCreateTime(long createTime) {
|
||||
this.createTime = createTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
//
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaTerminalInfo [" +
|
||||
"index=" + index +
|
||||
", tokenValue='" + tokenValue +
|
||||
", deviceType='" + deviceType +
|
||||
", deviceId='" + deviceId +
|
||||
", extraData=" + extraData +
|
||||
", createTime=" + createTime +
|
||||
']';
|
||||
}
|
||||
|
||||
/*
|
||||
* Expand in the future:
|
||||
* deviceName 登录设备端名称,一般为浏览器名称
|
||||
* systemName 登录设备操作系统名称
|
||||
* loginIp 登录IP地址
|
||||
* address 登录设备地理位置
|
||||
* loginTime 登录时间
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* 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.session;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Token 签名 Model
|
||||
*
|
||||
* <p> 挂在到 SaSession 上的 Token 签名,一般情况下,一个 TokenSign 代表一个登录的会话。</p>
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public class TokenSign implements Serializable {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1406115065849845073L;
|
||||
|
||||
/**
|
||||
* Token 值
|
||||
*/
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* 所属设备类型
|
||||
*/
|
||||
private String device;
|
||||
|
||||
/**
|
||||
* 此客户端登录的挂载数据
|
||||
*/
|
||||
private Object tag;
|
||||
|
||||
/**
|
||||
* 构建一个
|
||||
*/
|
||||
public TokenSign() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建一个
|
||||
*
|
||||
* @param value Token 值
|
||||
* @param device 所属设备类型
|
||||
* @param tag 此客户端登录的挂载数据
|
||||
*/
|
||||
public TokenSign(String value, String device, Object tag) {
|
||||
this.value = value;
|
||||
this.device = device;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Token 值
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 所属设备类型
|
||||
*/
|
||||
public String getDevice() {
|
||||
return device;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入 Token 值
|
||||
*
|
||||
* @param value /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public TokenSign setValue(String value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入所属设备类型
|
||||
*
|
||||
* @param device /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public TokenSign setDevice(String device) {
|
||||
this.device = device;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 此客户端登录的挂载数据
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public Object getTag() {
|
||||
return this.tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 此客户端登录的挂载数据
|
||||
*
|
||||
* @param tag /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public TokenSign setTag(Object tag) {
|
||||
this.tag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
//
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TokenSign [value=" + value + ", device=" + device + ", tag=" + tag + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.session.raw;
|
||||
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
|
||||
/**
|
||||
* SaSession 读写工具类 委托
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaRawSessionDelegator {
|
||||
|
||||
/**
|
||||
* raw session 类型
|
||||
*/
|
||||
public String type;
|
||||
|
||||
public SaRawSessionDelegator(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定 SaSession 是否存在
|
||||
*
|
||||
* @param valueId /
|
||||
* @return 是否存在
|
||||
*/
|
||||
public boolean isExists(Object valueId) {
|
||||
return SaRawSessionUtil.isExists(type, valueId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 SaSession 对象, 如果此 SaSession 尚未在 Cache 创建,isCreate 参数代表是否则新建并返回
|
||||
*
|
||||
* @param valueId /
|
||||
* @param isCreate 如果此 SaSession 尚未在 DB 创建,是否新建并返回
|
||||
* @return SaSession 对象
|
||||
*/
|
||||
public SaSession getSessionById(Object valueId, boolean isCreate) {
|
||||
return SaRawSessionUtil.getSessionById(type, valueId, isCreate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 SaSession, 如果此 SaSession 尚未在 DB 创建,则新建并返回
|
||||
*
|
||||
* @param valueId /
|
||||
* @return SaSession 对象
|
||||
*/
|
||||
public SaSession getSessionById(Object valueId) {
|
||||
return SaRawSessionUtil.getSessionById(type, valueId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定 SaSession
|
||||
*
|
||||
* @param valueId /
|
||||
*/
|
||||
public void deleteSessionById(Object valueId) {
|
||||
SaRawSessionUtil.deleteSessionById(type, valueId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.session.raw;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
|
||||
/**
|
||||
* SaSession 读写工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaRawSessionUtil {
|
||||
|
||||
private SaRawSessionUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接Key: 在存储 SaSession 时应该使用的 key
|
||||
*
|
||||
* @param type 类型
|
||||
* @param valueId 唯一标识
|
||||
* @return sessionId
|
||||
*/
|
||||
public static String splicingSessionKey(String type, Object valueId) {
|
||||
return SaManager.getConfig().getTokenName() + ":raw-session:" + type + ":" + valueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定 SaSession 是否存在
|
||||
*
|
||||
* @param type /
|
||||
* @param valueId /
|
||||
* @return 是否存在
|
||||
*/
|
||||
public static boolean isExists(String type, Object valueId) {
|
||||
return SaManager.getSaTokenDao().getSession(splicingSessionKey(type, valueId)) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 SaSession 对象, 如果此 SaSession 尚未在 Cache 创建,isCreate 参数代表是否则新建并返回
|
||||
*
|
||||
* @param type /
|
||||
* @param valueId /
|
||||
* @param isCreate 如果此 SaSession 尚未在 DB 创建,是否新建并返回
|
||||
* @return SaSession 对象
|
||||
*/
|
||||
public static SaSession getSessionById(String type, Object valueId, boolean isCreate) {
|
||||
String sessionId = splicingSessionKey(type, valueId);
|
||||
SaSession session = SaManager.getSaTokenDao().getSession(sessionId);
|
||||
if (session == null && isCreate) {
|
||||
session = SaStrategy.instance.createSession.apply(sessionId);
|
||||
session.setType(type);
|
||||
SaManager.getSaTokenDao().setSession(session, SaManager.getConfig().getTimeout());
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 SaSession, 如果此 SaSession 尚未在 DB 创建,则新建并返回
|
||||
*
|
||||
* @param type /
|
||||
* @param valueId /
|
||||
* @return SaSession 对象
|
||||
*/
|
||||
public static SaSession getSessionById(String type, Object valueId) {
|
||||
return getSessionById(type, valueId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定 SaSession
|
||||
*
|
||||
* @param type /
|
||||
* @param valueId /
|
||||
*/
|
||||
public static void deleteSessionById(String type, Object valueId) {
|
||||
SaManager.getSaTokenDao().deleteSession(splicingSessionKey(type, valueId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.sign;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.config.SaSignConfig;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.SaSignException;
|
||||
import cn.dev33.satoken.fun.SaParamRetFunction;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* API 参数签名算法 - 多实例总控类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class SaSignMany {
|
||||
|
||||
/**
|
||||
* 根据 appid 获取 SaSignConfig,允许自定义
|
||||
*/
|
||||
public static SaParamRetFunction<String, SaSignConfig> findSaSignConfigMethod = (appid) -> {
|
||||
return SaManager.getConfig().getSignMany().get(appid);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取 SaSignTemplate,根据 appid
|
||||
* @param appid /
|
||||
* @return /
|
||||
*/
|
||||
public static SaSignTemplate getSignTemplate(String appid) {
|
||||
|
||||
// appid 为空,返回全局默认 SaSignTemplate
|
||||
if(SaFoxUtil.isEmpty(appid)){
|
||||
return SaManager.getSaSignTemplate();
|
||||
}
|
||||
|
||||
// 获取 SaSignConfig
|
||||
SaSignConfig config = findSaSignConfigMethod.run(appid);
|
||||
if(config == null){
|
||||
throw new SaSignException("未找到签名配置,appid=" + appid).setCode(SaErrorCode.CODE_12211);
|
||||
}
|
||||
|
||||
// 创建 SaSignTemplate 并返回
|
||||
return new SaSignTemplate(config);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import cn.dev33.satoken.config.SaSignConfig;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.SaSignException;
|
||||
import cn.dev33.satoken.secure.SaSecureUtil;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -173,7 +172,7 @@ public class SaSignTemplate {
|
||||
// 计算签名
|
||||
String paramsStr = joinParamsDictSort(paramsMap);
|
||||
String fullStr = paramsStr + "&" + key + "=" + secretKey;
|
||||
String signStr = abstractStr(fullStr);
|
||||
String signStr = digestFullStr(fullStr);
|
||||
|
||||
// 输入日志,方便调试
|
||||
log.debug("fullStr:{}", fullStr);
|
||||
@@ -188,8 +187,8 @@ public class SaSignTemplate {
|
||||
* @param fullStr 待摘要的字符串
|
||||
* @return 签名
|
||||
*/
|
||||
public String abstractStr(String fullStr) {
|
||||
return SaSecureUtil.md5(fullStr);
|
||||
public String digestFullStr(String fullStr) {
|
||||
return getSignConfigOrGlobal().digestMethod.run(fullStr);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,7 +280,7 @@ public class SaSignTemplate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:给定的参数 + 秘钥 生成的签名是否为有效签名
|
||||
* 判断:给定的参数 生成的签名是否为有效签名
|
||||
* @param paramsMap 参数列表
|
||||
* @param sign 待验证的签名
|
||||
* @return 签名是否有效
|
||||
@@ -292,7 +291,7 @@ public class SaSignTemplate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:给定的参数 + 秘钥 生成的签名是否为有效签名,如果签名无效则抛出异常
|
||||
* 校验:给定的参数 生成的签名是否为有效签名,如果签名无效则抛出异常
|
||||
* @param paramsMap 参数列表
|
||||
* @param sign 待验证的签名
|
||||
*/
|
||||
@@ -349,6 +348,9 @@ public class SaSignTemplate {
|
||||
// 通过 √
|
||||
}
|
||||
|
||||
|
||||
// ----------- Web 请求相关 封装
|
||||
|
||||
/**
|
||||
* 判断:一个请求中的 nonce、timestamp、sign 是否均为合法的
|
||||
* @param request 待校验的请求对象
|
||||
@@ -365,8 +367,8 @@ public class SaSignTemplate {
|
||||
|
||||
/**
|
||||
* 校验:一个请求的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常
|
||||
* @param paramNames 指定参与签名的参数有哪些,如果不填写则默认为全部参数
|
||||
* @param request 待校验的请求对象
|
||||
* @param paramNames 指定参与签名的参数有哪些,如果不填写则默认为全部参数
|
||||
*/
|
||||
public void checkRequest(SaRequest request, String... paramNames) {
|
||||
if (paramNames.length == 0) {
|
||||
@@ -382,7 +384,7 @@ public class SaSignTemplate {
|
||||
* @param paramNames 指定的参数名称,不可为空,如果传入空数组则代表只拿 timestamp、nonce、sign 三个参数
|
||||
* @return 提取出的参数
|
||||
*/
|
||||
public Map<String, String> takeRequestParam(SaRequest request, String [] paramNames) {
|
||||
protected Map<String, String> takeRequestParam(SaRequest request, String [] paramNames) {
|
||||
Map<String, String> paramMap = new TreeMap<>();
|
||||
|
||||
// 此三个参数是必须获取的
|
||||
|
||||
@@ -119,7 +119,7 @@ public class SaSignUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:给定的参数 + 秘钥 生成的签名是否为有效签名
|
||||
* 判断:给定的参数 生成的签名是否为有效签名
|
||||
* @param paramsMap 参数列表
|
||||
* @param sign 待验证的签名
|
||||
* @return 签名是否有效
|
||||
@@ -129,7 +129,7 @@ public class SaSignUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:给定的参数 + 秘钥 生成的签名是否为有效签名,如果签名无效则抛出异常
|
||||
* 校验:给定的参数 生成的签名是否为有效签名,如果签名无效则抛出异常
|
||||
* @param paramsMap 参数列表
|
||||
* @param sign 待验证的签名
|
||||
*/
|
||||
@@ -154,21 +154,26 @@ public class SaSignUtil {
|
||||
SaManager.getSaSignTemplate().checkParamMap(paramMap);
|
||||
}
|
||||
|
||||
|
||||
// ----------- Web 请求相关 封装
|
||||
|
||||
/**
|
||||
* 判断:一个请求中的 nonce、timestamp、sign 是否均为合法的
|
||||
* @param request 待校验的请求对象
|
||||
* @param paramNames 指定参与签名的参数有哪些,如果不填写则默认为全部参数
|
||||
* @return 是否合法
|
||||
*/
|
||||
public static boolean isValidRequest(SaRequest request) {
|
||||
return SaManager.getSaSignTemplate().isValidRequest(request);
|
||||
public static boolean isValidRequest(SaRequest request, String... paramNames) {
|
||||
return SaManager.getSaSignTemplate().isValidRequest(request, paramNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:一个请求的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常
|
||||
* @param request 待校验的请求对象
|
||||
* @param paramNames 指定参与签名的参数有哪些,如果不填写则默认为全部参数
|
||||
*/
|
||||
public static void checkRequest(SaRequest request) {
|
||||
SaManager.getSaSignTemplate().checkRequest(request);
|
||||
public static void checkRequest(SaRequest request, String... paramNames) {
|
||||
SaManager.getSaSignTemplate().checkRequest(request, paramNames);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,22 +15,28 @@
|
||||
*/
|
||||
package cn.dev33.satoken.stp;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 快速、简洁的构建:调用 `StpUtil.login()` 时的 [ 配置参数 SaLoginModel ]
|
||||
* <h2> 请更换为 new SaLoginParameter() </h2>
|
||||
*
|
||||
* 快速、简洁的构建:调用 `StpUtil.login()` 时的 [ 配置参数 SaLoginParameter ]
|
||||
*
|
||||
* <pre>
|
||||
* // 例如:在登录时指定 token 有效期为七天,代码如下:
|
||||
* StpUtil.login(10001, SaLoginConfig.setTimeout(60 * 60 * 24 * 7));
|
||||
*
|
||||
* // 上面的代码与下面的代码等价
|
||||
* StpUtil.login(10001, new SaLoginModel().setTimeout(60 * 60 * 24 * 7));
|
||||
* StpUtil.login(10001, new SaLoginParameter().setTimeout(60 * 60 * 24 * 7));
|
||||
* </pre>
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.29.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class SaLoginConfig {
|
||||
|
||||
private SaLoginConfig() {
|
||||
@@ -40,15 +46,15 @@ public class SaLoginConfig {
|
||||
* @param device 此次登录的客户端设备类型
|
||||
* @return 登录参数 Model
|
||||
*/
|
||||
public static SaLoginModel setDevice(String device) {
|
||||
return create().setDevice(device);
|
||||
public static SaLoginParameter setDevice(String device) {
|
||||
return create().setDeviceType(device);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isLastingCookie 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
* @return 登录参数 Model
|
||||
*/
|
||||
public static SaLoginModel setIsLastingCookie(Boolean isLastingCookie) {
|
||||
public static SaLoginParameter setIsLastingCookie(Boolean isLastingCookie) {
|
||||
return create().setIsLastingCookie(isLastingCookie);
|
||||
}
|
||||
|
||||
@@ -56,7 +62,7 @@ public class SaLoginConfig {
|
||||
* @param timeout 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值)
|
||||
* @return 登录参数 Model
|
||||
*/
|
||||
public static SaLoginModel setTimeout(long timeout) {
|
||||
public static SaLoginParameter setTimeout(long timeout) {
|
||||
return create().setTimeout(timeout);
|
||||
}
|
||||
|
||||
@@ -64,7 +70,7 @@ public class SaLoginConfig {
|
||||
* @param activeTimeout 指定此次登录 token 最低活跃频率,单位:秒(如未指定,自动取全局配置的 activeTimeout 值)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public static SaLoginModel setActiveTimeout(long activeTimeout) {
|
||||
public static SaLoginParameter setActiveTimeout(long activeTimeout) {
|
||||
return create().setActiveTimeout(activeTimeout);
|
||||
}
|
||||
|
||||
@@ -72,7 +78,7 @@ public class SaLoginConfig {
|
||||
* @param extraData 扩展信息(只在jwt模式下生效)
|
||||
* @return 登录参数 Model
|
||||
*/
|
||||
public static SaLoginModel setExtraData(Map<String, Object> extraData) {
|
||||
public static SaLoginParameter setExtraData(Map<String, Object> extraData) {
|
||||
return create().setExtraData(extraData);
|
||||
}
|
||||
|
||||
@@ -80,7 +86,7 @@ public class SaLoginConfig {
|
||||
* @param token 预定Token(预定本次登录生成的Token值)
|
||||
* @return 登录参数 Model
|
||||
*/
|
||||
public static SaLoginModel setToken(String token) {
|
||||
public static SaLoginParameter setToken(String token) {
|
||||
return create().setToken(token);
|
||||
}
|
||||
|
||||
@@ -90,7 +96,7 @@ public class SaLoginConfig {
|
||||
* @param value 值
|
||||
* @return 登录参数 Model
|
||||
*/
|
||||
public static SaLoginModel setExtra(String key, Object value) {
|
||||
public static SaLoginParameter setExtra(String key, Object value) {
|
||||
return create().setExtra(key, value);
|
||||
}
|
||||
|
||||
@@ -98,7 +104,7 @@ public class SaLoginConfig {
|
||||
* @param isWriteHeader 是否在登录后将 Token 写入到响应头
|
||||
* @return 登录参数 Model
|
||||
*/
|
||||
public static SaLoginModel setIsWriteHeader(Boolean isWriteHeader) {
|
||||
public static SaLoginParameter setIsWriteHeader(Boolean isWriteHeader) {
|
||||
return create().setIsWriteHeader(isWriteHeader);
|
||||
}
|
||||
|
||||
@@ -108,16 +114,16 @@ public class SaLoginConfig {
|
||||
* @param tokenSignTag /
|
||||
* @return 登录参数 Model
|
||||
*/
|
||||
public static SaLoginModel setTokenSignTag(Object tokenSignTag) {
|
||||
return create().setTokenSignTag(tokenSignTag);
|
||||
public static SaLoginParameter setTokenSignTag(Map<String, Object> tokenSignTag) {
|
||||
return create().setTerminalExtraData(tokenSignTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法获取一个 SaLoginModel 对象
|
||||
* @return SaLoginModel 对象
|
||||
* 静态方法获取一个 SaLoginParameter 对象
|
||||
* @return SaLoginParameter 对象
|
||||
*/
|
||||
public static SaLoginModel create() {
|
||||
return new SaLoginModel();
|
||||
public static SaLoginParameter create() {
|
||||
return new SaLoginParameter(SaManager.getConfig());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,347 +15,16 @@
|
||||
*/
|
||||
package cn.dev33.satoken.stp;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
|
||||
/**
|
||||
* <h2> 请更改为 SaLoginParameter </h2>
|
||||
* 在调用 `StpUtil.login()` 时的 配置参数 Model,决定登录的一些细节行为 <br>
|
||||
*
|
||||
* <pre>
|
||||
* // 例如:在登录时指定 token 有效期为七天,代码如下:
|
||||
* StpUtil.login(10001, new SaLoginModel().setTimeout(60 * 60 * 24 * 7));
|
||||
* </pre>
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.13.2
|
||||
*/
|
||||
public class SaLoginModel {
|
||||
|
||||
/**
|
||||
* 此次登录的客户端设备类型
|
||||
*/
|
||||
public String device;
|
||||
|
||||
/**
|
||||
* 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
*/
|
||||
public Boolean isLastingCookie = true;
|
||||
|
||||
/**
|
||||
* 指定此次登录 token 有效期,单位:秒 (如未指定,自动取全局配置的 timeout 值)
|
||||
*/
|
||||
public Long timeout;
|
||||
|
||||
/**
|
||||
* 指定此次登录 token 最低活跃频率,单位:秒(如未指定,则使用全局配置的 activeTimeout 值)
|
||||
*/
|
||||
private Long activeTimeout;
|
||||
|
||||
/**
|
||||
* 扩展信息(只在jwt模式下生效)
|
||||
*/
|
||||
public Map<String, Object> extraData;
|
||||
|
||||
/**
|
||||
* 预定Token(预定本次登录生成的Token值)
|
||||
*/
|
||||
public String token;
|
||||
|
||||
/** 是否在登录后将 Token 写入到响应头 */
|
||||
private Boolean isWriteHeader;
|
||||
|
||||
/** 本次登录挂载到 TokenSign 的数据 */
|
||||
private Object tokenSignTag;
|
||||
|
||||
|
||||
/**
|
||||
* @return 此次登录的客户端设备类型
|
||||
*/
|
||||
public String getDevice() {
|
||||
return device;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param device 此次登录的客户端设备类型
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginModel setDevice(String device) {
|
||||
this.device = device;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
*/
|
||||
public Boolean getIsLastingCookie() {
|
||||
return isLastingCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isLastingCookie 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginModel setIsLastingCookie(Boolean isLastingCookie) {
|
||||
this.isLastingCookie = isLastingCookie;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 指定此次登录 token 有效期,单位:秒
|
||||
*/
|
||||
public Long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timeout 指定此次登录 token 有效期,单位:秒 (如未指定,自动取全局配置的 timeout 值)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginModel setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 此次登录 token 最低活跃频率,单位:秒(如未指定,则使用全局配置的 activeTimeout 值)
|
||||
*/
|
||||
public Long getActiveTimeout() {
|
||||
return activeTimeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param activeTimeout 指定此次登录 token 最低活跃频率,单位:秒(如未指定,则使用全局配置的 activeTimeout 值)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginModel setActiveTimeout(long activeTimeout) {
|
||||
this.activeTimeout = activeTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 扩展信息(只在jwt模式下生效)
|
||||
*/
|
||||
public Map<String, Object> getExtraData() {
|
||||
return extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param extraData 扩展信息(只在jwt模式下生效)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginModel setExtraData(Map<String, Object> extraData) {
|
||||
this.extraData = extraData;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 预定Token(预定本次登录生成的Token值)
|
||||
*/
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param token 预定Token(预定本次登录生成的Token值)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginModel setToken(String token) {
|
||||
this.token = token;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否在登录后将 Token 写入到响应头
|
||||
*/
|
||||
public Boolean getIsWriteHeader() {
|
||||
return isWriteHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isWriteHeader 是否在登录后将 Token 写入到响应头
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginModel setIsWriteHeader(Boolean isWriteHeader) {
|
||||
this.isWriteHeader = isWriteHeader;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 本次登录挂载到 TokenSign 的数据
|
||||
*
|
||||
* @return tokenSignTag 本次登录挂载到 TokenSign 的数据
|
||||
*/
|
||||
public Object getTokenSignTag() {
|
||||
return this.tokenSignTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 本次登录挂载到 TokenSign 的数据
|
||||
*
|
||||
* @param tokenSignTag 本次登录挂载到 TokenSign 的数据
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginModel setTokenSignTag(Object tokenSignTag) {
|
||||
this.tokenSignTag = tokenSignTag;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* toString
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaLoginModel ["
|
||||
+ "device=" + device
|
||||
+ ", isLastingCookie=" + isLastingCookie
|
||||
+ ", timeout=" + timeout
|
||||
+ ", activeTimeout=" + activeTimeout
|
||||
+ ", extraData=" + extraData
|
||||
+ ", token=" + token
|
||||
+ ", isWriteHeader=" + isWriteHeader
|
||||
+ ", tokenSignTag=" + tokenSignTag
|
||||
+ "]";
|
||||
}
|
||||
|
||||
|
||||
// ------ 附加方法
|
||||
|
||||
/**
|
||||
* @return 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
*/
|
||||
public Boolean getIsLastingCookieOrFalse() {
|
||||
if(isLastingCookie == null) {
|
||||
return false;
|
||||
}
|
||||
return isLastingCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入扩展数据(只在jwt模式下生效)
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginModel setExtra(String key, Object value) {
|
||||
if(this.extraData == null) {
|
||||
this.extraData = new LinkedHashMap<>();
|
||||
}
|
||||
this.extraData.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展数据(只在jwt模式下生效)
|
||||
* @param key 键
|
||||
* @return 扩展数据的值
|
||||
*/
|
||||
public Object getExtra(String key) {
|
||||
if(this.extraData == null) {
|
||||
return null;
|
||||
}
|
||||
return this.extraData.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否设置了扩展数据
|
||||
* @return /
|
||||
*/
|
||||
public boolean isSetExtraData() {
|
||||
return extraData != null && extraData.size() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Cookie时长
|
||||
*/
|
||||
public int getCookieTimeout() {
|
||||
if( ! getIsLastingCookieOrFalse()) {
|
||||
return -1;
|
||||
}
|
||||
long _timeout = getTimeout();
|
||||
if(_timeout == SaTokenDao.NEVER_EXPIRE || _timeout > Integer.MAX_VALUE) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
return (int)_timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 获取device参数,如果为null,则返回默认值
|
||||
*/
|
||||
public String getDeviceOrDefault() {
|
||||
if(device == null) {
|
||||
return SaTokenConsts.DEFAULT_LOGIN_DEVICE;
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建对象,初始化默认值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginModel build() {
|
||||
return build(SaManager.getConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建对象,初始化默认值
|
||||
* @param config 配置对象
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginModel build(SaTokenConfig config) {
|
||||
// if(device == null) {
|
||||
// device = SaTokenConsts.DEFAULT_LOGIN_DEVICE;
|
||||
// }
|
||||
// if(isLastingCookie == null) {
|
||||
// isLastingCookie = true;
|
||||
// }
|
||||
if(timeout == null) {
|
||||
timeout = config.getTimeout();
|
||||
}
|
||||
if(isWriteHeader == null) {
|
||||
isWriteHeader = config.getIsWriteHeader();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法获取一个 SaLoginModel 对象
|
||||
* @return SaLoginModel 对象
|
||||
*/
|
||||
public static SaLoginModel create() {
|
||||
return new SaLoginModel();
|
||||
}
|
||||
|
||||
|
||||
// ---------------- 过期方法
|
||||
|
||||
/**
|
||||
* 请改为 getTimeout
|
||||
* @return timeout 值 (如果此配置项尚未配置,则取全局配置的值)
|
||||
*/
|
||||
@Deprecated
|
||||
public Long getTimeoutOrGlobalConfig() {
|
||||
if(timeout == null) {
|
||||
timeout = SaManager.getConfig().getTimeout();
|
||||
}
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请改为 getIsWriteHeader
|
||||
* @return 是否在登录后将 Token 写入到响应头 (如果此配置项尚未配置,则取全局配置的值)
|
||||
*/
|
||||
@Deprecated
|
||||
public Boolean getIsWriteHeaderOrGlobalConfig() {
|
||||
if(isWriteHeader == null) {
|
||||
isWriteHeader = SaManager.getConfig().getIsWriteHeader();
|
||||
}
|
||||
return isWriteHeader;
|
||||
}
|
||||
@Deprecated
|
||||
public class SaLoginModel extends SaLoginParameter {
|
||||
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ package cn.dev33.satoken.stp;
|
||||
* "sessionTimeout": 2591977, // Account-Session剩余有效时间 (单位: 秒)
|
||||
* "tokenSessionTimeout": -2, // Token-Session剩余有效时间 (单位: 秒) (-2表示系统中不存在这个缓存)
|
||||
* "tokenActiveTimeout": -1, // Token 距离被冻结还剩多少时间 (单位: 秒)
|
||||
* "loginDevice": "default-device" // 登录设备类型
|
||||
* "loginDevice": "DEF" // 登录设备类型
|
||||
* }
|
||||
* </pre>
|
||||
* </p>
|
||||
@@ -69,7 +69,7 @@ public class SaTokenInfo {
|
||||
public long tokenActiveTimeout;
|
||||
|
||||
/** 登录设备类型 */
|
||||
public String loginDevice;
|
||||
public String loginDeviceType;
|
||||
|
||||
/** 自定义数据(暂无意义,留作扩展) */
|
||||
public String tag;
|
||||
@@ -205,15 +205,15 @@ public class SaTokenInfo {
|
||||
/**
|
||||
* @return 登录设备类型
|
||||
*/
|
||||
public String getLoginDevice() {
|
||||
return loginDevice;
|
||||
public String getLoginDeviceType() {
|
||||
return loginDeviceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param loginDevice 登录设备类型
|
||||
* @param loginDeviceType 登录设备类型
|
||||
*/
|
||||
public void setLoginDevice(String loginDevice) {
|
||||
this.loginDevice = loginDevice;
|
||||
public void setLoginDeviceType(String loginDeviceType) {
|
||||
this.loginDeviceType = loginDeviceType;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,7 +238,7 @@ public class SaTokenInfo {
|
||||
return "SaTokenInfo [tokenName=" + tokenName + ", tokenValue=" + tokenValue + ", isLogin=" + isLogin
|
||||
+ ", loginId=" + loginId + ", loginType=" + loginType + ", tokenTimeout=" + tokenTimeout
|
||||
+ ", sessionTimeout=" + sessionTimeout + ", tokenSessionTimeout=" + tokenSessionTimeout
|
||||
+ ", tokenActiveTimeout=" + tokenActiveTimeout + ", loginDevice=" + loginDevice + ", tag=" + tag
|
||||
+ ", tokenActiveTimeout=" + tokenActiveTimeout + ", loginDeviceType=" + loginDeviceType + ", tag=" + tag
|
||||
+ "]";
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,9 +17,12 @@ package cn.dev33.satoken.stp;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.fun.SaFunction;
|
||||
import cn.dev33.satoken.fun.SaTwoParamFunction;
|
||||
import cn.dev33.satoken.listener.SaTokenEventCenter;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.session.TokenSign;
|
||||
import cn.dev33.satoken.session.SaTerminalInfo;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -117,10 +120,19 @@ public class StpUtil {
|
||||
* 在当前会话写入指定 token 值
|
||||
*
|
||||
* @param tokenValue token 值
|
||||
* @param loginModel 登录参数
|
||||
* @param loginParameter 登录参数
|
||||
*/
|
||||
public static void setTokenValue(String tokenValue, SaLoginModel loginModel){
|
||||
stpLogic.setTokenValue(tokenValue, loginModel);
|
||||
public static void setTokenValue(String tokenValue, SaLoginParameter loginParameter){
|
||||
stpLogic.setTokenValue(tokenValue, loginParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 token 写入到当前请求的 Storage 存储器里
|
||||
*
|
||||
* @param tokenValue 要保存的 token 值
|
||||
*/
|
||||
public static void setTokenValueToStorage(String tokenValue){
|
||||
stpLogic.setTokenValueToStorage(tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,10 +180,10 @@ public class StpUtil {
|
||||
* 会话登录,并指定登录设备类型
|
||||
*
|
||||
* @param id 账号id,建议的类型:(long | int | String)
|
||||
* @param device 设备类型
|
||||
* @param deviceType 设备类型
|
||||
*/
|
||||
public static void login(Object id, String device) {
|
||||
stpLogic.login(id, device);
|
||||
public static void login(Object id, String deviceType) {
|
||||
stpLogic.login(id, deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,10 +210,10 @@ public class StpUtil {
|
||||
* 会话登录,并指定所有登录参数 Model
|
||||
*
|
||||
* @param id 账号id,建议的类型:(long | int | String)
|
||||
* @param loginModel 此次登录的参数Model
|
||||
* @param loginParameter 此次登录的参数Model
|
||||
*/
|
||||
public static void login(Object id, SaLoginModel loginModel) {
|
||||
stpLogic.login(id, loginModel);
|
||||
public static void login(Object id, SaLoginParameter loginParameter) {
|
||||
stpLogic.login(id, loginParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,11 +230,11 @@ public class StpUtil {
|
||||
* 创建指定账号 id 的登录会话数据
|
||||
*
|
||||
* @param id 账号id,建议的类型:(long | int | String)
|
||||
* @param loginModel 此次登录的参数Model
|
||||
* @param loginParameter 此次登录的参数Model
|
||||
* @return 返回会话令牌
|
||||
*/
|
||||
public static String createLoginSession(Object id, SaLoginModel loginModel) {
|
||||
return stpLogic.createLoginSession(id, loginModel);
|
||||
public static String createLoginSession(Object id, SaLoginParameter loginParameter) {
|
||||
return stpLogic.createLoginSession(id, loginParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,7 +247,7 @@ public class StpUtil {
|
||||
return stpLogic.getOrCreateLoginSession(id);
|
||||
}
|
||||
|
||||
// --- 注销
|
||||
// --- 注销 (根据 token)
|
||||
|
||||
/**
|
||||
* 在当前客户端会话注销
|
||||
@@ -245,26 +257,14 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据账号id
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* 在当前客户端会话注销,根据注销参数
|
||||
*/
|
||||
public static void logout(Object loginId) {
|
||||
stpLogic.logout(loginId);
|
||||
public static void logout(SaLogoutParameter logoutParameter) {
|
||||
stpLogic.logout(logoutParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据账号id 和 设备类型
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备类型 (填 null 代表注销该账号的所有设备类型)
|
||||
*/
|
||||
public static void logout(Object loginId, String device) {
|
||||
stpLogic.logout(loginId, device);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据指定 Token
|
||||
* 注销下线,根据指定 token
|
||||
*
|
||||
* @param tokenValue 指定 token
|
||||
*/
|
||||
@@ -273,24 +273,13 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢人下线,根据账号id
|
||||
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
|
||||
* 注销下线,根据指定 token、注销参数
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param tokenValue 指定 token
|
||||
* @param logoutParameter /
|
||||
*/
|
||||
public static void kickout(Object loginId) {
|
||||
stpLogic.kickout(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢人下线,根据账号id 和 设备类型
|
||||
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备类型 (填 null 代表踢出该账号的所有设备类型)
|
||||
*/
|
||||
public static void kickout(Object loginId, String device) {
|
||||
stpLogic.kickout(loginId, device);
|
||||
public static void logoutByTokenValue(String tokenValue, SaLogoutParameter logoutParameter) {
|
||||
stpLogic.logoutByTokenValue(tokenValue, logoutParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,17 +292,163 @@ public class StpUtil {
|
||||
stpLogic.kickoutByTokenValue(tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢人下线,根据指定 token、注销参数
|
||||
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
|
||||
*
|
||||
* @param tokenValue 指定 token
|
||||
* @param logoutParameter 注销参数
|
||||
*/
|
||||
public static void kickoutByTokenValue(String tokenValue, SaLogoutParameter logoutParameter) {
|
||||
stpLogic.kickoutByTokenValue(tokenValue, logoutParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 顶人下线,根据指定 token
|
||||
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
|
||||
*
|
||||
* @param tokenValue 指定 token
|
||||
*/
|
||||
public static void replacedByTokenValue(String tokenValue) {
|
||||
stpLogic.replacedByTokenValue(tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 顶人下线,根据指定 token、注销参数
|
||||
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
|
||||
*
|
||||
* @param tokenValue 指定 token
|
||||
* @param logoutParameter /
|
||||
*/
|
||||
public static void replacedByTokenValue(String tokenValue, SaLogoutParameter logoutParameter) {
|
||||
stpLogic.replacedByTokenValue(tokenValue, logoutParameter);
|
||||
}
|
||||
|
||||
// --- 注销 (根据 loginId)
|
||||
|
||||
/**
|
||||
* 会话注销,根据账号id
|
||||
*
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public static void logout(Object loginId) {
|
||||
stpLogic.logout(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据账号id 和 设备类型
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param deviceType 设备类型 (填 null 代表注销该账号的所有设备类型)
|
||||
*/
|
||||
public static void logout(Object loginId, String deviceType) {
|
||||
stpLogic.logout(loginId, deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据账号id 和 注销参数
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param logoutParameter 注销参数
|
||||
*/
|
||||
public static void logout(Object loginId, SaLogoutParameter logoutParameter) {
|
||||
stpLogic.logout(loginId, logoutParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢人下线,根据账号id
|
||||
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public static void kickout(Object loginId) {
|
||||
stpLogic.kickout(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢人下线,根据账号id 和 设备类型
|
||||
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param deviceType 设备类型 (填 null 代表踢出该账号的所有设备类型)
|
||||
*/
|
||||
public static void kickout(Object loginId, String deviceType) {
|
||||
stpLogic.kickout(loginId, deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢人下线,根据账号id 和 注销参数
|
||||
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param logoutParameter 注销参数
|
||||
*/
|
||||
public static void kickout(Object loginId, SaLogoutParameter logoutParameter) {
|
||||
stpLogic.kickout(loginId, logoutParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 顶人下线,根据账号id
|
||||
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public static void replaced(Object loginId) {
|
||||
stpLogic.replaced(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 顶人下线,根据账号id 和 设备类型
|
||||
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备类型 (填 null 代表顶替该账号的所有设备类型)
|
||||
* @param deviceType 设备类型 (填 null 代表顶替该账号的所有设备类型)
|
||||
*/
|
||||
public static void replaced(Object loginId, String device) {
|
||||
stpLogic.replaced(loginId, device);
|
||||
public static void replaced(Object loginId, String deviceType) {
|
||||
stpLogic.replaced(loginId, deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 顶人下线,根据账号id 和 注销参数
|
||||
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param logoutParameter 注销参数
|
||||
*/
|
||||
public static void replaced(Object loginId, SaLogoutParameter logoutParameter) {
|
||||
stpLogic.replaced(loginId, logoutParameter);
|
||||
}
|
||||
|
||||
// --- 注销 (会话管理辅助方法)
|
||||
|
||||
/**
|
||||
* 在 Account-Session 上移除 Terminal 信息 (注销下线方式)
|
||||
* @param session /
|
||||
* @param terminal /
|
||||
*/
|
||||
public static void removeTerminalByLogout(SaSession session, SaTerminalInfo terminal) {
|
||||
stpLogic.removeTerminalByLogout(session, terminal);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 Account-Session 上移除 Terminal 信息 (踢人下线方式)
|
||||
* @param session /
|
||||
* @param terminal /
|
||||
*/
|
||||
public static void removeTerminalByKickout(SaSession session, SaTerminalInfo terminal) {
|
||||
stpLogic.removeTerminalByKickout(session, terminal);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 Account-Session 上移除 Terminal 信息 (顶人下线方式)
|
||||
* @param session /
|
||||
* @param terminal /
|
||||
*/
|
||||
public static void removeTerminalByReplaced(SaSession session, SaTerminalInfo terminal) {
|
||||
stpLogic.removeTerminalByReplaced(session, terminal);
|
||||
}
|
||||
|
||||
|
||||
// 会话查询
|
||||
|
||||
/**
|
||||
@@ -398,7 +533,7 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 token 对应的账号id,如果未登录,则返回 null
|
||||
* 获取指定 token 对应的账号id,如果 token 无效或 token 处于被踢、被顶、被冻结等状态,则返回 null
|
||||
*
|
||||
* @param tokenValue token
|
||||
* @return 账号id
|
||||
@@ -407,6 +542,16 @@ public class StpUtil {
|
||||
return stpLogic.getLoginIdByToken(tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 token 对应的账号id,如果 token 无效或 token 处于被踢、被顶等状态 (不考虑被冻结),则返回 null
|
||||
*
|
||||
* @param tokenValue token
|
||||
* @return 账号id
|
||||
*/
|
||||
public Object getLoginIdByTokenNotThinkFreeze(String tokenValue) {
|
||||
return stpLogic.getLoginIdByTokenNotThinkFreeze(tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Token 的扩展信息(此函数只在jwt模式下生效)
|
||||
*
|
||||
@@ -806,11 +951,11 @@ public class StpUtil {
|
||||
* </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备类型,填 null 代表不限设备类型
|
||||
* @param deviceType 设备类型,填 null 代表不限设备类型
|
||||
* @return token值
|
||||
*/
|
||||
public static String getTokenValueByLoginId(Object loginId, String device) {
|
||||
return stpLogic.getTokenValueByLoginId(loginId, device);
|
||||
public static String getTokenValueByLoginId(Object loginId, String deviceType) {
|
||||
return stpLogic.getTokenValueByLoginId(loginId, deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -827,22 +972,42 @@ public class StpUtil {
|
||||
* 获取指定账号 id 指定设备类型端的 token 集合
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备类型,填 null 代表不限设备类型
|
||||
* @param deviceType 设备类型,填 null 代表不限设备类型
|
||||
* @return 此 loginId 的所有登录 token
|
||||
*/
|
||||
public static List<String> getTokenValueListByLoginId(Object loginId, String device) {
|
||||
return stpLogic.getTokenValueListByLoginId(loginId, device);
|
||||
public static List<String> getTokenValueListByLoginId(Object loginId, String deviceType) {
|
||||
return stpLogic.getTokenValueListByLoginId(loginId, deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定账号 id 指定设备类型端的 tokenSign 集合
|
||||
* 获取指定账号 id 已登录设备信息集合
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备类型,填 null 代表不限设备类型
|
||||
* @return 此 loginId 的所有登录 tokenSign
|
||||
* @return 此 loginId 的所有登录 token
|
||||
*/
|
||||
public static List<TokenSign> getTokenSignListByLoginId(Object loginId, String device) {
|
||||
return stpLogic.getTokenSignListByLoginId(loginId, device);
|
||||
public static List<SaTerminalInfo> getTerminalListByLoginId(Object loginId) {
|
||||
return stpLogic.getTerminalListByLoginId(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定账号 id 指定设备类型端的已登录设备信息集合
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param deviceType 设备类型,填 null 代表不限设备类型
|
||||
* @return /
|
||||
*/
|
||||
public static List<SaTerminalInfo> getTerminalListByLoginId(Object loginId, String deviceType) {
|
||||
return stpLogic.getTerminalListByLoginId(loginId, deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定账号 id 已登录设备信息集合,执行特定函数
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param function 需要执行的函数
|
||||
*/
|
||||
public static void forEachTerminalList(Object loginId, SaTwoParamFunction<SaSession, SaTerminalInfo> function) {
|
||||
stpLogic.forEachTerminalList(loginId, function);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -850,8 +1015,8 @@ public class StpUtil {
|
||||
*
|
||||
* @return 当前令牌的登录设备类型
|
||||
*/
|
||||
public static String getLoginDevice() {
|
||||
return stpLogic.getLoginDevice();
|
||||
public static String getLoginDeviceType() {
|
||||
return stpLogic.getLoginDeviceType();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -860,8 +1025,8 @@ public class StpUtil {
|
||||
* @param tokenValue 指定token
|
||||
* @return 当前令牌的登录设备类型
|
||||
*/
|
||||
public static String getLoginDeviceByToken(String tokenValue) {
|
||||
return stpLogic.getLoginDeviceByToken(tokenValue);
|
||||
public static String getLoginDeviceTypeByToken(String tokenValue) {
|
||||
return stpLogic.getLoginDeviceTypeByToken(tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -873,6 +1038,15 @@ public class StpUtil {
|
||||
return stpLogic.getTokenLastActiveTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断对于指定 loginId 来讲,指定设备 id 是否为可信任设备
|
||||
* @param deviceId /
|
||||
* @return /
|
||||
*/
|
||||
public static boolean isTrustDeviceId(Object userId, String deviceId) {
|
||||
return stpLogic.isTrustDeviceId(userId, deviceId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------- 会话管理 -------------------
|
||||
@@ -1260,4 +1434,42 @@ public class StpUtil {
|
||||
stpLogic.closeSafe(service);
|
||||
}
|
||||
|
||||
|
||||
// ------------------- Bean 对象、字段代理 -------------------
|
||||
|
||||
/**
|
||||
* 根据当前配置对象创建一个 SaLoginParameter 对象
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public static SaLoginParameter createSaLoginParameter() {
|
||||
return stpLogic.createSaLoginParameter();
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 过期方法 -------------------
|
||||
|
||||
/**
|
||||
* <h2>请更换为 getLoginDeviceType </h2>
|
||||
* 返回当前会话的登录设备类型
|
||||
*
|
||||
* @return 当前令牌的登录设备类型
|
||||
*/
|
||||
@Deprecated
|
||||
public static String getLoginDevice() {
|
||||
return stpLogic.getLoginDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* <h2>请更换为 getLoginDeviceTypeByToken </h2>
|
||||
* 返回指定 token 会话的登录设备类型
|
||||
*
|
||||
* @param tokenValue 指定token
|
||||
* @return 当前令牌的登录设备类型
|
||||
*/
|
||||
@Deprecated
|
||||
public static String getLoginDeviceByToken(String tokenValue) {
|
||||
return stpLogic.getLoginDeviceByToken(tokenValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,620 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.config.SaCookieConfig;
|
||||
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.SaReplacedRange;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 在调用 `StpUtil.login()` 时的 配置参数对象,决定登录的一些细节行为 <br>
|
||||
*
|
||||
* <pre>
|
||||
* // 例如:在登录时指定 token 有效期为七天,代码如下:
|
||||
* StpUtil.login(10001, new SaLoginParameter().setTimeout(60 * 60 * 24 * 7));
|
||||
* </pre>
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.13.2
|
||||
*/
|
||||
public class SaLoginParameter {
|
||||
|
||||
// --------- 单独参数
|
||||
|
||||
/**
|
||||
* 此次登录的客户端设备类型
|
||||
*/
|
||||
private String deviceType;
|
||||
|
||||
/**
|
||||
* 此次登录的客户端设备id
|
||||
*/
|
||||
private String deviceId;
|
||||
|
||||
/**
|
||||
* 扩展信息(只在 jwt 模式下生效)
|
||||
*/
|
||||
private Map<String, Object> extraData;
|
||||
|
||||
/**
|
||||
* 预定Token(预定本次登录生成的Token值)
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 本次登录挂载到 SaTerminalInfo 的自定义扩展数据
|
||||
*/
|
||||
private Map<String, Object> terminalExtraData;
|
||||
|
||||
|
||||
// --------- 覆盖性参数
|
||||
|
||||
/**
|
||||
* 指定此次登录 token 有效期,单位:秒 (如未指定,自动取全局配置的 timeout 值)
|
||||
*/
|
||||
private long timeout;
|
||||
|
||||
/**
|
||||
* 指定此次登录 token 最低活跃频率,单位:秒(如未指定,则使用全局配置的 activeTimeout 值)
|
||||
*/
|
||||
private Long activeTimeout;
|
||||
|
||||
/**
|
||||
* 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||
*/
|
||||
private Boolean isConcurrent;
|
||||
|
||||
/**
|
||||
* 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
|
||||
*/
|
||||
private Boolean isShare;
|
||||
|
||||
/**
|
||||
* 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置项才有意义)
|
||||
*/
|
||||
private int maxLoginCount;
|
||||
|
||||
/**
|
||||
* 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用)
|
||||
*/
|
||||
private int maxTryTimes;
|
||||
|
||||
/**
|
||||
* 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
*/
|
||||
private Boolean isLastingCookie;
|
||||
|
||||
/**
|
||||
* 是否在登录后将 Token 写入到响应头
|
||||
*/
|
||||
private Boolean isWriteHeader;
|
||||
|
||||
/**
|
||||
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
|
||||
*/
|
||||
private SaReplacedRange replacedRange;
|
||||
|
||||
/**
|
||||
* 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
|
||||
*/
|
||||
private SaLogoutMode overflowLogoutMode;
|
||||
|
||||
/**
|
||||
* 在登录时,是否立即创建对应的 Token-Session (true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建)
|
||||
*/
|
||||
private Boolean rightNowCreateTokenSession;
|
||||
|
||||
/**
|
||||
* Cookie 配置对象
|
||||
*/
|
||||
public SaCookieConfig cookie = new SaCookieConfig();
|
||||
|
||||
|
||||
// ------ 附加方法
|
||||
|
||||
public SaLoginParameter() {
|
||||
this(SaManager.getConfig());
|
||||
}
|
||||
public SaLoginParameter(SaTokenConfig config) {
|
||||
setDefaultValues(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 SaTokenConfig 对象初始化默认值
|
||||
*
|
||||
* @param config 使用的配置对象
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setDefaultValues(SaTokenConfig config) {
|
||||
this.deviceType = SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE;
|
||||
this.timeout = config.getTimeout();
|
||||
this.isConcurrent = config.getIsConcurrent();
|
||||
this.isShare = config.getIsShare();
|
||||
this.maxLoginCount = config.getMaxLoginCount();
|
||||
this.maxTryTimes = config.getMaxTryTimes();
|
||||
this.isLastingCookie = config.getIsLastingCookie();
|
||||
this.isWriteHeader = config.getIsWriteHeader();
|
||||
this.replacedRange = config.getReplacedRange();
|
||||
this.overflowLogoutMode = config.getOverflowLogoutMode();
|
||||
this.rightNowCreateTokenSession = config.getRightNowCreateTokenSession();
|
||||
|
||||
this.setupCookieConfig(cookie -> {
|
||||
SaCookieConfig gCookie = config.getCookie();
|
||||
cookie.setDomain(gCookie.getDomain());
|
||||
cookie.setPath(gCookie.getPath());
|
||||
cookie.setSecure(gCookie.getSecure());
|
||||
cookie.setHttpOnly(gCookie.getHttpOnly());
|
||||
cookie.setSameSite(gCookie.getSameSite());
|
||||
cookie.setExtraAttrs(new LinkedHashMap<>(gCookie.getExtraAttrs()));
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入扩展数据(只在jwt模式下生效)
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setExtra(String key, Object value) {
|
||||
if(this.extraData == null) {
|
||||
this.extraData = new LinkedHashMap<>();
|
||||
}
|
||||
this.extraData.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展数据(只在jwt模式下生效)
|
||||
* @param key 键
|
||||
* @return 扩展数据的值
|
||||
*/
|
||||
public Object getExtra(String key) {
|
||||
if(this.extraData == null) {
|
||||
return null;
|
||||
}
|
||||
return this.extraData.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否设置了扩展数据(只在jwt模式下生效)
|
||||
* @return /
|
||||
*/
|
||||
public boolean haveExtraData() {
|
||||
return extraData != null && !extraData.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入本次登录挂载到 SaTerminalInfo 的自定义扩展数据
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setTerminalExtra(String key, Object value) {
|
||||
if(this.terminalExtraData == null) {
|
||||
this.terminalExtraData = new LinkedHashMap<>();
|
||||
}
|
||||
this.terminalExtraData.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本次登录挂载到 SaTerminalInfo 的自定义扩展数据
|
||||
* @param key 键
|
||||
* @return 扩展数据的值
|
||||
*/
|
||||
public Object getTerminalExtra(String key) {
|
||||
if(this.terminalExtraData == null) {
|
||||
return null;
|
||||
}
|
||||
return this.terminalExtraData.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否设置了本次登录挂载到 SaTerminalInfo 的自定义扩展数据
|
||||
* @return /
|
||||
*/
|
||||
public boolean haveTerminalExtraData() {
|
||||
return terminalExtraData != null && !terminalExtraData.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算 Cookie 时长
|
||||
* @return /
|
||||
*/
|
||||
public int getCookieTimeout() {
|
||||
if( ! getIsLastingCookie()) {
|
||||
return -1;
|
||||
}
|
||||
long _timeout = getTimeout();
|
||||
if(_timeout == SaTokenDao.NEVER_EXPIRE || _timeout > Integer.MAX_VALUE) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
return (int)_timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法获取一个 SaLoginParameter 对象
|
||||
* @return SaLoginParameter 对象
|
||||
*/
|
||||
public static SaLoginParameter create() {
|
||||
return new SaLoginParameter(SaManager.getConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 Cookie 配置项
|
||||
* @param fun /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setupCookieConfig(SaParamFunction<SaCookieConfig> fun) {
|
||||
fun.run(this.cookie);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---------------- get set
|
||||
|
||||
/**
|
||||
* @return 此次登录的客户端设备类型
|
||||
*/
|
||||
public String getDeviceType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param deviceType 此次登录的客户端设备类型
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setDeviceType(String deviceType) {
|
||||
this.deviceType = deviceType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 此次登录的客户端设备id
|
||||
*
|
||||
* @return deviceId 此次登录的客户端设备id
|
||||
*/
|
||||
public String getDeviceId() {
|
||||
return this.deviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 此次登录的客户端设备id
|
||||
*
|
||||
* @param deviceId 此次登录的客户端设备id
|
||||
*/
|
||||
public SaLoginParameter setDeviceId(String deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
|
||||
*
|
||||
* @return replacedMode 顶人下线的范围
|
||||
*/
|
||||
public SaReplacedRange getReplacedRange() {
|
||||
return this.replacedRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
|
||||
*
|
||||
* @param replacedRange /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setReplacedRange(SaReplacedRange replacedRange) {
|
||||
this.replacedRange = replacedRange;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
|
||||
*
|
||||
* @return overflowLogoutMode /
|
||||
*/
|
||||
public SaLogoutMode getOverflowLogoutMode() {
|
||||
return this.overflowLogoutMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
|
||||
*
|
||||
* @param overflowLogoutMode /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setOverflowLogoutMode(SaLogoutMode overflowLogoutMode) {
|
||||
this.overflowLogoutMode = overflowLogoutMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
*/
|
||||
public Boolean getIsLastingCookie() {
|
||||
return isLastingCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isLastingCookie 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setIsLastingCookie(Boolean isLastingCookie) {
|
||||
this.isLastingCookie = isLastingCookie;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 指定此次登录 token 有效期,单位:秒
|
||||
*/
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timeout 指定此次登录 token 有效期,单位:秒 (如未指定,自动取全局配置的 timeout 值)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 此次登录 token 最低活跃频率,单位:秒(如未指定,则使用全局配置的 activeTimeout 值)
|
||||
*/
|
||||
public Long getActiveTimeout() {
|
||||
return activeTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param activeTimeout 指定此次登录 token 最低活跃频率,单位:秒(如未指定,则使用全局配置的 activeTimeout 值)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setActiveTimeout(long activeTimeout) {
|
||||
this.activeTimeout = activeTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||
*/
|
||||
public Boolean getIsConcurrent() {
|
||||
return isConcurrent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isConcurrent 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setIsConcurrent(Boolean isConcurrent) {
|
||||
this.isConcurrent = isConcurrent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个token, 为 false 时每次登录新建一个 token)
|
||||
*/
|
||||
public Boolean getIsShare() {
|
||||
return isShare;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isShare 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个token, 为 false 时每次登录新建一个 token)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setIsShare(Boolean isShare) {
|
||||
this.isShare = isShare;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置项才有意义)
|
||||
*/
|
||||
public int getMaxLoginCount() {
|
||||
return maxLoginCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxLoginCount 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置项才有意义)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setMaxLoginCount(int maxLoginCount) {
|
||||
this.maxLoginCount = maxLoginCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用)
|
||||
*/
|
||||
public int getMaxTryTimes() {
|
||||
return maxTryTimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxTryTimes 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setMaxTryTimes(int maxTryTimes) {
|
||||
this.maxTryTimes = maxTryTimes;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 扩展信息(只在jwt模式下生效)
|
||||
*/
|
||||
public Map<String, Object> getExtraData() {
|
||||
return extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param extraData 扩展信息(只在jwt模式下生效)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setExtraData(Map<String, Object> extraData) {
|
||||
this.extraData = extraData;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 预定Token(预定本次登录生成的Token值)
|
||||
*/
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param token 预定Token(预定本次登录生成的Token值)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setToken(String token) {
|
||||
this.token = token;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否在登录后将 Token 写入到响应头
|
||||
*/
|
||||
public Boolean getIsWriteHeader() {
|
||||
return isWriteHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isWriteHeader 是否在登录后将 Token 写入到响应头
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setIsWriteHeader(Boolean isWriteHeader) {
|
||||
this.isWriteHeader = isWriteHeader;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 本次登录挂载到 SaTerminalInfo 的自定义扩展数据
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public Map<String, Object> getTerminalExtraData() {
|
||||
return this.terminalExtraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 本次登录挂载到 SaTerminalInfo 的自定义扩展数据
|
||||
*
|
||||
* @param terminalExtraData /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setTerminalExtraData(Map<String, Object> terminalExtraData) {
|
||||
this.terminalExtraData = terminalExtraData;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 在登录时,是否立即创建对应的 Token-Session (true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public Boolean getRightNowCreateTokenSession() {
|
||||
return this.rightNowCreateTokenSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 在登录时,是否立即创建对应的 Token-Session (true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建)
|
||||
*
|
||||
* @param rightNowCreateTokenSession /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setRightNowCreateTokenSession(Boolean rightNowCreateTokenSession) {
|
||||
this.rightNowCreateTokenSession = rightNowCreateTokenSession;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Cookie 配置对象
|
||||
*/
|
||||
public SaCookieConfig getCookie() {
|
||||
return cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cookie Cookie 配置对象
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginParameter setCookie(SaCookieConfig cookie) {
|
||||
this.cookie = cookie;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* toString
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaLoginParameter ["
|
||||
+ "deviceType=" + deviceType
|
||||
+ ", deviceId=" + deviceId
|
||||
+ ", replacedRange=" + replacedRange
|
||||
+ ", overflowLogoutMode=" + overflowLogoutMode
|
||||
+ ", isLastingCookie=" + isLastingCookie
|
||||
+ ", timeout=" + timeout
|
||||
+ ", activeTimeout=" + activeTimeout
|
||||
+ ", isConcurrent=" + isConcurrent
|
||||
+ ", isShare=" + isShare
|
||||
+ ", maxLoginCount=" + maxLoginCount
|
||||
+ ", maxTryTimes=" + maxTryTimes
|
||||
+ ", extraData=" + extraData
|
||||
+ ", token=" + token
|
||||
+ ", isWriteHeader=" + isWriteHeader
|
||||
+ ", terminalTag=" + terminalExtraData
|
||||
+ ", rightNowCreateTokenSession=" + rightNowCreateTokenSession
|
||||
+ ", cookie=" + cookie
|
||||
+ "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* <h2> 请更换为 getDeviceType </h2>
|
||||
* @return 此次登录的客户端设备类型
|
||||
*/
|
||||
@Deprecated
|
||||
public String getDevice() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* <h2> 请更换为 setDeviceType </h2>
|
||||
* @param device 此次登录的客户端设备类型
|
||||
* @return 对象自身
|
||||
*/
|
||||
@Deprecated
|
||||
public SaLoginParameter setDevice(String device) {
|
||||
this.deviceType = device;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode;
|
||||
import cn.dev33.satoken.stp.parameter.enums.SaLogoutRange;
|
||||
|
||||
/**
|
||||
* 在会话注销时的 配置参数对象,决定注销时的一些细节行为 <br>
|
||||
*
|
||||
* <pre>
|
||||
* // 例如:
|
||||
* StpUtil.logout(10001, new SaLogoutParameter());
|
||||
* </pre>
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class SaLogoutParameter {
|
||||
|
||||
// --------- 单独参数
|
||||
|
||||
/**
|
||||
* 需要注销的设备类型 (如果不指定,则默认注销所有客户端)
|
||||
*/
|
||||
private String deviceType;
|
||||
|
||||
/**
|
||||
* 注销类型 (LOGOUT=注销下线、KICKOUT=踢人下线,REPLACED=顶人下线)
|
||||
*/
|
||||
private SaLogoutMode mode = SaLogoutMode.LOGOUT;
|
||||
|
||||
|
||||
// --------- 覆盖性参数
|
||||
|
||||
/**
|
||||
* 注销范围 (TOKEN=只注销当前 token 的会话,ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话)
|
||||
* <br/> (此参数只在调用 StpUtil.logout() 时有效)
|
||||
*/
|
||||
private SaLogoutRange range;
|
||||
|
||||
/**
|
||||
* 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API)
|
||||
* <br/> (此参数只在调用 StpUtil.[logout/kickout/replaced]ByTokenValue("token") 时有效)
|
||||
*/
|
||||
private Boolean isKeepFreezeOps;
|
||||
|
||||
/**
|
||||
* 在注销 token 后,是否保留其对应的 Token-Session
|
||||
*/
|
||||
private Boolean isKeepTokenSession;
|
||||
|
||||
|
||||
// ------ 附加方法
|
||||
|
||||
public SaLogoutParameter() {
|
||||
this(SaManager.getConfig());
|
||||
}
|
||||
public SaLogoutParameter(SaTokenConfig config) {
|
||||
setDefaultValues(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 SaTokenConfig 对象初始化默认值
|
||||
*
|
||||
* @param config 使用的配置对象
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLogoutParameter setDefaultValues(SaTokenConfig config) {
|
||||
this.range = config.getLogoutRange();
|
||||
this.isKeepFreezeOps = config.getIsLogoutKeepFreezeOps();
|
||||
this.isKeepTokenSession = config.getIsLogoutKeepTokenSession();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法获取一个 SaLoginParameter 对象
|
||||
* @return SaLoginParameter 对象
|
||||
*/
|
||||
public static SaLogoutParameter create() {
|
||||
return new SaLogoutParameter(SaManager.getConfig());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---------------- get set
|
||||
|
||||
/**
|
||||
* @return 在注销 token 后,是否保留其对应的 Token-Session
|
||||
*/
|
||||
public Boolean getIsKeepTokenSession() {
|
||||
return isKeepTokenSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isKeepTokenSession 在注销 token 后,是否保留其对应的 Token-Session
|
||||
*
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLogoutParameter setIsKeepTokenSession(Boolean isKeepTokenSession) {
|
||||
this.isKeepTokenSession = isKeepTokenSession;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API)
|
||||
* <br/> (此参数只在调用 StpUtil.[logout/kickout/replaced]ByTokenValue("token") 时有效)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public Boolean getIsKeepFreezeOps() {
|
||||
return this.isKeepFreezeOps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API)
|
||||
* <br/> (此参数只在调用 StpUtil.[logout/kickout/replaced]ByTokenValue("token") 时有效)
|
||||
*
|
||||
* @param isKeepFreezeOps /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLogoutParameter setIsKeepFreezeOps(Boolean isKeepFreezeOps) {
|
||||
this.isKeepFreezeOps = isKeepFreezeOps;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 需要注销的设备类型 (如果不指定,则默认注销所有客户端)
|
||||
*
|
||||
* @return deviceType /
|
||||
*/
|
||||
public String getDeviceType() {
|
||||
return this.deviceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 需要注销的设备类型 (如果不指定,则默认注销所有客户端)
|
||||
*
|
||||
* @param deviceType /
|
||||
* @return /
|
||||
*/
|
||||
public SaLogoutParameter setDeviceType(String deviceType) {
|
||||
this.deviceType = deviceType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销类型 (LOGOUT=注销下线、KICKOUT=踢人下线,REPLACED=顶人下线)
|
||||
*
|
||||
* @return logoutMode 注销类型
|
||||
*/
|
||||
public SaLogoutMode getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销类型 (LOGOUT=注销下线、KICKOUT=踢人下线,REPLACED=顶人下线)
|
||||
*
|
||||
* @param mode 注销类型
|
||||
* @return /
|
||||
*/
|
||||
public SaLogoutParameter setMode(SaLogoutMode mode) {
|
||||
this.mode = mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销范围 (TOKEN=只注销当前 token 的会话,ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话)
|
||||
* <br/> (此参数只在调用 StpUtil.logout() 时有效)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public SaLogoutRange getRange() {
|
||||
return this.range;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销范围 (TOKEN=只注销当前 token 的会话,ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话)
|
||||
* <br/> (此参数只在调用 StpUtil.logout() 时有效)
|
||||
*
|
||||
* @param range /
|
||||
* @return /
|
||||
*/
|
||||
public SaLogoutParameter setRange(SaLogoutRange range) {
|
||||
this.range = range;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* toString
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaLoginParameter ["
|
||||
+ "deviceType=" + deviceType
|
||||
+ ", isKeepTokenSession=" + isKeepTokenSession
|
||||
+ ", isKeepFreezeOps=" + isKeepFreezeOps
|
||||
+ ", mode=" + mode
|
||||
+ ", range=" + range
|
||||
+ "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* SaLogoutMode: 注销模式
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public enum SaLogoutMode {
|
||||
|
||||
/**
|
||||
* 注销下线
|
||||
*/
|
||||
LOGOUT,
|
||||
|
||||
/**
|
||||
* 踢人下线
|
||||
*/
|
||||
KICKOUT,
|
||||
|
||||
/**
|
||||
* 顶人下线
|
||||
*/
|
||||
REPLACED;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* SaLogoutMode: 注销范围
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public enum SaLogoutRange {
|
||||
|
||||
/**
|
||||
* token 范围:只注销提供的 token 指向的会话
|
||||
*/
|
||||
TOKEN,
|
||||
|
||||
/**
|
||||
* 账号范围:注销 token 指向的 loginId 会话
|
||||
*/
|
||||
ACCOUNT
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 顶人下线的范围
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public enum SaReplacedRange {
|
||||
|
||||
/**
|
||||
* 当前指定的设备类型端
|
||||
*/
|
||||
CURR_DEVICE_TYPE,
|
||||
|
||||
/**
|
||||
* 所有设备类型端
|
||||
*/
|
||||
ALL_DEVICE_TYPE
|
||||
|
||||
}
|
||||
@@ -65,6 +65,8 @@ public final class SaAnnotationStrategy {
|
||||
annotationHandlerMap.put(SaCheckHttpBasic.class, new SaCheckHttpBasicHandler());
|
||||
annotationHandlerMap.put(SaCheckHttpDigest.class, new SaCheckHttpDigestHandler());
|
||||
annotationHandlerMap.put(SaCheckOr.class, new SaCheckOrHandler());
|
||||
annotationHandlerMap.put(SaCheckSign.class, new SaCheckSignHandler());
|
||||
annotationHandlerMap.put(SaCheckApiKey.class, new SaCheckApiKeyHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user