From a72ba8379b4a49a8a3219a4759f91f9d41babb28 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 30 Apr 2023 17:54:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BE=AA=E7=8E=AF=E7=94=9F?= =?UTF-8?q?=E6=88=90=20token=20=E7=9A=84=E7=AE=97=E6=B3=95=EF=BC=8C?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E7=A1=AE=E4=BF=9D=20Token=20=E7=9A=84?= =?UTF-8?q?=E5=94=AF=E4=B8=80=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev33/satoken/config/SaTokenConfig.java | 86 +++++-------------- .../fun/SaGenerateUniqueTokenFunction.java | 31 +++++++ .../java/cn/dev33/satoken/stp/StpLogic.java | 33 ++++++- .../cn/dev33/satoken/strategy/SaStrategy.java | 75 +++++++++++----- .../java/cn/dev33/satoken/util/SaFoxUtil.java | 14 +-- .../java/com/pj/SaTokenDemoApplication.java | 3 +- .../main/java/com/pj/test/TestController.java | 2 +- .../src/main/resources/application.yml | 2 +- 8 files changed, 146 insertions(+), 100 deletions(-) create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/fun/SaGenerateUniqueTokenFunction.java diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java b/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java index be77d923..e0ff46f9 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java @@ -39,9 +39,13 @@ public class SaTokenConfig implements Serializable { */ private int maxLoginCount = 12; + /** 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用) */ + private int maxTryTimes = 12; + /** 是否尝试从请求体里读取token */ private Boolean isReadBody = true; + /** 是否尝试从header里读取token */ private Boolean isReadHeader = true; @@ -205,6 +209,22 @@ public class SaTokenConfig implements Serializable { return this; } + /** + * @return 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用) + */ + public int getMaxTryTimes() { + return maxTryTimes; + } + + /** + * @param maxTryTimes 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用) + * @return 对象自身 + */ + public SaTokenConfig setMaxTryTimes(int maxTryTimes) { + this.maxTryTimes = maxTryTimes; + return this; + } + /** * @return 是否尝试从请求体里读取token */ @@ -411,7 +431,7 @@ public class SaTokenConfig implements Serializable { * @param logLevelInt 日志等级 int 值(1=trace、2=debug、3=info、4=warn、5=error、6=fatal) * @return 对象自身 */ - public SaTokenConfig setLogLeveInt(int logLevelInt) { + public SaTokenConfig setLogLevelInt(int logLevelInt) { this.logLevelInt = logLevelInt; this.logLevel = SaFoxUtil.translateLogLevelToString(logLevelInt); return this; @@ -521,7 +541,8 @@ public class SaTokenConfig implements Serializable { + ", activityTimeout=" + activityTimeout + ", isConcurrent=" + isConcurrent + ", isShare=" + isShare - + ", maxLoginCount=" + maxLoginCount + + ", maxLoginCount=" + maxLoginCount + + ", maxTryTimes=" + maxTryTimes + ", isReadBody=" + isReadBody + ", isReadHeader=" + isReadHeader + ", isReadCookie=" + isReadCookie @@ -544,65 +565,4 @@ public class SaTokenConfig implements Serializable { + "]"; } - - /** - *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 getIsReadHeader() ,使用方式保持不变

- * @return 是否尝试从header里读取token - */ - @Deprecated - public Boolean getIsReadHead() { - return isReadHeader; - } - - /** - *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 setIsReadHeader() ,使用方式保持不变

- * @param isReadHead 是否尝试从header里读取token - * @return 对象自身 - */ - @Deprecated - public SaTokenConfig setIsReadHead(Boolean isReadHead) { - this.isReadHeader = isReadHead; - return this; - } - - /** - *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 getSameTokenTimeout() ,使用方式保持不变

- * @return Id-Token的有效期 (单位: 秒) - */ - @Deprecated - public long getIdTokenTimeout() { - return sameTokenTimeout; - } - - /** - *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 setSameTokenTimeout() ,使用方式保持不变

- * @param idTokenTimeout Id-Token的有效期 (单位: 秒) - * @return 对象自身 - */ - @Deprecated - public SaTokenConfig setIdTokenTimeout(long idTokenTimeout) { - this.sameTokenTimeout = idTokenTimeout; - return this; - } - - /** - *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 getCheckSameToken() ,使用方式保持不变

- * @return 是否校验Id-Token(部分rpc插件有效) - */ - @Deprecated - public Boolean getCheckIdToken() { - return checkSameToken; - } - - /** - *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 setCheckSameToken() ,使用方式保持不变

- * @param checkIdToken 是否校验Id-Token(部分rpc插件有效) - * @return 对象自身 - */ - @Deprecated - public SaTokenConfig setCheckIdToken(Boolean checkIdToken) { - this.checkSameToken = checkIdToken; - return this; - } - } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/fun/SaGenerateUniqueTokenFunction.java b/sa-token-core/src/main/java/cn/dev33/satoken/fun/SaGenerateUniqueTokenFunction.java new file mode 100644 index 00000000..76f4cc6d --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/fun/SaGenerateUniqueTokenFunction.java @@ -0,0 +1,31 @@ +package cn.dev33.satoken.fun; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * 生成唯一式 token 的方法签名 + * + * @author click33 + * @since 2023/4/30 + */ +@FunctionalInterface +public interface SaGenerateUniqueTokenFunction { + + /** + * 封装 token 生成、校验的代码,生成唯一式 token + * + * @param elementName 要生成的元素名称,方便抛出异常时组织提示信息 + * @param maxTryTimes 最大尝试次数 + * @param createTokenFunction 创建 token 的函数 + * @param checkTokenFunction 校验 token 是否唯一的函数(返回 true 表示唯一,可用) + * @return 最终生成的唯一式 token + */ + public String execute( + String elementName, + int maxTryTimes, + Supplier createTokenFunction, + Function checkTokenFunction + ); + +} \ No newline at end of file diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java index 60f6d355..b31c8fce 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java @@ -421,8 +421,17 @@ public class StpLogic { } } - // 如果代码走到此处,说明未能成功复用旧Token,需要新建Token - return createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout(), loginModel.getExtraData()); + // 如果代码走到此处,说明未能成功复用旧Token,需要新建Token + return SaStrategy.me.generateUniqueToken.execute( + "token", + getConfigOfMaxTryTimes(), + () -> { + return createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout(), loginModel.getExtraData()); + }, + tokenValue -> { + return getLoginIdNotHandle(tokenValue) == null; + } + ); } // --- 注销 @@ -1036,7 +1045,17 @@ public class StpLogic { */ if(isCreate) { // 随机创建一个 Token - tokenValue = createTokenValue(null, null, getConfig().getTimeout(), null); + tokenValue = SaStrategy.me.generateUniqueToken.execute( + "token", + getConfigOfMaxTryTimes(), + () -> { + return createTokenValue(null, null, getConfig().getTimeout(), null); + }, + token -> { + return getTokenSessionByToken(token, false) == null; + } + ); + // 写入 [最后操作时间] setLastActivityToNow(tokenValue); // 在当前上下文写入此 TokenValue @@ -2305,6 +2324,14 @@ public class StpLogic { return (int) timeout; } + /** + * 返回全局配置的 maxTryTimes 值,在每次创建 token 时,对其唯一性测试的最高次数(-1=不测试) + * @return / + */ + public int getConfigOfMaxTryTimes() { + return getConfig().getMaxTryTimes(); + } + /** * 返回持久化对象 diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaStrategy.java b/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaStrategy.java index 56186e97..4b0095dd 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaStrategy.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaStrategy.java @@ -1,5 +1,14 @@ package cn.dev33.satoken.strategy; +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.annotation.*; +import cn.dev33.satoken.basic.SaBasicUtil; +import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.fun.SaGenerateUniqueTokenFunction; +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.util.SaFoxUtil; +import cn.dev33.satoken.util.SaTokenConsts; + import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; @@ -8,18 +17,7 @@ import java.util.UUID; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; - -import cn.dev33.satoken.SaManager; -import cn.dev33.satoken.annotation.SaCheckBasic; -import cn.dev33.satoken.annotation.SaCheckDisable; -import cn.dev33.satoken.annotation.SaCheckLogin; -import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.dev33.satoken.annotation.SaCheckRole; -import cn.dev33.satoken.annotation.SaCheckSafe; -import cn.dev33.satoken.basic.SaBasicUtil; -import cn.dev33.satoken.session.SaSession; -import cn.dev33.satoken.util.SaFoxUtil; -import cn.dev33.satoken.util.SaTokenConsts; +import java.util.function.Supplier; /** * Sa-Token 策略对象 @@ -47,9 +45,7 @@ public final class SaStrategy { */ public static final SaStrategy me = new SaStrategy(); - // - // 所有策略 - // + // ----------------------- 所有策略 /** * 创建 Token 的策略 @@ -194,11 +190,38 @@ public final class SaStrategy { me.getAnnotation.apply(method.getDeclaringClass(), annotationClass) != null; }; + /** + * 生成唯一式 token 的算法 + *

参数 [元素名称, 最大尝试次数, 创建 token 函数, 检查 token 函数] + */ + public SaGenerateUniqueTokenFunction generateUniqueToken = (elementName, maxTryTimes, createTokenFunction, checkTokenFunction) -> { - // - // 重写策略 set连缀风格 - // - + // 为方便叙述,以下代码注释均假设在处理生成 token 的场景,但实际上本方法也可能被用于生成 code、ticket 等 + + // 循环生成 + for (int i = 1; ; i++) { + // 生成 token + String token = createTokenFunction.get(); + + // 如果 maxTryTimes == -1,表示不做唯一性验证,直接返回 + if (maxTryTimes == -1) { + return token; + } + + // 如果 token 在DB库查询不到数据,说明是个可用的全新 token,直接返回 + if (checkTokenFunction.apply(token)) { + return token; + } + + // 如果已经循环了 maxTryTimes 次,仍然没有创建出可用的 token,那么抛出异常 + if (i >= maxTryTimes) { + throw new SaTokenException(elementName + " 生成失败,已尝试" + i + "次,生成算法过于简单或资源池已耗尽"); + } + } + }; + + + // ----------------------- 重写策略 set连缀风格 /** * 重写创建 Token 的策略 @@ -276,5 +299,17 @@ public final class SaStrategy { this.isAnnotationPresent = isAnnotationPresent; return this; } - + + /** + * 生成唯一式 token 的算法 + *

参数 [元素名称, 最大尝试次数, 创建 token 函数, 检查 token 函数] + * + * @param generateUniqueToken / + * @return 对象自身 + */ + public SaStrategy setGenerateUniqueToken(SaGenerateUniqueTokenFunction generateUniqueToken) { + this.generateUniqueToken = generateUniqueToken; + return this; + } + } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java index 40edc4d0..4dc0509f 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java @@ -1,5 +1,8 @@ package cn.dev33.satoken.util; +import cn.dev33.satoken.error.SaErrorCode; +import cn.dev33.satoken.exception.SaTokenException; + import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; @@ -8,19 +11,10 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.Iterator; -import java.util.List; +import java.util.*; import java.util.concurrent.ThreadLocalRandom; import java.util.regex.Pattern; -import cn.dev33.satoken.error.SaErrorCode; -import cn.dev33.satoken.exception.SaTokenException; - /** * Sa-Token 内部工具类 * diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java index 4f5e4467..7c01ff54 100644 --- a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java @@ -1,10 +1,9 @@ package com.pj; +import cn.dev33.satoken.SaManager; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import cn.dev33.satoken.SaManager; - /** * Sa-Token整合SpringBoot 示例,整合redis * @author kong diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java index d0aa2580..308bcaa8 100644 --- a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java @@ -17,7 +17,7 @@ public class TestController { // 测试 浏览器访问: http://localhost:8081/test/test @RequestMapping("test") public SaResult test() { - System.out.println("------------进来了"); + System.out.println("------------进来了"); return SaResult.ok(); } diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml index 3a41ae12..fdb03d91 100644 --- a/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml @@ -18,7 +18,7 @@ sa-token: token-style: uuid # 是否输出操作日志 is-log: true - + spring: # redis配置 redis: