Commit f6601ab6390a9a36facec8df78cecb2ccfdf557d
1 parent
4d88cfdf
171 fix(protection): 修复HTTP接口签名 API重复请求问题
Showing
2 changed files
with
14 additions
and
8 deletions
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) { |