fix(protection): 修复HTTP接口签名 API重复请求问题

- 在 ApiSignatureAspect 中添加了对重复请求的检查逻辑
- 修改 ApiSignatureRedisDAO 中的 setNonce 方法,使用 setIfAbsent 代替 set
- 优化了日志记录,增加了重复请求的相关信息
This commit is contained in:
1351515658@qq.com
2025-02-24 19:33:12 +08:00
parent c2de5d9c8c
commit e9ae4196e6
2 changed files with 13 additions and 8 deletions

View File

@@ -6,6 +6,7 @@ import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.crypto.digest.DigestUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature; import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature;
import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO; import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO;
@@ -69,13 +70,17 @@ public class ApiSignatureAspect {
// 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 // 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2
String nonce = request.getHeader(signature.nonce()); String nonce = request.getHeader(signature.nonce());
signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit()); if (!signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit())) {
String timestamp = request.getHeader(signature.timestamp());
log.info("[verifySignature][appId({}) timestamp({}) nonce({}) sign({}) 存在重复请求]", appId, timestamp, nonce, clientSignature);
throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), "存在重复请求");
}
return true; return true;
} }
/** /**
* 校验请求头加签参数 * 校验请求头加签参数
* * <p>
* 1. appId 是否为空 * 1. appId 是否为空
* 2. timestamp 是否为空,请求是否已经超时,默认 10 分钟 * 2. timestamp 是否为空,请求是否已经超时,默认 10 分钟
* 3. nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了 * 3. nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了
@@ -118,7 +123,7 @@ public class ApiSignatureAspect {
/** /**
* 构建签名字符串 * 构建签名字符串
* * <p>
* 格式为 = 请求参数 + 请求体 + 请求头 + 密钥 * 格式为 = 请求参数 + 请求体 + 请求头 + 密钥
* *
* @param signature signature * @param signature signature
@@ -139,7 +144,7 @@ public class ApiSignatureAspect {
/** /**
* 获取请求头加签参数 Map * 获取请求头加签参数 Map
* *
* @param request 请求 * @param request 请求
* @param signature 签名注解 * @param signature 签名注解
* @return signature params * @return signature params
*/ */

View File

@@ -17,7 +17,7 @@ public class ApiSignatureRedisDAO {
/** /**
* 验签随机数 * 验签随机数
* * <p>
* KEY 格式signature_nonce:%s // 参数为 随机数 * KEY 格式signature_nonce:%s // 参数为 随机数
* VALUE 格式String * VALUE 格式String
* 过期时间:不固定 * 过期时间:不固定
@@ -26,7 +26,7 @@ public class ApiSignatureRedisDAO {
/** /**
* 签名密钥 * 签名密钥
* * <p>
* HASH 结构 * HASH 结构
* KEY 格式:%s // 参数为 appid * KEY 格式:%s // 参数为 appid
* VALUE 格式String * VALUE 格式String
@@ -40,8 +40,8 @@ public class ApiSignatureRedisDAO {
return stringRedisTemplate.opsForValue().get(formatNonceKey(appId, nonce)); return stringRedisTemplate.opsForValue().get(formatNonceKey(appId, nonce));
} }
public void setNonce(String appId, String nonce, int time, TimeUnit timeUnit) { public Boolean setNonce(String appId, String nonce, int time, TimeUnit timeUnit) {
stringRedisTemplate.opsForValue().set(formatNonceKey(appId, nonce), "", time, timeUnit); return stringRedisTemplate.opsForValue().setIfAbsent(formatNonceKey(appId, nonce), "", time, timeUnit);
} }
private static String formatNonceKey(String appId, String nonce) { private static String formatNonceKey(String appId, String nonce) {