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