重构:API 接口签名所有方法均迁移至 core 核心模块。

This commit is contained in:
click33
2023-05-03 04:23:48 +08:00
parent aa2663e3f5
commit ac09cced2b
19 changed files with 661 additions and 301 deletions
@@ -1,8 +1,5 @@
package cn.dev33.satoken;
import java.util.LinkedHashMap;
import java.util.Map;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.config.SaTokenConfigFactory;
import cn.dev33.satoken.context.SaTokenContext;
@@ -19,7 +16,6 @@ import cn.dev33.satoken.log.SaLog;
import cn.dev33.satoken.log.SaLogForConsole;
import cn.dev33.satoken.same.SaSameTemplate;
import cn.dev33.satoken.sign.SaSignTemplate;
import cn.dev33.satoken.sign.SaSignTemplateDefaultImpl;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
import cn.dev33.satoken.stp.StpLogic;
@@ -28,6 +24,9 @@ import cn.dev33.satoken.temp.SaTempDefaultImpl;
import cn.dev33.satoken.temp.SaTempInterface;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 管理 Sa-Token 所有全局组件
* @author kong
@@ -209,7 +208,7 @@ public class SaManager {
if (saSignTemplate == null) {
synchronized (SaManager.class) {
if (saSignTemplate == null) {
SaManager.saSignTemplate = new SaSignTemplateDefaultImpl();
SaManager.saSignTemplate = new SaSignTemplate();
}
}
}
@@ -0,0 +1,111 @@
package cn.dev33.satoken.config;
/**
* Sa-Token API 接口签名/验签 相关配置类
*
* @author click33
* @since 2023/5/2
*/
public class SaSignConfig {
/**
* API 调用签名秘钥
*/
private String secretKey;
/**
* 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距,默认15分钟
*/
private long timestampDisparity = 1000 * 60 * 15;
/**
* 是否校验 nonce 随机字符串
*/
private Boolean isCheckNonce = true;
/**
* 获取 API 调用签名秘钥
*
* @return /
*/
public String getSecretKey() {
return this.secretKey;
}
/**
* 设置 API 调用签名秘钥
*
* @param secretKey /
* @return 对象自身
*/
public SaSignConfig setSecretKey(String secretKey) {
this.secretKey = secretKey;
return this;
}
/**
* 获取 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距,默认15分钟
*
* @return /
*/
public long getTimestampDisparity() {
return this.timestampDisparity;
}
/**
* 设置 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距,默认15分钟
*
* @param timestampDisparity /
* @return 对象自身
*/
public SaSignConfig setTimestampDisparity(long timestampDisparity) {
this.timestampDisparity = timestampDisparity;
return this;
}
/**
* 获取 是否校验 nonce 随机字符串
*
* @return /
*/
public Boolean getIsCheckNonce() {
return this.isCheckNonce;
}
/**
* 设置 是否校验 nonce 随机字符串
*
* @param isCheckNonce /
* @return 对象自身
*/
public SaSignConfig setIsCheckNonce(Boolean isCheckNonce) {
this.isCheckNonce = isCheckNonce;
return this;
}
/**
* 计算保存 nonce 时应该使用的 ttl,单位:秒
* @return /
*/
public long getSaveNonceExpire() {
// 如果 timestampDisparity >= 0,则 nonceTtl 的值等于 timestampDisparity 的值,单位转秒
if(timestampDisparity >= 0) {
return timestampDisparity / 1000;
}
// 否则,nonceTtl 的值为 24 小时
else {
return 60 * 60 * 24;
}
}
@Override
public String toString() {
return "SaSignConfig ["
+ "secretKey=" + secretKey
+ ", timestampDisparity=" + timestampDisparity
+ ", isCheckNonce=" + isCheckNonce
+ "]";
}
}
@@ -1,9 +1,9 @@
package cn.dev33.satoken.config;
import java.io.Serializable;
import cn.dev33.satoken.util.SaFoxUtil;
import java.io.Serializable;
/**
* Sa-Token 配置类 Model
* <p>
@@ -109,7 +109,12 @@ public class SaTokenConfig implements Serializable {
* Cookie配置对象
*/
public SaCookieConfig cookie = new SaCookieConfig();
/**
* API 签名配置对象
*/
public SaSignConfig sign = new SaSignConfig();
/**
* @return token名称 (同时也是cookie名称)
@@ -532,7 +537,23 @@ public class SaTokenConfig implements Serializable {
this.cookie = cookie;
return this;
}
/**
* @return API 签名全局配置对象
*/
public SaSignConfig getSign() {
return sign;
}
/**
* @param sign API 签名全局配置对象
* @return 对象自身
*/
public SaTokenConfig setSign(SaSignConfig sign) {
this.sign = sign;
return this;
}
@Override
public String toString() {
return "SaTokenConfig ["
@@ -561,7 +582,8 @@ public class SaTokenConfig implements Serializable {
+ ", currDomain=" + currDomain
+ ", sameTokenTimeout=" + sameTokenTimeout
+ ", checkSameToken=" + checkSameToken
+ ", cookie=" + cookie
+ ", cookie=" + cookie
+ ", sign=" + sign
+ "]";
}
@@ -0,0 +1,48 @@
package cn.dev33.satoken.exception;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* 一个异常:代表 API 参数签名校验失败
*
* @author kong
* @since 2023-5-3
*/
public class SaSignException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130144L;
/**
* 一个异常:代表 API 参数签名校验失败
* @param message 异常描述
*/
public SaSignException(String message) {
super(message);
}
/**
* 如果flag==true,则抛出message异常
* @param flag 标记
* @param message 异常信息
*/
public static void throwBy(boolean flag, String message) {
if(flag) {
throw new SaSignException(message);
}
}
/**
* 如果 value isEmpty,则抛出 message 异常
* @param value 值
* @param message 异常信息
*/
public static void throwByNull(Object value, String message) {
if(SaFoxUtil.isEmpty(value)) {
throw new SaSignException(message);
}
}
}
@@ -1,7 +1,10 @@
package cn.dev33.satoken.sign;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaSignConfig;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.exception.SaSignException;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.util.SaFoxUtil;
@@ -9,19 +12,74 @@ import java.util.Map;
import java.util.TreeMap;
/**
* 参数签名算法
* API 参数签名算法
*
* @author kong
* @since 2022-4-27
*/
public interface SaSignTemplate {
public class SaSignTemplate {
// ----------- 签名配置
SaSignConfig signConfig;
/**
* 获取:API 签名配置
* @return /
*/
public SaSignConfig getSignConfig() {
return signConfig;
}
/**
* 获取:API 签名配置:
* 1. 如果用户自定义了 signConfig ,则使用用户自定义的。
* 2. 否则使用全局默认配置。
* @return /
*/
public SaSignConfig getSignConfigOrGlobal() {
// 如果用户自定义了 signConfig ,则使用用户自定义的
if(signConfig != null) {
return signConfig;
}
// 否则使用全局默认配置
return SaManager.getConfig().getSign();
}
/**
* 获取:API 签名配置的秘钥
* @return /
*/
public String getSecretKey() {
return getSignConfigOrGlobal().getSecretKey();
}
/**
* 设置:API 签名配置
* @param signConfig /
*/
public SaSignTemplate setSignConfig(SaSignConfig signConfig) {
this.signConfig = signConfig;
return this;
}
// ----------- 自定义使用的参数名称 (不声明final,允许开发者自定义修改)
public static String key = "key";
public static String timestamp = "timestamp";
public static String nonce = "nonce";
public static String sign = "sign";
// ----------- 拼接参数
/**
* 将所有参数连接成一个字符串(不排序),形如:b=28a=18c=3
* @param paramsMap 参数列表
* @return 拼接出的参数字符串
*/
public default String joinParams(Map<String, ?> paramsMap) {
public String joinParams(Map<String, ?> paramsMap) {
// 按照 k1=v1&k2=v2&k3=v3 排列
StringBuilder sb = new StringBuilder();
@@ -46,7 +104,7 @@ public interface SaSignTemplate {
* @param paramsMap 参数列表
* @return 拼接出的参数字符串
*/
public default String joinParamsDictSort(Map<String, ?> paramsMap) {
public String joinParamsDictSort(Map<String, ?> paramsMap) {
// 保证字段按照字典顺序排列
if( ! (paramsMap instanceof TreeMap) ) {
paramsMap = new TreeMap<>(paramsMap);
@@ -56,62 +114,40 @@ public interface SaSignTemplate {
return joinParams(paramsMap);
}
// ----------- 创建签名
/**
* 创建签名:md5(paramsStr + keyStr)
* @param paramsMap 参数列表
* @param key 秘钥
* @param paramsMap 参数列表
* @return 签名
*/
public default String createSign(Map<String, ?> paramsMap, String key) {
SaTokenException.throwByNull(key, "参与参数签名的秘钥不可为空", SaErrorCode.CODE_12201);
public String createSign(Map<String, ?> paramsMap) {
String secretKey = getSecretKey();
SaSignException.throwByNull(secretKey, "参与参数签名的秘钥不可为空", SaErrorCode.CODE_12201);
// 如果调用者不小心传入了 sign 参数,则此处需要将 sign 参数排除在外
// 为了保证不影响原有的 paramsMap,此处需要再复制一份
if(paramsMap.containsKey("sign")) {
if(paramsMap.containsKey(sign)) {
// 为了保证不影响原有的 paramsMap,此处需要再复制一份
paramsMap = new TreeMap<>(paramsMap);
paramsMap.remove("sign");
paramsMap.remove(sign);
}
// 计算签名
String paramsStr = joinParamsDictSort(paramsMap);
String fullStr = paramsStr + "&key=" + key;
String fullStr = paramsStr + "&" + key + "=" + secretKey;
return SaSecureUtil.md5(fullStr);
}
/**
* 判断:给定的参数 + 秘钥 生成的签名是否为有效签名
* @param paramsMap 参数列表
* @param key 秘钥
* @param sign 待验证的签名
* @return 签名是否有效
*/
public default boolean isValidSign(Map<String, ?> paramsMap, String key, String sign) {
String theSign = createSign(paramsMap, key);
return theSign.equals(sign);
}
/**
* 校验:给定的参数 + 秘钥 生成的签名是否为有效签名,如果签名无效则抛出异常
* @param paramsMap 参数列表
* @param key 秘钥
* @param sign 待验证的签名
*/
public default void checkSign(Map<String, ?> paramsMap, String key, String sign) {
if( ! isValidSign(paramsMap, key, sign) ) {
throw new SaTokenException("无效签名:" + sign).setCode(SaErrorCode.CODE_12202);
}
}
/**
* 给 paramsMap 追加 timestamp、nonce、sign 三个参数
* @param paramsMap 参数列表
* @param key 秘钥
* @param paramsMap 参数列表
* @return 加工后的参数列表
*/
public default Map<String, Object> addSignParams(Map<String, Object> paramsMap, String key) {
paramsMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
paramsMap.put("nonce", SaFoxUtil.getRandomString(32));
paramsMap.put("sign", createSign(paramsMap, key));
public Map<String, Object> addSignParams(Map<String, Object> paramsMap) {
paramsMap.put(timestamp, String.valueOf(System.currentTimeMillis()));
paramsMap.put(nonce, SaFoxUtil.getRandomString(32));
paramsMap.put(sign, createSign(paramsMap));
return paramsMap;
}
@@ -119,24 +155,26 @@ public interface SaSignTemplate {
* 给 paramsMap 追加 timestamp、nonce、sign 三个参数,并转换为参数字符串,形如:
* <code>data=xxx8nonce=xxx8timestamp=xxx8sign=xxx</code>
* @param paramsMap 参数列表
* @param key 秘钥
* @return 加工后的参数列表 转化为的参数字符串
*/
public default String addSignParamsAndToString(Map<String, Object> paramsMap, String key) {
public String addSignParamsAndJoin(Map<String, Object> paramsMap) {
// 追加参数
paramsMap = addSignParams(paramsMap, key);
paramsMap = addSignParams(paramsMap);
// .
// 拼接参数
return joinParams(paramsMap);
}
// ----------- 校验签名
/**
* 判断:指定时间戳与当前时间戳的差距是否在允许的范围内
* @param timestamp 待校验的时间戳
* @param allowDisparity 允许的最大时间差(单位:ms),-1 代表不限制
* @return 是否在允许的范围内
*/
public default boolean isValidTimestamp(long timestamp, long allowDisparity) {
public boolean isValidTimestamp(long timestamp) {
long allowDisparity = getSignConfigOrGlobal().getTimestampDisparity();
long disparity = Math.abs(System.currentTimeMillis() - timestamp);
return allowDisparity == -1 || disparity <= allowDisparity;
}
@@ -144,29 +182,142 @@ public interface SaSignTemplate {
/**
* 校验:指定时间戳与当前时间戳的差距是否在允许的范围内,如果超出则抛出异常
* @param timestamp 待校验的时间戳
* @param allowDisparity 允许的最大时间差(单位:ms),-1 代表不限制
*/
public default void checkTimestamp(long timestamp, long allowDisparity) {
if( ! isValidTimestamp(timestamp, allowDisparity) ) {
throw new SaTokenException("timestamp 超出允许的范围:" + timestamp).setCode(SaErrorCode.CODE_12203);
public void checkTimestamp(long timestamp) {
if( ! isValidTimestamp(timestamp) ) {
throw new SaSignException("timestamp 超出允许的范围:" + timestamp).setCode(SaErrorCode.CODE_12203);
}
}
// ------------------ 以下为兼容旧版本的方法 ------------------
/**
* 判断:随机字符串 nonce 是否有效。
* 注意:同一 nonce 可以被多次判断有效,不会被缓存
* @param nonce 待判断的随机字符串
* @return 是否有效
*/
public boolean isValidNonce(String nonce) {
// 为空代表无效
if(SaFoxUtil.isEmpty(nonce)) {
return false;
}
// 校验此 nonce 是否已被使用过
String key = splicingNonceSaveKey(nonce);
return SaManager.getSaTokenDao().get(key) == null;
}
/**
* 请更换为 addSignParamsAndToString
* @param paramsMap 参数列表
* @param key 秘钥
* @return 加工后的参数列表 转化为的参数字符串
* 校验:随机字符串 nonce 是否有效,如果无效则抛出异常。
* 注意:同一 nonce 只可以被校验通过一次,校验后将保存在缓存中,再次校验将无法通过
* @param nonce 待校验的随机字符串
*/
@Deprecated
public default String addSignParamsToString(Map<String, Object> paramsMap, String key) {
// 追加参数
paramsMap = addSignParams(paramsMap, key);
public void checkNonce(String nonce) {
// 为空代表无效
if(SaFoxUtil.isEmpty(nonce)) {
throw new SaSignException("nonce 为空,无效");
}
// .
return joinParams(paramsMap);
// 校验此 nonce 是否已被使用过
String key = splicingNonceSaveKey(nonce);
if(SaManager.getSaTokenDao().get(key) != null) {
throw new SaSignException("此 nonce 已被使用过,不可重复使用:" + nonce);
}
// 校验通过后,将此 nonce 保存在缓存中,保证下次校验无法通过
SaManager.getSaTokenDao().set(key, nonce, getSignConfigOrGlobal().getSaveNonceExpire());
}
/**
* 判断:给定的参数 + 秘钥 生成的签名是否为有效签名
* @param paramsMap 参数列表
* @param sign 待验证的签名
* @return 签名是否有效
*/
public boolean isValidSign(Map<String, ?> paramsMap, String sign) {
String theSign = createSign(paramsMap);
return theSign.equals(sign);
}
/**
* 校验:给定的参数 + 秘钥 生成的签名是否为有效签名,如果签名无效则抛出异常
* @param paramsMap 参数列表
* @param sign 待验证的签名
*/
public void checkSign(Map<String, ?> paramsMap, String sign) {
if( ! isValidSign(paramsMap, sign) ) {
throw new SaSignException("无效签名:" + sign).setCode(SaErrorCode.CODE_12202);
}
}
/**
* 判断:参数列表中的 nonce、timestamp、sign 是否均为合法的
* @param paramMap 待校验的请求参数集合
* @return 是否合法
*/
public boolean isValidParamMap(Map<String, String> paramMap) {
// 获取必须的三个参数
String timestampValue = paramMap.get(timestamp);
String nonceValue = paramMap.get(nonce);
String signValue = paramMap.get(sign);
// 三个参数必须全部非空
SaSignException.throwByNull(timestampValue, "缺少 timestamp 字段");
SaSignException.throwByNull(nonceValue, "缺少 nonce 字段");
SaSignException.throwByNull(signValue, "缺少 sign 字段");
// 三个值的校验必须全部通过
return isValidTimestamp(Long.parseLong(timestampValue))
&& (getSignConfigOrGlobal().getIsCheckNonce() ? isValidNonce(nonceValue) : true)
&& isValidSign(paramMap, signValue);
}
/**
* 校验:参数列表中的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常
* @param paramMap 待校验的请求参数集合
*/
public void checkParamMap(Map<String, String> paramMap) {
// 获取必须的三个参数
String timestampValue = paramMap.get(timestamp);
String nonceValue = paramMap.get(nonce);
String signValue = paramMap.get(sign);
// 依次校验三个参数
checkTimestamp(Long.parseLong(timestampValue));
if(getSignConfigOrGlobal().getIsCheckNonce()) {
checkNonce(nonceValue);
}
checkSign(paramMap, signValue);
// 通过 √
}
/**
* 判断:一个请求中的 nonce、timestamp、sign 是否均为合法的
* @param request 待校验的请求对象
* @return 是否合法
*/
public boolean isValidRequest(SaRequest request) {
return isValidParamMap(request.getParamMap());
}
/**
* 校验:一个请求的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常
* @param request 待校验的请求对象
*/
public void checkRequest(SaRequest request) {
checkParamMap(request.getParamMap());
}
// ------------------- 返回相应key -------------------
/**
* 拼接key:存储 nonce 时使用的 key
* @param nonce nonce 值
* @return key
*/
public String splicingNonceSaveKey(String nonce) {
return SaManager.getConfig().getTokenName() + ":sign:nonce:" + nonce;
}
}
@@ -1,11 +0,0 @@
package cn.dev33.satoken.sign;
/**
* 参数签名算法 [默认实现类]
*
* @author kong
* @since: 2022-4-27
*/
public class SaSignTemplateDefaultImpl implements SaSignTemplate {
}
@@ -0,0 +1,159 @@
package cn.dev33.satoken.sign;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.model.SaRequest;
import java.util.Map;
/**
* API 参数签名算法 - 工具类
*
* @author kong
* @since 2022-4-27
*/
public class SaSignUtil {
// ----------- 拼接参数
/**
* 将所有参数连接成一个字符串(不排序),形如:b=28a=18c=3
* @param paramsMap 参数列表
* @return 拼接出的参数字符串
*/
public static String joinParams(Map<String, ?> paramsMap) {
return SaManager.getSaSignTemplate().joinParams(paramsMap);
}
/**
* 将所有参数按照字典顺序连接成一个字符串,形如:a=18b=28c=3
* @param paramsMap 参数列表
* @return 拼接出的参数字符串
*/
public static String joinParamsDictSort(Map<String, ?> paramsMap) {
return SaManager.getSaSignTemplate().joinParamsDictSort(paramsMap);
}
// ----------- 创建签名
/**
* 创建签名:md5(paramsStr + keyStr)
* @param paramsMap 参数列表
* @return 签名
*/
public static String createSign(Map<String, ?> paramsMap) {
return SaManager.getSaSignTemplate().createSign(paramsMap);
}
/**
* 给 paramsMap 追加 timestamp、nonce、sign 三个参数
* @param paramsMap 参数列表
* @return 加工后的参数列表
*/
public static Map<String, Object> addSignParams(Map<String, Object> paramsMap) {
return SaManager.getSaSignTemplate().addSignParams(paramsMap);
}
/**
* 给 paramsMap 追加 timestamp、nonce、sign 三个参数,并转换为参数字符串,形如:
* <code>data=xxx8nonce=xxx8timestamp=xxx8sign=xxx</code>
* @param paramsMap 参数列表
* @return 加工后的参数列表 转化为的参数字符串
*/
public static String addSignParamsAndJoin(Map<String, Object> paramsMap) {
return SaManager.getSaSignTemplate().addSignParamsAndJoin(paramsMap);
}
// ----------- 校验签名
/**
* 判断:指定时间戳与当前时间戳的差距是否在允许的范围内
* @param timestamp 待校验的时间戳
* @return 是否在允许的范围内
*/
public static boolean isValidTimestamp(long timestamp) {
return SaManager.getSaSignTemplate().isValidTimestamp(timestamp);
}
/**
* 校验:指定时间戳与当前时间戳的差距是否在允许的范围内,如果超出则抛出异常
* @param timestamp 待校验的时间戳
*/
public static void checkTimestamp(long timestamp) {
SaManager.getSaSignTemplate().checkTimestamp(timestamp);
}
/**
* 判断:随机字符串 nonce 是否有效。
* 注意:同一 nonce 可以被多次判断有效,不会被缓存
* @param nonce 待判断的随机字符串
* @return 是否有效
*/
public static boolean isValidNonce(String nonce) {
return SaManager.getSaSignTemplate().isValidNonce(nonce);
}
/**
* 校验:随机字符串 nonce 是否有效,如果无效则抛出异常。
* 注意:同一 nonce 只可以被校验通过一次,校验后将保存在缓存中,再次校验将无法通过
* @param nonce 待校验的随机字符串
*/
public static void checkNonce(String nonce) {
SaManager.getSaSignTemplate().checkNonce(nonce);
}
/**
* 判断:给定的参数 + 秘钥 生成的签名是否为有效签名
* @param paramsMap 参数列表
* @param sign 待验证的签名
* @return 签名是否有效
*/
public static boolean isValidSign(Map<String, ?> paramsMap, String sign) {
return SaManager.getSaSignTemplate().isValidSign(paramsMap, sign);
}
/**
* 校验:给定的参数 + 秘钥 生成的签名是否为有效签名,如果签名无效则抛出异常
* @param paramsMap 参数列表
* @param sign 待验证的签名
*/
public static void checkSign(Map<String, ?> paramsMap, String sign) {
SaManager.getSaSignTemplate().checkSign(paramsMap, sign);
}
/**
* 判断:参数列表中的 nonce、timestamp、sign 是否均为合法的
* @param paramMap 待校验的请求参数集合
* @return 是否合法
*/
public static boolean isValidParamMap(Map<String, String> paramMap) {
return SaManager.getSaSignTemplate().isValidParamMap(paramMap);
}
/**
* 校验:参数列表中的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常
* @param paramMap 待校验的请求参数集合
*/
public static void checkParamMap(Map<String, String> paramMap) {
SaManager.getSaSignTemplate().checkParamMap(paramMap);
}
/**
* 判断:一个请求中的 nonce、timestamp、sign 是否均为合法的
* @param request 待校验的请求对象
* @return 是否合法
*/
public static boolean isValidRequest(SaRequest request) {
return SaManager.getSaSignTemplate().isValidRequest(request);
}
/**
* 校验:一个请求的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常
* @param request 待校验的请求对象
*/
public static void checkRequest(SaRequest request) {
SaManager.getSaSignTemplate().checkRequest(request);
}
}