From 33baa0436d5c61e1d73b7042f964da52395a6ae8 Mon Sep 17 00:00:00 2001 From: 13009 Date: Wed, 29 May 2024 10:04:46 +0800 Subject: [PATCH] =?UTF-8?q?test:=20=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/core/cipher/DefaultValidator.java | 33 ++-- .../com/czcb/scfs/api/core/ConstantsTest.java | 29 ++++ .../api/core/cipher/DefaultValidatorTest.java | 157 ++++++++++++++++-- 3 files changed, 190 insertions(+), 29 deletions(-) create mode 100644 scfs-api-core/src/test/java/com/czcb/scfs/api/core/ConstantsTest.java diff --git a/scfs-api-core/src/main/java/com/czcb/scfs/api/core/cipher/DefaultValidator.java b/scfs-api-core/src/main/java/com/czcb/scfs/api/core/cipher/DefaultValidator.java index c8e75a2..436e2d5 100644 --- a/scfs-api-core/src/main/java/com/czcb/scfs/api/core/cipher/DefaultValidator.java +++ b/scfs-api-core/src/main/java/com/czcb/scfs/api/core/cipher/DefaultValidator.java @@ -49,22 +49,22 @@ public final class DefaultValidator implements Validator { public void isInvalidHttpCode(OriginalResponse response) { if (response.getStatusCode() < HTTP_OK || response.getStatusCode() >= HTTP_MULT_CHOICE) { throw new ValidationException(String.format("校验失败, HttpStatusCode=%s, Request-Id=%s, HttpResponseBody=%s", - response.getStatusCode(), response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response))); + response.getStatusCode(), response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response))); } } public void validateTimestamp(OriginalResponse response) { String timestamp = response.getHttpHeaders().getHeader(TIMESTAMP); if (Strings.isEmpty(timestamp)) { - throw new TimestampException(String.format("校验失败, 时间戳[%s]不存在, Request-Id=%s, HttpResponseBody=%s", TIMESTAMP, - response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response))); + throw new TimestampException(String.format("校验失败, 时间戳[%s]不存在, Request-Id=%s, HttpResponseBody=%s", + TIMESTAMP, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response))); } Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp)); // 拒绝过期请求 if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) { - throw new TimestampException(String.format("校验失败, 时间戳[%s=%s]已过期, Request-Id=%s, HttpResponseBody=%s", TIMESTAMP, - timestamp, response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response))); + throw new TimestampException(String.format("校验失败, 时间戳[%s=%s]已过期, Request-Id=%s, HttpResponseBody=%s", + TIMESTAMP, timestamp, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response))); } } @@ -72,31 +72,34 @@ public final class DefaultValidator implements Validator { // 待签名串 String message = profile.getSignature().getCredential().buildResponseMessage(response, channel); String signature = response.getHttpHeaders().getHeader(SIGNATURE); - if (signature == null || signature.isEmpty()) { - throw new ValidationException(String.format("校验失败, 签名[%s]不存在, Request-Id=%s, HttpResponseBody=%s", SIGNATURE, - response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response))); + if (Strings.isEmpty(signature)) { + throw new ValidationException(String.format("校验失败, 签名[%s]不存在, Request-Id=%s, HttpResponseBody=%s", + SIGNATURE, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response))); } // 签名证书编号 String serialNumber = response.getHttpHeaders().getHeader(BANK_CERTIFICATE_SERIAL); if (!verifier.verify(serialNumber, message, signature)) { - throw new ValidationException(String.format("校验失败, 签名[%s=%s]校验未通过, Request-Id=%s, HttpResponseBody=%s", SIGNATURE, - signature, response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response))); + throw new ValidationException(String.format("校验失败, 签名[%s=%s]校验未通过, Request-Id=%s, HttpResponseBody=%s", + SIGNATURE, signature, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response))); } } - private String plainBody(OriginalResponse originalResponse) { - byte[] body = originalResponse.getBody(); + private String decryptResponseBodyIfNecessary(OriginalResponse originalResponse) { + // 原始应答报文 + byte[] responseBody = originalResponse.getBody(); // 判断是否有body加密密钥 if (!originalResponse.getHttpHeaders().hasHeader(SECRET_KEY)) { - return Strings.toStr(body); + // 明文直接返回 + return Strings.toStr(responseBody); } - // 解密密钥 + // SecretKey String secretKey = originalResponse.getHttpHeaders().getHeader(SECRET_KEY); + // 解密密钥 String decryptSecretKey = getProfile().getPrivacy().getDecryptor().decrypt(secretKey); // 解密body - return getProfile().getPrivacy().getSecretCipher().decrypt(Strings.toBytes(decryptSecretKey), body); + return getProfile().getPrivacy().getSecretCipher().decrypt(Strings.toBytes(decryptSecretKey), responseBody); } } diff --git a/scfs-api-core/src/test/java/com/czcb/scfs/api/core/ConstantsTest.java b/scfs-api-core/src/test/java/com/czcb/scfs/api/core/ConstantsTest.java new file mode 100644 index 0000000..eb50c1d --- /dev/null +++ b/scfs-api-core/src/test/java/com/czcb/scfs/api/core/ConstantsTest.java @@ -0,0 +1,29 @@ +package com.czcb.scfs.api.core; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class ConstantsTest { + + @Test + void test() { + Assertions.assertEquals("X-SCFS-Channel-Serial", Constants.CHANNEL_CERTIFICATE_SERIAL); + Assertions.assertEquals("X-SCFS-Channel-No", Constants.CHANNEL_NO); + Assertions.assertEquals("X-SCFS-App-No", Constants.APP_NO); + Assertions.assertEquals("X-SCFS-Signature", Constants.SIGNATURE); + Assertions.assertEquals("X-SCFS-Api-Version", Constants.API_VERSION); + Assertions.assertEquals("X-SCFS-Secret-Key", Constants.SECRET_KEY); + Assertions.assertEquals("X-SCFS-Nonce", Constants.NONCE); + Assertions.assertEquals("X-SCFS-Timestamp", Constants.TIMESTAMP); + Assertions.assertEquals("X-SCFS-Request-Id", Constants.REQUEST_ID); + + Assertions.assertEquals("Content-Type", Constants.CONTENT_TYPE); + Assertions.assertEquals("Authorization", Constants.AUTHORIZATION); + Assertions.assertEquals("Accept-Encoding", Constants.ACCEPT_ENCODING); + Assertions.assertEquals("gzip", Constants.GZIP_ENCODING); + Assertions.assertEquals("Content-Encoding", Constants.CONTENT_ENCODING); + Assertions.assertEquals("User-Agent", Constants.USER_AGENT); + + Assertions.assertEquals(16, Constants.HEX); + } +} \ No newline at end of file diff --git a/scfs-api-core/src/test/java/com/czcb/scfs/api/core/cipher/DefaultValidatorTest.java b/scfs-api-core/src/test/java/com/czcb/scfs/api/core/cipher/DefaultValidatorTest.java index 4a5bf55..23ffec4 100644 --- a/scfs-api-core/src/test/java/com/czcb/scfs/api/core/cipher/DefaultValidatorTest.java +++ b/scfs-api-core/src/test/java/com/czcb/scfs/api/core/cipher/DefaultValidatorTest.java @@ -13,6 +13,7 @@ import com.czcb.scfs.api.core.http.client.TestVerifier; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.nio.charset.StandardCharsets; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -23,6 +24,7 @@ import java.util.Map; import static com.czcb.scfs.api.core.Constants.TIMESTAMP; class DefaultValidatorTest { + // 创建配置文件 Profile buildProfile() { PrivateKey privateKey = KeyText.loadTestPrivateKeyRSA(); X509Certificate certificate = KeyText.loadTestRSA(); @@ -83,9 +85,51 @@ class DefaultValidatorTest { .statusCode(500) .build(); - Assertions.assertThrows(ValidationException.class, () -> { - defaultValidator.isInvalidHttpCode(response); - }); + ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.isInvalidHttpCode(response)); + Assertions.assertEquals("校验失败, HttpStatusCode=500, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage()); + } + + @Test + void isInvalidHttpCodeError1xx() { + DefaultValidator defaultValidator = new DefaultValidator(buildProfile()); + + Map headers = new HashMap<>(); + headers.put("X-SCFS-Request-Id", "123456789"); + OriginalResponse response = new OriginalResponse.Builder() + .request(new HttpRequest.Builder() + .httpMethod(HttpMethod.POST) + .url("http://demo") + .build()) + .body("123") + .headers(headers) + .statusCode(100) + .build(); + + ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.isInvalidHttpCode(response)); + Assertions.assertEquals("校验失败, HttpStatusCode=100, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage()); + } + + @Test + void isInvalidHttpCodeError1xxBody() { + DefaultValidator defaultValidator = new DefaultValidator(buildProfile()); + + String plain = "0987654321123456"; + String secretKey = defaultValidator.getProfile().getPrivacy().getEncryptor().encrypt(plain); + Map headers = new HashMap<>(); + headers.put("X-SCFS-Request-Id", "123456789"); + headers.put("X-SCFS-Secret-Key", secretKey); + OriginalResponse response = new OriginalResponse.Builder() + .request(new HttpRequest.Builder() + .httpMethod(HttpMethod.POST) + .url("http://demo") + .build()) + .body(defaultValidator.getProfile().getPrivacy().getSecretCipher().encrypt(plain.getBytes(StandardCharsets.UTF_8), "123".getBytes(StandardCharsets.UTF_8))) + .headers(headers) + .statusCode(100) + .build(); + + ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.isInvalidHttpCode(response)); + Assertions.assertEquals("校验失败, HttpStatusCode=100, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage()); } @Test @@ -108,8 +152,9 @@ class DefaultValidatorTest { defaultValidator.validateTimestamp(response); } + // 没有时间戳案例 @Test - void validateTimestampError() { + void validateTimestampNoneError() { DefaultValidator defaultValidator = new DefaultValidator(buildProfile()); Map headers = new HashMap<>(); @@ -124,9 +169,30 @@ class DefaultValidatorTest { .statusCode(500) .build(); - Assertions.assertThrows(TimestampException.class, () -> { - defaultValidator.validateTimestamp(response); - }); + TimestampException validationException = Assertions.assertThrows(TimestampException.class, () -> defaultValidator.validateTimestamp(response)); + Assertions.assertEquals("校验失败, 时间戳[X-SCFS-Timestamp]不存在, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage()); + } + + // 时间戳错误案例 + @Test + void validateTimestampError() { + DefaultValidator defaultValidator = new DefaultValidator(buildProfile()); + + Map headers = new HashMap<>(); + headers.put("X-SCFS-Request-Id", "123456789"); + headers.put("X-SCFS-Timestamp", "1716941850"); + OriginalResponse response = new OriginalResponse.Builder() + .request(new HttpRequest.Builder() + .httpMethod(HttpMethod.POST) + .url("http://demo") + .build()) + .body("123") + .headers(headers) + .statusCode(200) + .build(); + + TimestampException validationException = Assertions.assertThrows(TimestampException.class, () -> defaultValidator.validateTimestamp(response)); + Assertions.assertEquals("校验失败, 时间戳[X-SCFS-Timestamp=1716941850]已过期, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage()); } @Test @@ -142,14 +208,77 @@ class DefaultValidatorTest { .build()) .body("123") .headers(headers) - .statusCode(500) + .statusCode(200) .build(); - Assertions.assertThrows(ValidationException.class, () -> { - defaultValidator.validateResponseSignature(response, new DefaultChannel.Builder() - .channelNo("1010") - .appNo("1111") - .build()); - }); + ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.validateResponseSignature(response, new DefaultChannel.Builder() + .channelNo("1010") + .appNo("1111") + .build())); + Assertions.assertEquals("校验失败, 签名[X-SCFS-Signature]不存在, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage()); + } + + @Test + void validateResponseSignatureError() { + DefaultValidator defaultValidator = new DefaultValidator(buildProfile()); + + Map headers = new HashMap<>(); + headers.put("X-SCFS-Request-Id", "123456789"); + headers.put("X-SCFS-Signature", "MTIzNDU2Nzg5"); + headers.put("X-SCFS-Serial", "6CDDAA92CAD75998325027647847330C1756291"); + OriginalResponse response = new OriginalResponse.Builder() + .request(new HttpRequest.Builder() + .httpMethod(HttpMethod.POST) + .url("http://demo") + .build()) + .body("123") + .headers(headers) + .statusCode(200) + .build(); + + ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.validateResponseSignature(response, new DefaultChannel.Builder() + .channelNo("1010") + .appNo("1111") + .build())); + Assertions.assertEquals("校验失败, 签名[X-SCFS-Signature=MTIzNDU2Nzg5]校验未通过, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage()); + } + + @Test + void validate() { + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + String message = String.format("X-SCFS-Nonce=3333333333,X-SCFS-Timestamp=%s,X-SCFS-Serial=6CDDAA92CAD75998325027647847330C1756291,X-SCFS-Channel-Serial=6CDDAA92CAD75998325027647847330C1756291,X-SCFS-Secret-Key=ddd\n" + + "123\n", timestamp); + + DefaultValidator defaultValidator = new DefaultValidator(buildProfile()); + Map headers = new HashMap<>(); + headers.put("X-SCFS-Request-Id", "123456789"); + headers.put("X-SCFS-Timestamp", timestamp); + headers.put("X-SCFS-Nonce", "3333333333"); + headers.put("X-SCFS-Secret-Key", "ddd"); + headers.put("X-SCFS-Signature", defaultValidator.getProfile().getSignature().getSigner().sign(message).getSignature()); + headers.put("X-SCFS-Serial", "6CDDAA92CAD75998325027647847330C1756291"); + headers.put("X-SCFS-Channel-Serial", "6CDDAA92CAD75998325027647847330C1756291"); + + OriginalResponse response = new OriginalResponse.Builder() + .request(new HttpRequest.Builder() + .httpMethod(HttpMethod.POST) + .url("http://demo") + .build()) + .body("123") + .headers(headers) + .statusCode(200) + .build(); + + Assertions.assertDoesNotThrow(() -> defaultValidator.validate(null, response, new DefaultChannel.Builder() + .channelNo("1010") + .appNo("1111") + .build())); + } + + @Test + void getProfile() { + DefaultValidator defaultValidator = new DefaultValidator(buildProfile()); + Assertions.assertNotNull(defaultValidator.getProfile()); + Assertions.assertEquals("000000", defaultValidator.getProfile().getChannel().getChannelNo()); } } \ No newline at end of file