entry : sortParamsMap.entrySet()) {
- queryString.append("&").append(entry.getKey()).append("=").append(entry.getValue());
- }
- return queryString.substring(1);
- }
-
-}
-
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/ApiSignatureRedisDAO.java
similarity index 59%
rename from yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java
rename to yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/ApiSignatureRedisDAO.java
index 326e238ee..f4aa84910 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/ApiSignatureRedisDAO.java
@@ -6,50 +6,52 @@ import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
/**
- * API 签名 Redis DAO
+ * HTTP API 签名 Redis DAO
*
* @author Zhougang
*/
@AllArgsConstructor
-public class SignatureRedisDAO {
+public class ApiSignatureRedisDAO {
private final StringRedisTemplate stringRedisTemplate;
/**
* 验签随机数
- *
+ *
* KEY 格式:signature_nonce:%s // 参数为 随机数
* VALUE 格式:String
* 过期时间:不固定
*/
- private static final String SIGNATURE_NONCE = "signature_nonce:%s";
+ private static final String SIGNATURE_NONCE = "api_signature_nonce:%s";
/**
* 签名密钥
- *
- * KEY 格式:signature_appid:%s // 参数为 appid
+ *
+ * HASH 结构
+ * KEY 格式:%s // 参数为 appid
* VALUE 格式:String
- * 过期时间:预加载到 redis 永不过期
+ * 过期时间:永不过期(预加载到 Redis)
*/
- private static final String SIGNATURE_APPID = "signature_appid:%s";
+ private static final String SIGNATURE_APPID = "api_signature_app";
- public String getAppSecret(String appId) {
- return stringRedisTemplate.opsForValue().get(formatAppIdKey(appId));
- }
+ // ========== 验签随机数 ==========
public String getNonce(String nonce) {
return stringRedisTemplate.opsForValue().get(formatNonceKey(nonce));
}
- public void setNonce(String nonce, long time, TimeUnit timeUnit) {
- stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), nonce, time, timeUnit);
- }
-
- private static String formatAppIdKey(String key) {
- return String.format(SIGNATURE_APPID, key);
+ public void setNonce(String nonce, int time, TimeUnit timeUnit) {
+ stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), "", time, timeUnit);
}
private static String formatNonceKey(String key) {
return String.format(SIGNATURE_NONCE, key);
}
+
+ // ========== 签名密钥 ==========
+
+ public String getAppSecret(String appId) {
+ return (String) stringRedisTemplate.opsForHash().get(SIGNATURE_APPID, appId);
+ }
+
}
diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/package-info.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/package-info.java
new file mode 100644
index 000000000..4ebd87afb
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * HTTP API 签名,校验安全性
+ *
+ * @see builder()
+ .put("v1", new String[]{"k1"}).put("k1", new String[]{"v1"}).build());
+ when(request.getContentType()).thenReturn("application/json");
+ when(request.getReader()).thenReturn(new BufferedReader(new StringReader("test")));
+ // mock 方法
+ when(signatureRedisDAO.getAppSecret(eq(appId))).thenReturn(appSecret);
+
+ // 调用
+ boolean result = apiSignatureAspect.verifySignature(apiSignature, request);
+ // 断言结果
+ assertTrue(result);
+ // 断言调用
+ verify(signatureRedisDAO).setNonce(eq(nonce), eq(120), eq(TimeUnit.SECONDS));
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/SignatureTest.java b/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/SignatureTest.java
deleted file mode 100644
index a253cd51a..000000000
--- a/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/SignatureTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package cn.iocoder.yudao.framework.signature.core;
-
-import cn.hutool.core.lang.Snowflake;
-import cn.hutool.crypto.digest.DigestUtil;
-import cn.hutool.http.HttpRequest;
-import cn.hutool.http.HttpResponse;
-import cn.hutool.http.HttpUtil;
-import org.junit.jupiter.api.Test;
-
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-/**
- * {@link SignatureTest} 的单元测试
- */
-public class SignatureTest {
-
- @Test
- public void testSignatureGet() {
- String appId = "xxxxxx";
- Snowflake snowflake = new Snowflake();
-
- // 验签请求头前端需传入字段
- SortedMap headersMap = new TreeMap<>();
- headersMap.put("appId", appId);
- headersMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
- headersMap.put("nonce", String.valueOf(snowflake.nextId()));
-
- // 客户端加签内容
- StringBuilder clientSignatureContent = new StringBuilder();
- // 请求头
- for (Map.Entry entry : headersMap.entrySet()) {
- clientSignatureContent.append(entry.getKey()).append(entry.getValue());
- }
- // 请求 url
- clientSignatureContent.append("/admin-api/infra/demo01-contact/get");
- // 请求参数
- SortedMap paramsMap = new TreeMap<>();
- paramsMap.put("id", "100");
- paramsMap.put("name", "张三");
- StringBuilder queryString = new StringBuilder();
- for (Map.Entry entry : paramsMap.entrySet()) {
- queryString.append("&").append(entry.getKey()).append("=").append(entry.getValue());
- }
- clientSignatureContent.append(queryString.substring(1));
- // 密钥
- clientSignatureContent.append("d3cbeed9baf4e68673a1f69a2445359a20022b7c28ea2933dd9db9f3a29f902b");
- System.out.println("加签内容:" + clientSignatureContent);
- // 加签
- headersMap.put("sign", DigestUtil.sha256Hex(clientSignatureContent.toString()));
- headersMap.put("Authorization", "Bearer xxx");
-
- HttpRequest get = HttpUtil.createGet("http://localhost:48080/admin-api/infra/demo01-contact/get?id=100&name=张三");
- get.addHeaders(headersMap);
- System.out.println("执行结果==" + get.execute());
- }
-
- @Test
- public void testSignaturePost() {
- String appId = "xxxxxx";
- Snowflake snowflake = new Snowflake();
-
- // 验签请求头前端需传入字段
- SortedMap headersMap = new TreeMap<>();
- headersMap.put("appId", appId);
- headersMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
- headersMap.put("nonce", String.valueOf(snowflake.nextId()));
-
- // 客户端加签内容
- StringBuilder clientSignatureContent = new StringBuilder();
- // 请求头
- for (Map.Entry entry : headersMap.entrySet()) {
- clientSignatureContent.append(entry.getKey()).append(entry.getValue());
- }
- // 请求 url
- clientSignatureContent.append("/admin-api/infra/demo01-contact/create");
- // 请求体
- String body = "{\n" +
- " \"password\": \"1\",\n" +
- " \"date\": \"2024-04-24 16:28:00\",\n" +
- " \"user\": {\n" +
- " \"area\": \"浦东新区\",\n" +
- " \"1\": \"xx\",\n" +
- " \"2\": \"xx\",\n" +
- " \"province\": \"上海市\",\n" +
- " \"data\": {\n" +
- " \"99\": \"xx\",\n" +
- " \"1\": \"xx\",\n" +
- " \"100\": \"xx\",\n" +
- " \"2\": \"xx\",\n" +
- " \"3\": \"xx\",\n" +
- " \"array\": [\n" +
- " {\n" +
- " \"3\": \"aa\",\n" +
- " \"4\": \"aa\",\n" +
- " \"2\": \"aa\",\n" +
- " \"1\": \"aa\"\n" +
- " },\n" +
- " {\n" +
- " \"99\": \"aa\",\n" +
- " \"100\": \"aa\",\n" +
- " \"88\": \"aa\",\n" +
- " \"120\": \"aa\"\n" +
- " }\n" +
- " ]\n" +
- " },\n" +
- " \"sex\": \"男\",\n" +
- " \"name\": \"张三\",\n" +
- " \"array\": [\n" +
- " \"1\",\n" +
- " \"3\",\n" +
- " \"5\",\n" +
- " \"2\"\n" +
- " ]\n" +
- " },\n" +
- " \"username\": \"xiaoming\"\n" +
- "}";
- clientSignatureContent.append(body);
-
- // 密钥
- clientSignatureContent.append("d3cbeed9baf4e68673a1f69a2445359a20022b7c28ea2933dd9db9f3a29f902b");
- System.out.println("加签内容:" + clientSignatureContent);
- // 加签
- headersMap.put("sign", DigestUtil.sha256Hex(clientSignatureContent.toString()));
- headersMap.put("Authorization", "Bearer xxx");
-
- HttpRequest post = HttpUtil.createPost("http://localhost:48080/admin-api/infra/demo01-contact/create");
- post.addHeaders(headersMap);
- post.body(body);
- try (HttpResponse execute = post.execute()) {
- System.out.println("执行结果==" + execute);
- }
- }
-
-}