Commit f6601ab6390a9a36facec8df78cecb2ccfdf557d

Authored by YunaiV
1 parent 4d88cfdf

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

yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java
... ... @@ -2,10 +2,12 @@ package cn.iocoder.yudao.framework.signature.core.aop;
2 2  
3 3 import cn.hutool.core.lang.Assert;
4 4 import cn.hutool.core.map.MapUtil;
  5 +import cn.hutool.core.util.BooleanUtil;
5 6 import cn.hutool.core.util.ObjUtil;
6 7 import cn.hutool.core.util.StrUtil;
7 8 import cn.hutool.crypto.digest.DigestUtil;
8 9 import cn.iocoder.yudao.framework.common.exception.ServiceException;
  10 +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
9 11 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
10 12 import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature;
11 13 import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO;
... ... @@ -69,13 +71,17 @@ public class ApiSignatureAspect {
69 71  
70 72 // 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 )
71 73 String nonce = request.getHeader(signature.nonce());
72   - signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit());
  74 + if (BooleanUtil.isFalse(signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit()))) {
  75 + String timestamp = request.getHeader(signature.timestamp());
  76 + log.info("[verifySignature][appId({}) timestamp({}) nonce({}) sign({}) 存在重复请求]", appId, timestamp, nonce, clientSignature);
  77 + throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), "存在重复请求");
  78 + }
73 79 return true;
74 80 }
75 81  
76 82 /**
77 83 * 校验请求头加签参数
78   - *
  84 + * <p>
79 85 * 1. appId 是否为空
80 86 * 2. timestamp 是否为空,请求是否已经超时,默认 10 分钟
81 87 * 3. nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了
... ... @@ -118,7 +124,7 @@ public class ApiSignatureAspect {
118 124  
119 125 /**
120 126 * 构建签名字符串
121   - *
  127 + * <p>
122 128 * 格式为 = 请求参数 + 请求体 + 请求头 + 密钥
123 129 *
124 130 * @param signature signature
... ... @@ -139,7 +145,7 @@ public class ApiSignatureAspect {
139 145 /**
140 146 * 获取请求头加签参数 Map
141 147 *
142   - * @param request 请求
  148 + * @param request 请求
143 149 * @param signature 签名注解
144 150 * @return signature params
145 151 */
... ...
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/ApiSignatureRedisDAO.java
... ... @@ -17,7 +17,7 @@ public class ApiSignatureRedisDAO {
17 17  
18 18 /**
19 19 * 验签随机数
20   - *
  20 + * <p>
21 21 * KEY 格式:signature_nonce:%s // 参数为 随机数
22 22 * VALUE 格式:String
23 23 * 过期时间:不固定
... ... @@ -26,7 +26,7 @@ public class ApiSignatureRedisDAO {
26 26  
27 27 /**
28 28 * 签名密钥
29   - *
  29 + * <p>
30 30 * HASH 结构
31 31 * KEY 格式:%s // 参数为 appid
32 32 * VALUE 格式:String
... ... @@ -40,8 +40,8 @@ public class ApiSignatureRedisDAO {
40 40 return stringRedisTemplate.opsForValue().get(formatNonceKey(appId, nonce));
41 41 }
42 42  
43   - public void setNonce(String appId, String nonce, int time, TimeUnit timeUnit) {
44   - stringRedisTemplate.opsForValue().set(formatNonceKey(appId, nonce), "", time, timeUnit);
  43 + public Boolean setNonce(String appId, String nonce, int time, TimeUnit timeUnit) {
  44 + return stringRedisTemplate.opsForValue().setIfAbsent(formatNonceKey(appId, nonce), "", time, timeUnit);
45 45 }
46 46  
47 47 private static String formatNonceKey(String appId, String nonce) {
... ...