Я пытаюсь создать интеграционные тесты, либо по mockmvc , либо testresttemplate , но, к сожалению, я не нашел ни одного примера в Интернете, который моделирует такой случай Полем Я некоторое время застрял с этим и ничего не достиг.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ClientCertificateValidationFilterTests {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@BeforeEach
public void setup() throws Exception {
String TRUSTSTORE_PATH = "truststore.p12";
String KEYSTORE_PATH = "keystore.p12";
String KEYSTORE_PASSWORD = "clientkeystore";
String TRUSTSTORE_PASSWORD = "servertruststore";
SSLContext sslContext = createSSLContext(KEYSTORE_PATH, KEYSTORE_PASSWORD, TRUSTSTORE_PATH, TRUSTSTORE_PASSWORD);
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
HttpClientConnectionManager connectionManager =
PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(socketFactory).build();
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder()
.requestFactory(() -> factory)
.rootUri("https://localhost:" + port);
this.restTemplate = new TestRestTemplate(restTemplateBuilder,
null,
null,
TestRestTemplate.HttpClientOption.SSL);
}
@Test
public void test() throws Exception {
String url = "https://localhost:" + port + "/api/test";
ResponseEntity response = this.restTemplate.exchange(
url,
HttpMethod.GET,
null,
String.class
);
Assertions.assertEquals(200, response.getStatusCode().value());
}
private SSLContext createSSLContext(String keystorePath, String keystorePassword,
String truststorePath, String truststorePassword) throws Exception {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream(new ClassPathResource(keystorePath).getFile()), keystorePassword.toCharArray());
KeyStore trustStore = KeyStore.getInstance("PKCS12");
trustStore.load(new FileInputStream(new ClassPathResource(truststorePath).getFile()), truststorePassword.toCharArray());
return SSLContexts.custom()
.loadKeyMaterial(keyStore, keystorePassword.toCharArray())
.loadTrustMaterial(trustStore, null)
.build();
}
}
< /code>
Вот вывод консоли при запуске предыдущего теста: < /p>
org.springframework.web.client.ResourceAccessException: I/O error on HEAD request for "https://localhost:56110/test": Received fatal alert: bad_certificate
at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:915)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:895)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:790)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:672)
at org.springframework.boot.test.web.client.TestRestTemplate.exchange(TestRestTemplate.java:710)
at com.ahmed.demo.ClientCertificateValidationFilterTests.test(ClientCertificateValidationFilterTests.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:130)
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:370)
at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:287)
at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:209)
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1509)
at java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1480)
at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:1066)
at org.apache.hc.core5.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:149)
at org.apache.hc.core5.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280)
at org.apache.hc.core5.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:247)
at org.apache.hc.core5.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:54)
at org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:304)
at org.apache.hc.core5.http.impl.io.HttpRequestExecutor.execute(HttpRequestExecutor.java:175)
at org.apache.hc.core5.http.impl.io.HttpRequestExecutor.execute(HttpRequestExecutor.java:218)
at org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager$InternalConnectionEndpoint.execute(PoolingHttpClientConnectionManager.java:712)
at org.apache.hc.client5.http.impl.classic.InternalExecRuntime.execute(InternalExecRuntime.java:216)
at org.apache.hc.client5.http.impl.classic.MainClientExec.execute(MainClientExec.java:116)
at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
at org.apache.hc.client5.http.impl.classic.ConnectExec.execute(ConnectExec.java:188)
at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
at org.apache.hc.client5.http.impl.classic.ProtocolExec.execute(ProtocolExec.java:192)
at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
at org.apache.hc.client5.http.impl.classic.HttpRequestRetryExec.execute(HttpRequestRetryExec.java:96)
at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
at org.apache.hc.client5.http.impl.classic.ContentCompressionExec.execute(ContentCompressionExec.java:152)
at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
at org.apache.hc.client5.http.impl.classic.RedirectExec.execute(RedirectExec.java:115)
at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
at org.apache.hc.client5.http.impl.classic.InternalHttpClient.doExecute(InternalHttpClient.java:170)
at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:87)
at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:55)
at org.apache.hc.client5.http.classic.HttpClient.executeOpen(HttpClient.java:183)
at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:99)
at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:70)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:889)
... 7 more
Имейте в виду, что я прикрепляю правильный TrustStore.p12 , keystore.p12 , и я уверен, что они работают правильно, как они есть Уже протестировано на моем терминале с использованием команды Curl в запущенном приложении, и те же значения, используемые внутри Application.yaml , используются для Test/Application.yaml .
Я попробовал Mockmvc Но, к сожалению, не нашел способ настроить его на использование контекста SSL.
У меня есть приложение Spring Boot, где я реализовал аутентификацию MTLS, включив следующие свойства: < /p> [code]server: port: 8080 ssl: enabled: true client-auth: need key-store: 'classpath:keystore.p12' key-store-password: serverkeystore key-store-type: PKCS12 trust-store: 'classpath:truststore.p12' trust-store-type: PKCS12 trust-store-password: servertruststore subject-validation: enabled: true allowed-list: test1.com,test.test1.com cn-pattern: "CN=([^,]+)" < /code> Также я добавил следующую реализацию для сертификата CN и SAN Validation: < /p> @Component @Order(Ordered.HIGHEST_PRECEDENCE) @Slf4j @ConditionalOnProperty( name = "server.ssl.subject-validation.enabled", havingValue = "true" ) @RequiredArgsConstructor public class ClientCertificateValidationFilter implements Filter {
@Value("#{'${server.ssl.subject-validation.allowed-list:[]}'.split(',')}") private final List clientCnOrSanAllowedList; @Value("${server.ssl.subject-validation.cn-pattern:CN=([^,]+)}") private String cnValidationPattern; private Pattern cnPattern;
private boolean validateClientSan(X509Certificate x509Certificate) throws CertificateParsingException { if(Objects.nonNull(x509Certificate.getSubjectAlternativeNames())) { log.debug("[validateClientSan()] Client SANs: {}", x509Certificate.getSubjectAlternativeNames()); return x509Certificate.getSubjectAlternativeNames().stream() .anyMatch(sanList -> sanList.stream() .anyMatch(clientCnOrSanAllowedList::equals)); } return false; } } [/code] Я пытаюсь создать интеграционные тесты, либо по mockmvc , либо testresttemplate , но, к сожалению, я не нашел ни одного примера в Интернете, который моделирует такой случай Полем Я некоторое время застрял с этим и ничего не достиг.[code]@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class ClientCertificateValidationFilterTests {
return SSLContexts.custom() .loadKeyMaterial(keyStore, keystorePassword.toCharArray()) .loadTrustMaterial(trustStore, null) .build(); } } < /code> Вот вывод консоли при запуске предыдущего теста: < /p> org.springframework.web.client.ResourceAccessException: I/O error on HEAD request for "https://localhost:56110/test": Received fatal alert: bad_certificate
at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:915) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:895) at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:790) at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:672) at org.springframework.boot.test.web.client.TestRestTemplate.exchange(TestRestTemplate.java:710) at com.ahmed.demo.ClientCertificateValidationFilterTests.test(ClientCertificateValidationFilterTests.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:130) at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117) at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:370) at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:287) at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:209) at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172) at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1509) at java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1480) at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:1066) at org.apache.hc.core5.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:149) at org.apache.hc.core5.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280) at org.apache.hc.core5.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:247) at org.apache.hc.core5.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:54) at org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:304) at org.apache.hc.core5.http.impl.io.HttpRequestExecutor.execute(HttpRequestExecutor.java:175) at org.apache.hc.core5.http.impl.io.HttpRequestExecutor.execute(HttpRequestExecutor.java:218) at org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager$InternalConnectionEndpoint.execute(PoolingHttpClientConnectionManager.java:712) at org.apache.hc.client5.http.impl.classic.InternalExecRuntime.execute(InternalExecRuntime.java:216) at org.apache.hc.client5.http.impl.classic.MainClientExec.execute(MainClientExec.java:116) at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51) at org.apache.hc.client5.http.impl.classic.ConnectExec.execute(ConnectExec.java:188) at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51) at org.apache.hc.client5.http.impl.classic.ProtocolExec.execute(ProtocolExec.java:192) at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51) at org.apache.hc.client5.http.impl.classic.HttpRequestRetryExec.execute(HttpRequestRetryExec.java:96) at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51) at org.apache.hc.client5.http.impl.classic.ContentCompressionExec.execute(ContentCompressionExec.java:152) at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51) at org.apache.hc.client5.http.impl.classic.RedirectExec.execute(RedirectExec.java:115) at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51) at org.apache.hc.client5.http.impl.classic.InternalHttpClient.doExecute(InternalHttpClient.java:170) at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:87) at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:55) at org.apache.hc.client5.http.classic.HttpClient.executeOpen(HttpClient.java:183) at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:99) at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:70) at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:889) ... 7 more [/code] Имейте в виду, что я прикрепляю правильный TrustStore.p12 , keystore.p12 , и я уверен, что они работают правильно, как они есть Уже протестировано на моем терминале с использованием команды Curl в запущенном приложении, и те же значения, используемые внутри Application.yaml , используются для Test/Application.yaml .
Я попробовал Mockmvc Но, к сожалению, не нашел способ настроить его на использование контекста SSL.