mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-13 07:45:41 +08:00
fix(channel): 传输加密拦截器区分明文错误响应并对齐 dev 通道密钥
- 拦截器 decryptResponse 按状态码分流: 2xx 无加密头视为协议违规(保留安全约束); 非2xx 明文错误响应解析 body 透传真实错误详情, 避免子应用入站解密失败等错误被「响应未携带加密头」掩盖 - 新增 i18n key channel.error.transportEncrypt.plainErrorResponse (10 语种同步) - 新增 ChannelTransportEncryptInterceptorTest 覆盖 6 场景 - dev 环境 channel.one/two 传输密钥对齐对应子应用(原复用数据加密密钥且与子应用不匹配, 导致调用 Go/Java 子应用均报错)
This commit is contained in:
@@ -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}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"ciphertextInvalid": "チャネル転送の暗号文の長さが不正です",
|
||||
"decryptFailed": "チャネル転送の復号に失敗しました",
|
||||
"responseHeaderMissing": "チャネル応答に転送暗号化ヘッダー {0} がありません",
|
||||
"requestHeaderMissing": "チャネル転送暗号化: リクエストに暗号化ヘッダーがありません"
|
||||
"requestHeaderMissing": "チャネル転送暗号化: リクエストに暗号化ヘッダーがありません",
|
||||
"plainErrorResponse": "チャネル子アプリから平文エラーレスポンス[{0}]が返されました: {1}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"ciphertextInvalid": "채널 전송 암호문 길이가 잘못되었습니다",
|
||||
"decryptFailed": "채널 전송 복호화에 실패했습니다",
|
||||
"responseHeaderMissing": "채널 응답에 전송 암호화 헤더 {0}가 없습니다",
|
||||
"requestHeaderMissing": "채널 전송 암호화: 요청에 암호화 헤더가 없습니다"
|
||||
"requestHeaderMissing": "채널 전송 암호화: 요청에 암호화 헤더가 없습니다",
|
||||
"plainErrorResponse": "채널 자식 앱이 평문 오류 응답[{0}]을 반환했습니다: {1}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"ciphertextInvalid": "ความยาวciphertext การส่งช่องทางไม่ถูกต้อง",
|
||||
"decryptFailed": "การถอดรหัสการส่งช่องทางล้มเหลว",
|
||||
"responseHeaderMissing": "การตอบกลับช่องทางไม่มีส่วนหัวเข้ารหัสการส่ง {0}",
|
||||
"requestHeaderMissing": "การเข้ารหัสการส่งช่องทาง: คำขอไม่มีส่วนหัวเข้ารหัส"
|
||||
"requestHeaderMissing": "การเข้ารหัสการส่งช่องทาง: คำขอไม่มีส่วนหัวเข้ารหัส",
|
||||
"plainErrorResponse": "แอปย่อยของช่องทางส่งกลับการตอบกลับข้อผิดพลาดแบบข้อความธรรมดา [{0}]: {1}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"ciphertextInvalid": "通道传输密文长度非法",
|
||||
"decryptFailed": "通道传输解密失败",
|
||||
"responseHeaderMissing": "通道子应用响应未携带传输加密头 {0}",
|
||||
"requestHeaderMissing": "通道传输加密:请求未携带加密头"
|
||||
"requestHeaderMissing": "通道传输加密:请求未携带加密头",
|
||||
"plainErrorResponse": "通道子应用返回明文错误响应[{0}]: {1}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"ciphertextInvalid": "通道傳輸密文長度非法",
|
||||
"decryptFailed": "通道傳輸解密失敗",
|
||||
"responseHeaderMissing": "通道子應用回應未攜帶傳輸加密頭 {0}",
|
||||
"requestHeaderMissing": "通道傳輸加密:請求未攜帶加密頭"
|
||||
"requestHeaderMissing": "通道傳輸加密:請求未攜帶加密頭",
|
||||
"plainErrorResponse": "通道子應用傳回明文錯誤回應[{0}]: {1}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"ciphertextInvalid": "通道傳輸密文長度非法",
|
||||
"decryptFailed": "通道傳輸解密失敗",
|
||||
"responseHeaderMissing": "通道子應用回應未攜帶傳輸加密頭 {0}",
|
||||
"requestHeaderMissing": "通道傳輸加密:請求未攜帶加密頭"
|
||||
"requestHeaderMissing": "通道傳輸加密:請求未攜帶加密頭",
|
||||
"plainErrorResponse": "通道子應用回傳明文錯誤回應[{0}]: {1}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,5 +46,10 @@
|
||||
<artifactId>common-config</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -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 解析
|
||||
|
||||
@@ -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<String, Object> 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user