1
0
mirror of https://gitee.com/dromara/sa-token.git synced 2025-04-05 17:37:53 +08:00

增加循环生成 token 的算法,用于确保 Token 的唯一性

This commit is contained in:
click33 2023-04-30 17:54:22 +08:00
parent ca29da17ee
commit a72ba8379b
8 changed files with 146 additions and 100 deletions
sa-token-core/src/main/java/cn/dev33/satoken
sa-token-demo/sa-token-demo-springboot-redis/src/main

View File

@ -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=trace2=debug3=info4=warn5=error6=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 {
+ "]";
}
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 getIsReadHeader() 使用方式保持不变 </h1>
* @return 是否尝试从header里读取token
*/
@Deprecated
public Boolean getIsReadHead() {
return isReadHeader;
}
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 setIsReadHeader() 使用方式保持不变 </h1>
* @param isReadHead 是否尝试从header里读取token
* @return 对象自身
*/
@Deprecated
public SaTokenConfig setIsReadHead(Boolean isReadHead) {
this.isReadHeader = isReadHead;
return this;
}
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 getSameTokenTimeout() 使用方式保持不变 </h1>
* @return Id-Token的有效期 (单位: )
*/
@Deprecated
public long getIdTokenTimeout() {
return sameTokenTimeout;
}
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 setSameTokenTimeout() 使用方式保持不变 </h1>
* @param idTokenTimeout Id-Token的有效期 (单位: )
* @return 对象自身
*/
@Deprecated
public SaTokenConfig setIdTokenTimeout(long idTokenTimeout) {
this.sameTokenTimeout = idTokenTimeout;
return this;
}
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 getCheckSameToken() 使用方式保持不变 </h1>
* @return 是否校验Id-Token部分rpc插件有效
*/
@Deprecated
public Boolean getCheckIdToken() {
return checkSameToken;
}
/**
* <h1> 本函数设计已过时未来版本可能移除此函数请及时更换为 setCheckSameToken() 使用方式保持不变 </h1>
* @param checkIdToken 是否校验Id-Token部分rpc插件有效
* @return 对象自身
*/
@Deprecated
public SaTokenConfig setCheckIdToken(Boolean checkIdToken) {
this.checkSameToken = checkIdToken;
return this;
}
}

View File

@ -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<String> createTokenFunction,
Function<String, Boolean> checkTokenFunction
);
}

View File

@ -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();
}
/**
* 返回持久化对象

View File

@ -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 的算法
* <p> 参数 [元素名称, 最大尝试次数, 创建 token 函数, 检查 token 函数]
*/
public SaGenerateUniqueTokenFunction generateUniqueToken = (elementName, maxTryTimes, createTokenFunction, checkTokenFunction) -> {
//
// 重写策略 set连缀风格
//
// 为方便叙述以下代码注释均假设在处理生成 token 的场景但实际上本方法也可能被用于生成 codeticket
// 循环生成
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 的算法
* <p> 参数 [元素名称, 最大尝试次数, 创建 token 函数, 检查 token 函数]
*
* @param generateUniqueToken /
* @return 对象自身
*/
public SaStrategy setGenerateUniqueToken(SaGenerateUniqueTokenFunction generateUniqueToken) {
this.generateUniqueToken = generateUniqueToken;
return this;
}
}

View File

@ -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 内部工具类
*

View File

@ -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

View File

@ -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();
}

View File

@ -18,7 +18,7 @@ sa-token:
token-style: uuid
# 是否输出操作日志
is-log: true
spring:
# redis配置
redis: