test: 单元测试
parent
03e6ac3f9f
commit
33baa0436d
|
|
@ -49,22 +49,22 @@ public final class DefaultValidator implements Validator {
|
||||||
public void isInvalidHttpCode(OriginalResponse response) {
|
public void isInvalidHttpCode(OriginalResponse response) {
|
||||||
if (response.getStatusCode() < HTTP_OK || response.getStatusCode() >= HTTP_MULT_CHOICE) {
|
if (response.getStatusCode() < HTTP_OK || response.getStatusCode() >= HTTP_MULT_CHOICE) {
|
||||||
throw new ValidationException(String.format("校验失败, HttpStatusCode=%s, Request-Id=%s, HttpResponseBody=%s",
|
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) {
|
public void validateTimestamp(OriginalResponse response) {
|
||||||
String timestamp = response.getHttpHeaders().getHeader(TIMESTAMP);
|
String timestamp = response.getHttpHeaders().getHeader(TIMESTAMP);
|
||||||
if (Strings.isEmpty(timestamp)) {
|
if (Strings.isEmpty(timestamp)) {
|
||||||
throw new TimestampException(String.format("校验失败, 时间戳[%s]不存在, Request-Id=%s, HttpResponseBody=%s", TIMESTAMP,
|
throw new TimestampException(String.format("校验失败, 时间戳[%s]不存在, Request-Id=%s, HttpResponseBody=%s",
|
||||||
response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response)));
|
TIMESTAMP, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
|
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
|
||||||
// 拒绝过期请求
|
// 拒绝过期请求
|
||||||
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
|
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
|
||||||
throw new TimestampException(String.format("校验失败, 时间戳[%s=%s]已过期, Request-Id=%s, HttpResponseBody=%s", TIMESTAMP,
|
throw new TimestampException(String.format("校验失败, 时间戳[%s=%s]已过期, Request-Id=%s, HttpResponseBody=%s",
|
||||||
timestamp, response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response)));
|
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 message = profile.getSignature().getCredential().buildResponseMessage(response, channel);
|
||||||
String signature = response.getHttpHeaders().getHeader(SIGNATURE);
|
String signature = response.getHttpHeaders().getHeader(SIGNATURE);
|
||||||
if (signature == null || signature.isEmpty()) {
|
if (Strings.isEmpty(signature)) {
|
||||||
throw new ValidationException(String.format("校验失败, 签名[%s]不存在, Request-Id=%s, HttpResponseBody=%s", SIGNATURE,
|
throw new ValidationException(String.format("校验失败, 签名[%s]不存在, Request-Id=%s, HttpResponseBody=%s",
|
||||||
response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response)));
|
SIGNATURE, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 签名证书编号
|
// 签名证书编号
|
||||||
String serialNumber = response.getHttpHeaders().getHeader(BANK_CERTIFICATE_SERIAL);
|
String serialNumber = response.getHttpHeaders().getHeader(BANK_CERTIFICATE_SERIAL);
|
||||||
if (!verifier.verify(serialNumber, message, signature)) {
|
if (!verifier.verify(serialNumber, message, signature)) {
|
||||||
throw new ValidationException(String.format("校验失败, 签名[%s=%s]校验未通过, Request-Id=%s, HttpResponseBody=%s", SIGNATURE,
|
throw new ValidationException(String.format("校验失败, 签名[%s=%s]校验未通过, Request-Id=%s, HttpResponseBody=%s",
|
||||||
signature, response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response)));
|
SIGNATURE, signature, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String plainBody(OriginalResponse originalResponse) {
|
private String decryptResponseBodyIfNecessary(OriginalResponse originalResponse) {
|
||||||
byte[] body = originalResponse.getBody();
|
// 原始应答报文
|
||||||
|
byte[] responseBody = originalResponse.getBody();
|
||||||
|
|
||||||
// 判断是否有body加密密钥
|
// 判断是否有body加密密钥
|
||||||
if (!originalResponse.getHttpHeaders().hasHeader(SECRET_KEY)) {
|
if (!originalResponse.getHttpHeaders().hasHeader(SECRET_KEY)) {
|
||||||
return Strings.toStr(body);
|
// 明文直接返回
|
||||||
|
return Strings.toStr(responseBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解密密钥
|
// SecretKey
|
||||||
String secretKey = originalResponse.getHttpHeaders().getHeader(SECRET_KEY);
|
String secretKey = originalResponse.getHttpHeaders().getHeader(SECRET_KEY);
|
||||||
|
// 解密密钥
|
||||||
String decryptSecretKey = getProfile().getPrivacy().getDecryptor().decrypt(secretKey);
|
String decryptSecretKey = getProfile().getPrivacy().getDecryptor().decrypt(secretKey);
|
||||||
// 解密body
|
// 解密body
|
||||||
return getProfile().getPrivacy().getSecretCipher().decrypt(Strings.toBytes(decryptSecretKey), body);
|
return getProfile().getPrivacy().getSecretCipher().decrypt(Strings.toBytes(decryptSecretKey), responseBody);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ import com.czcb.scfs.api.core.http.client.TestVerifier;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -23,6 +24,7 @@ import java.util.Map;
|
||||||
import static com.czcb.scfs.api.core.Constants.TIMESTAMP;
|
import static com.czcb.scfs.api.core.Constants.TIMESTAMP;
|
||||||
|
|
||||||
class DefaultValidatorTest {
|
class DefaultValidatorTest {
|
||||||
|
// 创建配置文件
|
||||||
Profile buildProfile() {
|
Profile buildProfile() {
|
||||||
PrivateKey privateKey = KeyText.loadTestPrivateKeyRSA();
|
PrivateKey privateKey = KeyText.loadTestPrivateKeyRSA();
|
||||||
X509Certificate certificate = KeyText.loadTestRSA();
|
X509Certificate certificate = KeyText.loadTestRSA();
|
||||||
|
|
@ -83,9 +85,51 @@ class DefaultValidatorTest {
|
||||||
.statusCode(500)
|
.statusCode(500)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Assertions.assertThrows(ValidationException.class, () -> {
|
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.isInvalidHttpCode(response));
|
||||||
defaultValidator.isInvalidHttpCode(response);
|
Assertions.assertEquals("校验失败, HttpStatusCode=500, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage());
|
||||||
});
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isInvalidHttpCodeError1xx() {
|
||||||
|
DefaultValidator defaultValidator = new DefaultValidator(buildProfile());
|
||||||
|
|
||||||
|
Map<String, String> 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<String, String> 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
|
@Test
|
||||||
|
|
@ -108,8 +152,9 @@ class DefaultValidatorTest {
|
||||||
defaultValidator.validateTimestamp(response);
|
defaultValidator.validateTimestamp(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 没有时间戳案例
|
||||||
@Test
|
@Test
|
||||||
void validateTimestampError() {
|
void validateTimestampNoneError() {
|
||||||
DefaultValidator defaultValidator = new DefaultValidator(buildProfile());
|
DefaultValidator defaultValidator = new DefaultValidator(buildProfile());
|
||||||
|
|
||||||
Map<String, String> headers = new HashMap<>();
|
Map<String, String> headers = new HashMap<>();
|
||||||
|
|
@ -124,9 +169,30 @@ class DefaultValidatorTest {
|
||||||
.statusCode(500)
|
.statusCode(500)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Assertions.assertThrows(TimestampException.class, () -> {
|
TimestampException validationException = Assertions.assertThrows(TimestampException.class, () -> defaultValidator.validateTimestamp(response));
|
||||||
defaultValidator.validateTimestamp(response);
|
Assertions.assertEquals("校验失败, 时间戳[X-SCFS-Timestamp]不存在, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage());
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// 时间戳错误案例
|
||||||
|
@Test
|
||||||
|
void validateTimestampError() {
|
||||||
|
DefaultValidator defaultValidator = new DefaultValidator(buildProfile());
|
||||||
|
|
||||||
|
Map<String, String> 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
|
@Test
|
||||||
|
|
@ -142,14 +208,77 @@ class DefaultValidatorTest {
|
||||||
.build())
|
.build())
|
||||||
.body("123")
|
.body("123")
|
||||||
.headers(headers)
|
.headers(headers)
|
||||||
.statusCode(500)
|
.statusCode(200)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Assertions.assertThrows(ValidationException.class, () -> {
|
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.validateResponseSignature(response, new DefaultChannel.Builder()
|
||||||
defaultValidator.validateResponseSignature(response, new DefaultChannel.Builder()
|
.channelNo("1010")
|
||||||
.channelNo("1010")
|
.appNo("1111")
|
||||||
.appNo("1111")
|
.build()));
|
||||||
.build());
|
Assertions.assertEquals("校验失败, 签名[X-SCFS-Signature]不存在, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage());
|
||||||
});
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateResponseSignatureError() {
|
||||||
|
DefaultValidator defaultValidator = new DefaultValidator(buildProfile());
|
||||||
|
|
||||||
|
Map<String, String> 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<String, String> 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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue