From 5f278ac23b392d6ecd9452cc50fdfc9e77af97e8 Mon Sep 17 00:00:00 2001 From: zhougang Date: Tue, 28 May 2024 15:09:01 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{Signature.java => ApiSignature.java} | 16 ++++--- .../signature/core/aop/SignatureAspect.java | 43 ++++++------------- .../core/redis/SignatureRedisDAO.java | 3 +- ...ot.autoconfigure.AutoConfiguration.imports | 3 +- .../core/filter/CacheRequestBodyWrapper.java | 4 -- 5 files changed, 26 insertions(+), 43 deletions(-) rename yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/{Signature.java => ApiSignature.java} (75%) diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/Signature.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/ApiSignature.java similarity index 75% rename from yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/Signature.java rename to yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/ApiSignature.java index 1b7e12786..e338ae709 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/Signature.java +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/ApiSignature.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.signature.core.annotation; import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; /** @@ -14,12 +15,17 @@ import java.lang.annotation.*; @Documented @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) -public @interface Signature { +public @interface ApiSignature { /** - * 同一个请求多长时间内有效 默认 10分钟 + * 同一个请求多长时间内有效 默认 60 秒 */ - long expireTime() default 600000L; + int timeout() default 60; + + /** + * 时间单位,默认为 SECONDS 秒 + */ + TimeUnit timeUnit() default TimeUnit.SECONDS; // ========================== 签名参数 ========================== @@ -50,8 +56,4 @@ public @interface Signature { */ String sign() default "sign"; - /** - * url 客户端不需要传递,但是可以用来加签(如: /{id} 带有动态参数的 url ,如果没有动态参数可设置为 false 不进行加签) - */ - boolean urlEnable() default true; } diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java index dc1510465..a001419f8 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java @@ -1,12 +1,13 @@ package cn.iocoder.yudao.framework.signature.core.aop; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SignUtil; 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.signature.core.annotation.Signature; +import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature; import cn.iocoder.yudao.framework.signature.core.redis.SignatureRedisDAO; import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyWrapper; import jakarta.servlet.http.HttpServletRequest; @@ -15,7 +16,6 @@ import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; -import org.springframework.util.Assert; import java.nio.charset.StandardCharsets; import java.util.Map; @@ -25,7 +25,7 @@ import java.util.TreeMap; import java.util.concurrent.TimeUnit; /** - * 拦截声明了 {@link Signature} 注解的方法,实现签名 + * 拦截声明了 {@link ApiSignature} 注解的方法,实现签名 * * @author Zhougang */ @@ -37,9 +37,9 @@ public class SignatureAspect { private final SignatureRedisDAO signatureRedisDAO; @Before("@annotation(signature)") - public void beforePointCut(JoinPoint joinPoint, Signature signature) { + public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) { if (!verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) { - log.info("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(), + log.error("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(), joinPoint.getArgs()); String message = StrUtil.blankToDefault(signature.message(), GlobalErrorCodeConstants.BAD_REQUEST.getMsg()); @@ -47,25 +47,22 @@ public class SignatureAspect { } } - private boolean verifySignature(Signature signature, HttpServletRequest request) { + private boolean verifySignature(ApiSignature signature, HttpServletRequest request) { if (!verifyHeaders(signature, request)) { return false; } // 校验 appId 是否能获取到对应的 appSecret String appId = request.getHeader(signature.appId()); String appSecret = signatureRedisDAO.getAppSecret(appId); - Assert.notNull(appSecret, "找不到对应的 appSecret"); + Assert.notNull(appSecret, "[appId({})] 找不到对应的 appSecret", appId); // 请求头 SortedMap headersMap = getRequestHeaders(signature, request); - // 如:/user/{id} url 带有动态参数的情况 - String urlParams = signature.urlEnable() ? request.getServletPath() : ""; // 请求参数 String requestParams = getRequestParams(request); // 请求体 - String requestBody = getRequestBody(request); + String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : ""; // 生成服务端签名 - String serverSignature = SignUtil.signParamsSha256(headersMap, - urlParams + requestParams + requestBody + appSecret); + String serverSignature = SignUtil.signParamsSha256(headersMap, requestParams + requestBody + appSecret); // 客户端签名 String clientSignature = request.getHeader(signature.sign()); if (!StrUtil.equals(clientSignature, serverSignature)) { @@ -73,7 +70,7 @@ public class SignatureAspect { } String nonce = headersMap.get(signature.nonce()); // 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 ) - signatureRedisDAO.setNonce(nonce, signature.expireTime(), TimeUnit.MILLISECONDS); + signatureRedisDAO.setNonce(nonce, signature.timeout() * 2L, signature.timeUnit()); return true; } @@ -87,7 +84,7 @@ public class SignatureAspect { * @param signature signature * @param request request */ - private boolean verifyHeaders(Signature signature, HttpServletRequest request) { + private boolean verifyHeaders(ApiSignature signature, HttpServletRequest request) { String appId = request.getHeader(signature.appId()); if (StrUtil.isBlank(appId)) { return false; @@ -97,7 +94,7 @@ public class SignatureAspect { return false; } String nonce = request.getHeader(signature.nonce()); - if (StrUtil.isBlank(nonce) || nonce.length() < 10) { + if (StrUtil.isBlank(nonce) || StrUtil.length(nonce) < 10) { return false; } String sign = request.getHeader(signature.sign()); @@ -105,7 +102,7 @@ public class SignatureAspect { return false; } // 其他合法性校验 - long expireTime = signature.expireTime(); + long expireTime = signature.timeUnit().toMillis(signature.timeout()); long requestTimestamp = Long.parseLong(timestamp); // 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值) long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp); @@ -122,7 +119,7 @@ public class SignatureAspect { * @param request request * @return signature params */ - private SortedMap getRequestHeaders(Signature signature, HttpServletRequest request) { + private SortedMap getRequestHeaders(ApiSignature signature, HttpServletRequest request) { SortedMap sortedMap = new TreeMap<>(); sortedMap.put(signature.appId(), request.getHeader(signature.appId())); sortedMap.put(signature.timestamp(), request.getHeader(signature.timestamp())); @@ -154,17 +151,5 @@ public class SignatureAspect { return queryString.substring(1); } - /** - * 获取请求体参数 - * - * @param request request - * @return body - */ - private String getRequestBody(HttpServletRequest request) { - CacheRequestBodyWrapper requestWrapper = new CacheRequestBodyWrapper(request); - // 获取 body - return new String(requestWrapper.getBody(), StandardCharsets.UTF_8); - } - } diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java index d00fe7f8d..326e238ee 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java @@ -42,8 +42,7 @@ public class SignatureRedisDAO { } public void setNonce(String nonce, long time, TimeUnit timeUnit) { - // 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 ) - stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), nonce, time * 2, timeUnit); + stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), nonce, time, timeUnit); } private static String formatAppIdKey(String key) { diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-framework/yudao-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index d7cd3a883..6cab74e75 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,3 +1,4 @@ cn.iocoder.yudao.framework.idempotent.config.YudaoIdempotentConfiguration cn.iocoder.yudao.framework.lock4j.config.YudaoLock4jConfiguration -cn.iocoder.yudao.framework.ratelimiter.config.YudaoRateLimiterConfiguration \ No newline at end of file +cn.iocoder.yudao.framework.ratelimiter.config.YudaoRateLimiterConfiguration +cn.iocoder.yudao.framework.signature.config.YudaoSignatureAutoConfiguration \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java index e181edeb4..8e80fa591 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java @@ -23,10 +23,6 @@ public class CacheRequestBodyWrapper extends HttpServletRequestWrapper { */ private final byte[] body; - public byte[] getBody() { - return body; - } - public CacheRequestBodyWrapper(HttpServletRequest request) { super(request); body = ServletUtils.getBodyBytes(request);