diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/channel/error.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/channel/error.json index 16c4f8f2d..efc6711da 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/channel/error.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/channel/error.json @@ -17,6 +17,7 @@ "ciphertextInvalid": "Channel transport ciphertext length is invalid", "decryptFailed": "Channel transport decrypt failed", "responseHeaderMissing": "Channel response missing transport encrypt header {0}", - "requestHeaderMissing": "Channel transport encrypt: request missing encrypt header" + "requestHeaderMissing": "Channel transport encrypt: request missing encrypt header", + "plainErrorResponse": "Channel sub-app returned plaintext error response [{0}]: {1}" } } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/channel/error.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/channel/error.json index ae3478ac8..5882ba7a0 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/channel/error.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/channel/error.json @@ -17,6 +17,7 @@ "ciphertextInvalid": "Panjang ciphertext transport saluran tidak valid", "decryptFailed": "Dekripsi transport saluran gagal", "responseHeaderMissing": "Respons saluran tidak memiliki header enkripsi transport {0}", - "requestHeaderMissing": "Enkripsi transport saluran: permintaan tanpa header enkripsi" + "requestHeaderMissing": "Enkripsi transport saluran: permintaan tanpa header enkripsi", + "plainErrorResponse": "Sub-aplikasi saluran mengembalikan respons kesalahan teks polos [{0}]: {1}" } } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/channel/error.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/channel/error.json index 0b84801fc..a3f84685a 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/channel/error.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/channel/error.json @@ -17,6 +17,7 @@ "ciphertextInvalid": "チャネル転送の暗号文の長さが不正です", "decryptFailed": "チャネル転送の復号に失敗しました", "responseHeaderMissing": "チャネル応答に転送暗号化ヘッダー {0} がありません", - "requestHeaderMissing": "チャネル転送暗号化: リクエストに暗号化ヘッダーがありません" + "requestHeaderMissing": "チャネル転送暗号化: リクエストに暗号化ヘッダーがありません", + "plainErrorResponse": "チャネル子アプリから平文エラーレスポンス[{0}]が返されました: {1}" } } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/channel/error.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/channel/error.json index e9c8c1e43..858efe016 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/channel/error.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/channel/error.json @@ -17,6 +17,7 @@ "ciphertextInvalid": "채널 전송 암호문 길이가 잘못되었습니다", "decryptFailed": "채널 전송 복호화에 실패했습니다", "responseHeaderMissing": "채널 응답에 전송 암호화 헤더 {0}가 없습니다", - "requestHeaderMissing": "채널 전송 암호화: 요청에 암호화 헤더가 없습니다" + "requestHeaderMissing": "채널 전송 암호화: 요청에 암호화 헤더가 없습니다", + "plainErrorResponse": "채널 자식 앱이 평문 오류 응답[{0}]을 반환했습니다: {1}" } } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/channel/error.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/channel/error.json index 369fe74fc..cc8585dd8 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/channel/error.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/channel/error.json @@ -17,6 +17,7 @@ "ciphertextInvalid": "Panjang ciphertext pengangkutan saluran tidak sah", "decryptFailed": "Penyahsulitan pengangkutan saluran gagal", "responseHeaderMissing": "Respons saluran tiada pengepala penyulitan pengangkutan {0}", - "requestHeaderMissing": "Penyulitan pengangkutan saluran: permintaan tiada pengepala penyulitan" + "requestHeaderMissing": "Penyulitan pengangkutan saluran: permintaan tiada pengepala penyulitan", + "plainErrorResponse": "Sub-aplikasi saluran memulangkan respons ralat teks biasa [{0}]: {1}" } } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/channel/error.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/channel/error.json index 25ba8c695..ae82d9349 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/channel/error.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/channel/error.json @@ -17,6 +17,7 @@ "ciphertextInvalid": "ความยาวciphertext การส่งช่องทางไม่ถูกต้อง", "decryptFailed": "การถอดรหัสการส่งช่องทางล้มเหลว", "responseHeaderMissing": "การตอบกลับช่องทางไม่มีส่วนหัวเข้ารหัสการส่ง {0}", - "requestHeaderMissing": "การเข้ารหัสการส่งช่องทาง: คำขอไม่มีส่วนหัวเข้ารหัส" + "requestHeaderMissing": "การเข้ารหัสการส่งช่องทาง: คำขอไม่มีส่วนหัวเข้ารหัส", + "plainErrorResponse": "แอปย่อยของช่องทางส่งกลับการตอบกลับข้อผิดพลาดแบบข้อความธรรมดา [{0}]: {1}" } } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/channel/error.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/channel/error.json index 7aa0959b4..b1fd10b5b 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/channel/error.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/channel/error.json @@ -17,6 +17,7 @@ "ciphertextInvalid": "Độ dài bản mã truyền tải kênh không hợp lệ", "decryptFailed": "Giải mã truyền tải kênh thất bại", "responseHeaderMissing": "Phản hồi kênh thiếu header mã hóa truyền tải {0}", - "requestHeaderMissing": "Mã hóa truyền tải kênh: yêu cầu thiếu header mã hóa" + "requestHeaderMissing": "Mã hóa truyền tải kênh: yêu cầu thiếu header mã hóa", + "plainErrorResponse": "Ứng dụng con kênh trả về phản hồi lỗi văn bản thuần [{0}]: {1}" } } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/channel/error.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/channel/error.json index 72dd6dc1a..f1beb0d4b 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/channel/error.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/channel/error.json @@ -17,6 +17,7 @@ "ciphertextInvalid": "通道传输密文长度非法", "decryptFailed": "通道传输解密失败", "responseHeaderMissing": "通道子应用响应未携带传输加密头 {0}", - "requestHeaderMissing": "通道传输加密:请求未携带加密头" + "requestHeaderMissing": "通道传输加密:请求未携带加密头", + "plainErrorResponse": "通道子应用返回明文错误响应[{0}]: {1}" } } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/channel/error.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/channel/error.json index b296f2a68..1b26496f2 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/channel/error.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/channel/error.json @@ -17,6 +17,7 @@ "ciphertextInvalid": "通道傳輸密文長度非法", "decryptFailed": "通道傳輸解密失敗", "responseHeaderMissing": "通道子應用回應未攜帶傳輸加密頭 {0}", - "requestHeaderMissing": "通道傳輸加密:請求未攜帶加密頭" + "requestHeaderMissing": "通道傳輸加密:請求未攜帶加密頭", + "plainErrorResponse": "通道子應用傳回明文錯誤回應[{0}]: {1}" } } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/channel/error.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/channel/error.json index 2aaed942b..340b33e9e 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/channel/error.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/channel/error.json @@ -17,6 +17,7 @@ "ciphertextInvalid": "通道傳輸密文長度非法", "decryptFailed": "通道傳輸解密失敗", "responseHeaderMissing": "通道子應用回應未攜帶傳輸加密頭 {0}", - "requestHeaderMissing": "通道傳輸加密:請求未攜帶加密頭" + "requestHeaderMissing": "通道傳輸加密:請求未攜帶加密頭", + "plainErrorResponse": "通道子應用回傳明文錯誤回應[{0}]: {1}" } } diff --git a/daxpay-platform/daxpay-platform-common/common-spring/pom.xml b/daxpay-platform/daxpay-platform-common/common-spring/pom.xml index d303c00d8..05ad37083 100644 --- a/daxpay-platform/daxpay-platform-common/common-spring/pom.xml +++ b/daxpay-platform/daxpay-platform-common/common-spring/pom.xml @@ -46,5 +46,10 @@ common-config ${project.version} + + org.junit.jupiter + junit-jupiter + test + diff --git a/daxpay-platform/daxpay-platform-common/common-spring/src/main/java/cn/daxpay/open/platform/common/spring/channel/ChannelTransportEncryptInterceptor.java b/daxpay-platform/daxpay-platform-common/common-spring/src/main/java/cn/daxpay/open/platform/common/spring/channel/ChannelTransportEncryptInterceptor.java index 4a68abe58..59a14b178 100644 --- a/daxpay-platform/daxpay-platform-common/common-spring/src/main/java/cn/daxpay/open/platform/common/spring/channel/ChannelTransportEncryptInterceptor.java +++ b/daxpay-platform/daxpay-platform-common/common-spring/src/main/java/cn/daxpay/open/platform/common/spring/channel/ChannelTransportEncryptInterceptor.java @@ -13,6 +13,9 @@ import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.util.StreamUtils; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -37,9 +40,19 @@ public class ChannelTransportEncryptInterceptor implements ClientHttpRequestInte public static final String MSG_RESPONSE_HEADER_MISSING = "channel.error.transportEncrypt.responseHeaderMissing"; + /// 通道子应用返回明文错误响应(子应用主动返回的未加密错误,如入站解密失败 400) + public static final String MSG_PLAIN_ERROR_RESPONSE = + "channel.error.transportEncrypt.plainErrorResponse"; + private static final MediaType TEXT_PLAIN_UTF8 = new MediaType(MediaType.TEXT_PLAIN, StandardCharsets.UTF_8); + /// 明文错误 body 片段最大长度,防止异常日志爆炸 + private static final int SNIPPET_MAX_LENGTH = 200; + + /// 解析子应用明文错误响应的独立 ObjectMapper(拦截器为手动 new,非 Spring Bean,无法注入) + private static final ObjectMapper PLAIN_ERROR_MAPPER = JsonMapper.builder().build(); + private final ChannelAesGcmEncryptor encryptor; @Override @@ -58,7 +71,7 @@ public class ChannelTransportEncryptInterceptor implements ClientHttpRequestInte return decryptResponse(response); } - /// 解密响应体;无 body 原样返回;有 body 但无加密头视为协议错误 + /// 解密响应体;无 body 原样返回;有加密头则解密;无加密头按状态码区分协议违规与明文错误响应 private ClientHttpResponse decryptResponse(ClientHttpResponse response) throws IOException { byte[] responseBytes = StreamUtils.copyToByteArray(response.getBody()); if (responseBytes.length == 0) { @@ -66,16 +79,48 @@ public class ChannelTransportEncryptInterceptor implements ClientHttpRequestInte } String encryptedFlag = response.getHeaders().getFirst(HEADER_X_DAX_PAYLOAD_ENCRYPTED); - if (!"true".equalsIgnoreCase(encryptedFlag)) { - response.close(); + if ("true".equalsIgnoreCase(encryptedFlag)) { + // 正常加密响应:解密后交给业务层 + String plaintext = encryptor.decrypt(new String(responseBytes, StandardCharsets.UTF_8)); + byte[] plainBytes = plaintext.getBytes(StandardCharsets.UTF_8); + return new DecryptedClientHttpResponse(response, plainBytes, true); + } + + // 无加密头:子应用未按加密协议响应。区分「成功响应未加密」与「明文错误响应」 + // 状态码须在 close 前获取,避免部分实现 close 后状态丢失 + HttpStatusCode statusCode = response.getStatusCode(); + response.close(); + if (statusCode.is2xxSuccessful()) { + // 2xx 成功响应却未加密:真正的协议违规(子应用未加密或被劫持),按安全约束报错 // 通道子应用响应未携带传输加密头 throw new BizInfoException( CommonErrorCode.SYSTEM_ERROR, MSG_RESPONSE_HEADER_MISSING, HEADER_X_DAX_PAYLOAD_ENCRYPTED); } + // 非 2xx 明文错误响应:子应用入站解密失败/加密失败等主动返回的明文错误(如 400 解密失败) + // 透传子应用返回的真实错误详情,避免被「响应未携带加密头」掩盖 + // 通道子应用返回明文错误响应 + String detail = extractPlainErrorDetail(responseBytes); + throw new BizInfoException( + CommonErrorCode.SYSTEM_ERROR, MSG_PLAIN_ERROR_RESPONSE, statusCode.value(), detail); + } - String plaintext = encryptor.decrypt(new String(responseBytes, StandardCharsets.UTF_8)); - byte[] plainBytes = plaintext.getBytes(StandardCharsets.UTF_8); - return new DecryptedClientHttpResponse(response, plainBytes, true); + /// 从子应用明文错误响应体提取错误详情 + /// 优先解析 JSON 的 msg 字段(子应用返回 DaxResult 结构 {"code":400,"msg":"..."}),解析失败回退原始 body 片段 + private static String extractPlainErrorDetail(byte[] responseBytes) { + String body = new String(responseBytes, StandardCharsets.UTF_8); + try { + JsonNode node = PLAIN_ERROR_MAPPER.readTree(body); + String msg = node.path("msg").asText(""); + if (!msg.isEmpty()) { + return msg; + } + } catch (Exception ignored) { + // 解析失败回退到原始 body 片段 + } + // 截断防止异常日志爆炸 + return body.length() > SNIPPET_MAX_LENGTH + ? body.substring(0, SNIPPET_MAX_LENGTH) + "..." + : body; } /// 包装已解密的响应,供 RestClient/HttpExchange 按 JSON 解析 diff --git a/daxpay-platform/daxpay-platform-common/common-spring/src/test/java/cn/daxpay/open/platform/common/spring/channel/ChannelTransportEncryptInterceptorTest.java b/daxpay-platform/daxpay-platform-common/common-spring/src/test/java/cn/daxpay/open/platform/common/spring/channel/ChannelTransportEncryptInterceptorTest.java new file mode 100644 index 000000000..1175e84a5 --- /dev/null +++ b/daxpay-platform/daxpay-platform-common/common-spring/src/test/java/cn/daxpay/open/platform/common/spring/channel/ChannelTransportEncryptInterceptorTest.java @@ -0,0 +1,205 @@ +package cn.daxpay.open.platform.common.spring.channel; + +import cn.daxpay.open.platform.common.config.encrypt.ChannelAesGcmEncryptor; +import cn.daxpay.open.platform.core.exception.BizInfoException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpResponse; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/// # 通道传输报文加解密拦截器测试 +/// +/// 覆盖 [ChannelTransportEncryptInterceptor#decryptResponse] 的四种分支: +/// 1. 正常加密响应(2xx + 加密头)应解密为明文 JSON +/// 2. 成功响应未加密(2xx + 无加密头)应抛「响应未携带传输加密头」(协议违规) +/// 3. 子应用明文错误响应(非 2xx + 无加密头)应抛「明文错误响应」并透传真实错误详情 +/// 4. 空 body 响应原样放行 +class ChannelTransportEncryptInterceptorTest { + + private ChannelAesGcmEncryptor encryptor; + private ChannelTransportEncryptInterceptor interceptor; + + @BeforeEach + void initInterceptor() { + encryptor = new ChannelAesGcmEncryptor(generateKey(32)); + interceptor = new ChannelTransportEncryptInterceptor(encryptor); + } + + @Test + @DisplayName("正常加密响应(200 + 加密头)应解密为明文 JSON") + void shouldDecryptEncryptedResponse() throws IOException { + // 模拟子应用返回的加密响应 + String plaintext = "{\"code\":0,\"data\":\"ok\"}"; + String ciphertext = encryptor.encrypt(plaintext); + ClientHttpResponse response = mockResponse(200, ciphertext, true); + + ClientHttpResponse result = interceptor.intercept(mockRequest(), new byte[0], executionReturning(response)); + + // 出站请求 body 为空时不会被加密;响应应被解密为明文 JSON + byte[] body = result.getBody().readAllBytes(); + assertEquals(plaintext, new String(body, StandardCharsets.UTF_8)); + } + + @Test + @DisplayName("成功响应未携带加密头应抛协议违规错误") + void shouldThrowWhenSuccessResponseMissingEncryptedHeader() { + // 2xx 成功响应却未加密,属于真正的协议违规 + ClientHttpResponse response = mockResponse(200, "{\"code\":0,\"data\":\"ok\"}", false); + + BizInfoException ex = assertThrows(BizInfoException.class, + () -> interceptor.intercept(mockRequest(), new byte[0], executionReturning(response))); + + assertEquals(ChannelTransportEncryptInterceptor.MSG_RESPONSE_HEADER_MISSING, ex.getMessageKey()); + } + + @Test + @DisplayName("子应用明文错误响应(400 解密失败)应透传真实错误详情") + void shouldUnwrapPlainErrorResponseDetail() { + // 模拟子应用入站解密失败返回的 400 明文 JSON + String plainErrorBody = "{\"code\":400,\"msg\":\"通道传输解密失败\"}"; + ClientHttpResponse response = mockResponse(400, plainErrorBody, false); + + BizInfoException ex = assertThrows(BizInfoException.class, + () -> interceptor.intercept(mockRequest(), new byte[0], executionReturning(response))); + + // 应抛「明文错误响应」而非「响应未携带加密头」,并携带状态码与真实错误 msg + assertEquals(ChannelTransportEncryptInterceptor.MSG_PLAIN_ERROR_RESPONSE, ex.getMessageKey()); + Object[] args = ex.getArgs(); + assertEquals(400, args[0]); + assertEquals("通道传输解密失败", args[1]); + } + + @Test + @DisplayName("明文错误 body 非 JSON 时应回退为原始片段") + void shouldFallbackToRawSnippetWhenBodyNotJson() { + // 子应用返回非 JSON 明文(如网关/代理介入) + ClientHttpResponse response = mockResponse(502, "Bad Gateway", false); + + BizInfoException ex = assertThrows(BizInfoException.class, + () -> interceptor.intercept(mockRequest(), new byte[0], executionReturning(response))); + + assertEquals(ChannelTransportEncryptInterceptor.MSG_PLAIN_ERROR_RESPONSE, ex.getMessageKey()); + assertEquals(502, ex.getArgs()[0]); + // 回退为原始 body 片段 + assertEquals("Bad Gateway", ex.getArgs()[1]); + } + + @Test + @DisplayName("空 body 响应原样放行") + void shouldPassThroughEmptyBody() throws IOException { + ClientHttpResponse response = mockResponse(200, "", true); + + ClientHttpResponse result = interceptor.intercept(mockRequest(), new byte[0], executionReturning(response)); + + // 空 body 不解密,原样返回空字节数组 + assertArrayEquals(new byte[0], result.getBody().readAllBytes()); + } + + @Test + @DisplayName("出站非空请求 body 应被加密并携带加密头") + void shouldEncryptNonEmptyRequestBody() throws IOException { + String requestBody = "{\"channel\":\"alipay\"}"; + byte[] body = requestBody.getBytes(StandardCharsets.UTF_8); + // 加密响应,确保入站不抛错 + String ciphertext = encryptor.encrypt("{\"code\":0}"); + ClientHttpResponse response = mockResponse(200, ciphertext, true); + + // 捕获实际发给 execution 的请求 body + HttpRequest request = mockRequest(); + interceptor.intercept(request, body, executionReturning(response)); + + // 请求应被加密并设置加密头 + assertEquals("true", request.getHeaders().getFirst(ChannelTransportEncryptInterceptor.HEADER_X_DAX_PAYLOAD_ENCRYPTED)); + } + + /// 构造返回固定响应的 execution + private static ClientHttpRequestExecution executionReturning(ClientHttpResponse response) { + return (request, body) -> response; + } + + /// 构造最简 HttpRequest(带可写 headers) + private static HttpRequest mockRequest() { + HttpHeaders headers = new HttpHeaders(); + return new HttpRequest() { + @Override + public HttpMethod getMethod() { + return HttpMethod.POST; + } + + @Override + public URI getURI() { + return URI.create("http://localhost/channel/test"); + } + + @Override + public HttpHeaders getHeaders() { + return headers; + } + + @Override + public Map getAttributes() { + return Map.of(); + } + }; + } + + /// 构造 mock 响应 + private static ClientHttpResponse mockResponse(int status, String body, boolean withEncryptedHeader) { + HttpHeaders headers = new HttpHeaders(); + if (withEncryptedHeader) { + headers.add(ChannelTransportEncryptInterceptor.HEADER_X_DAX_PAYLOAD_ENCRYPTED, "true"); + } + byte[] bytes = body.getBytes(StandardCharsets.UTF_8); + return new ClientHttpResponse() { + @Override + public HttpStatusCode getStatusCode() { + return HttpStatusCode.valueOf(status); + } + + @Override + public String getStatusText() { + return ""; + } + + @Override + public void close() { + // no-op + } + + @Override + public InputStream getBody() { + return new ByteArrayInputStream(bytes); + } + + @Override + public HttpHeaders getHeaders() { + return headers; + } + }; + } + + /// 生成指定长度的随机密钥字符串 + private static String generateKey(int length) { + byte[] keyBytes = new byte[length]; + new SecureRandom().nextBytes(keyBytes); + return Base64.getEncoder().encodeToString(keyBytes).substring(0, length); + } +} diff --git a/daxpay-start/src/main/resources/application-dev.yml b/daxpay-start/src/main/resources/application-dev.yml index 235b8130f..f70f744de 100644 --- a/daxpay-start/src/main/resources/application-dev.yml +++ b/daxpay-start/src/main/resources/application-dev.yml @@ -175,13 +175,15 @@ daxpay: one: base-url: http://127.0.0.1:20100 # 通道传输 AES 密钥(强制常开,恰好 32 字符;仅本地占位,勿用于生产) + # 须与子应用一致: dax-pay-channel-one application.yml / dax-pay-channel-one-go config.yaml 的 channel.transport-encrypt.key transport-encrypt: - key: z0Vd8jDKB80pA6OOptGLO+qDVvWboEko + key: 0123456789abcdef0123456789abcdef # 子应用2: 其他支付通道 two: base-url: http://127.0.0.1:20200 + # 须与子应用一致: dax-pay-channel-two application.yml 的 channel.transport-encrypt.key transport-encrypt: - key: z0Vd8jDKB80pA6OOptGLO+qDVvWboEko + key: fedcba9876543210fedcba9876543210 # 子应用3: 其他通道(未来扩展, 暂未启用) # three: # base-url: http://127.0.0.1:20300