Spring RestTemplate에서의 SSL 증명서 검증 디세블화
2대의 스프링 기반의 웹 앱 A와 B를 2대의 다른 머신에 탑재하고 있습니다.
웹 앱 A에서 웹 앱 B로 HTTPS 콜을 하고 싶은데, 머신 B에서 자기 서명 증명서를 사용하고 있습니다.따라서 HTTPS 요구는 실패합니다.
봄에 RestTemplate를 사용할 때 HTTPS 증명서 검증을 비활성화하려면 어떻게 해야 합니까?웹 앱 A와 B가 모두 내부 네트워크 내에 있지만 데이터 전송은 HTTPS를 통해 이루어져야 하므로 검증을 해제하고 싶습니다.
@Bean
public RestTemplate restTemplate()
throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
기본적으로 필요한 것은 모든 증명서를 신뢰하는 커스텀 TrustStrategy 를 사용하는 것과 호스트명 검증을 디세블로 하기 위해서 NoopHostnameVerifier() 를 사용하는 것입니다.다음은 코드와 관련된 모든 Import입니다.
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
public RestTemplate getRestTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
커스텀을 추가해야 합니다.HostnameVerifier
class는 증명서 검증을 바이패스하고 true를 반환합니다.
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
이것은 당신의 코드에 적절히 입력되어야 합니다.
나는 간단한 방법을 찾았다.
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
사용한 Import
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
cookie를 사용하여 내 응답 추가:
public static void main(String[] args) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("username", testUser);
params.add("password", testPass);
NullHostnameVerifier verifier = new NullHostnameVerifier();
MySimpleClientHttpRequestFactory requestFactory = new MySimpleClientHttpRequestFactory(verifier , rememberMeCookie);
ResponseEntity<String> response = restTemplate.postForEntity(appUrl + "/login", params, String.class);
HttpHeaders headers = response.getHeaders();
String cookieResponse = headers.getFirst("Set-Cookie");
String[] cookieParts = cookieResponse.split(";");
rememberMeCookie = cookieParts[0];
cookie.setCookie(rememberMeCookie);
requestFactory = new MySimpleClientHttpRequestFactory(verifier,cookie.getCookie());
restTemplate.setRequestFactory(requestFactory);
}
public class MySimpleClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
private final HostnameVerifier verifier;
private final String cookie;
public MySimpleClientHttpRequestFactory(HostnameVerifier verifier ,String cookie) {
this.verifier = verifier;
this.cookie = cookie;
}
@Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setHostnameVerifier(verifier);
((HttpsURLConnection) connection).setSSLSocketFactory(trustSelfSignedSSL().getSocketFactory());
((HttpsURLConnection) connection).setAllowUserInteraction(true);
String rememberMeCookie = cookie == null ? "" : cookie;
((HttpsURLConnection) connection).setRequestProperty("Cookie", rememberMeCookie);
}
super.prepareConnection(connection, httpMethod);
}
public SSLContext trustSelfSignedSSL() {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[] { tm }, null);
SSLContext.setDefault(ctx);
return ctx;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
public class NullHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
Apache 또는 알 수 없는 패키지를 가져오지 않고 매우 간단하게 이 트릭을 수행할 수 있는 또 다른 방법입니다.
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
private void ignoreCertificates() {
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
}
RestTemplate 앞에 ignoreCertificates()를 설정합니다.
ignoreCertificates();
RestTemplate restTemplate = new RestTemplate();
HTTP Client API와 함께 사용할 수 있습니다.
public RestTemplate getRestTemplateBypassingHostNameVerifcation() {
CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return new RestTemplate(requestFactory);
}
SSL 호스트 이름 확인자를 사용하지 않도록 설정하는 전체 코드,
RestTemplate restTemplate = new RestTemplate();
//to disable ssl hostname verifier
restTemplate.setRequestFactory(new SimpleClientHttpRequestFactory() {
@Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setHostnameVerifier(new NoopHostnameVerifier());
}
super.prepareConnection(connection, httpMethod);
}
});
기본 전략을 무시하려면 restTemplate를 배선하는 클래스에 간단한 메서드를 만듭니다.
protected void acceptEveryCertificate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
};
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(
HttpClientBuilder
.create()
.setSSLContext(SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build())
.build()));
}
주의: 이 방법은 예외를 더 멀리 던질 뿐이므로 예외를 처리해야 합니다.
이 문제는 SSL 연결에 관한 것입니다.일부 리소스에 연결하려고 할 때 https 프로토콜은 보안 연결을 생성해야 합니다.즉, 브라우저와 웹 사이트 서버만이 요청 본문에 전송되는 데이터를 알 수 있습니다.이 보안은 웹 사이트에 저장되어 있으며 브라우저(또는 이 경우 Apache Http Client가 뒤에 있는 다른 클라이언트, Spring RestTemplate)에 의해 다운로드되어 호스트에 대한 첫 번째 접속으로 실현됩니다.RSA256 암호화 및 기타 유용한 기능이 많이 있습니다.하지만 결국:증명서가 등록되어 있지 않거나 무효인 경우는, 증명서 에러가 표시됩니다(HTTPS 접속은 시큐어하지 않습니다).증명서 오류를 수정하려면 웹사이트 프로바이더가 특정 웹사이트용으로 구입하거나 https://www.register.com/ssl-certificates와 같은 방법으로 수정해야 합니다.
문제를 해결하는 올바른 방법
- SSL 증명서 등록
문제를 해결하는 올바른 방법이 아니다.
Java cacerts에 SSL 인증서 가져오기(증명서 저장소)
keytool -importcert -trustcacerts -noprompt -storepass changeit -alias name -keystore "C:\Program Files\Java\jdk-11.0.2\lib\security\cacerts" -file file.cer
문제 해결 방법이 더럽다(안전하지 않다)
SSL 검증을 무시하도록 RestTemplate를 만듭니다.
@Bean public RestTemplateBuilder restTemplateBuilder(@Autowired SSLContext sslContext) { return new RestTemplateBuilder() { @Override public ClientHttpRequestFactory buildRequestFactory() { return new HttpComponentsClientHttpRequestFactory( HttpClients.custom().setSSLSocketFactory( new SSLConnectionSocketFactory(sslContext , NoopHostnameVerifier.INSTANCE)).build()); } }; } @Bean public SSLContext insecureSslContext() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException { return SSLContexts.custom() .loadTrustMaterial(null, (x509Certificates, s) -> true) .build(); }
보안: https/TLS 증명서 호스트명 체크를 무효로 합니다.다음 코드는 스프링 부트레스트 템플릿에서 동작합니다.
*HttpsURLConnection.setDefaultHostnameVerifier(
//SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
// * @deprecated (4.4) Use {@link org.apache.http.conn.ssl.NoopHostnameVerifier}
new NoopHostnameVerifier()
);*
package com.example.teocodownloader;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
public class Example {
public static void main(String[] args) {
CloseableHttpClient httpClient
= HttpClients.custom()
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();
HttpComponentsClientHttpRequestFactory requestFactory
= new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
}
}
덧붙여서, pom 파일에 다음의 의존 관계를 추가하는 것을 잊지 말아 주세요.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
HttpClient < 4.3 의 Java 코드의 예도 참조할 수 있습니다.
rest 템플릿을 사용하는 경우 이 코드를 사용할 수 있습니다.
fun getClientHttpRequestFactory(): ClientHttpRequestFactory {
val timeout = envTimeout.toInt()
val config = RequestConfig.custom()
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build()
val acceptingTrustStrategy = TrustStrategy { chain: Array<X509Certificate?>?, authType: String? -> true }
val sslContext: SSLContext = SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build()
val csf = SSLConnectionSocketFactory(sslContext)
val client = HttpClientBuilder
.create()
.setDefaultRequestConfig(config)
.setSSLSocketFactory(csf)
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build()
return HttpComponentsClientHttpRequestFactory(client)
}
@Bean
fun getRestTemplate(): RestTemplate {
return RestTemplate(getClientHttpRequestFactory())
}
@Bean
public RestTemplate restTemplate() throws Exception {
SSLContext sslContext = new SSLContextBuilder()
.loadKeyMaterial(keyStore, keyStorePassword.toCharArray(), keyStorePassword.toCharArray())
.loadTrustMaterial(trustStore, trustStorePassword.toCharArray(), (cert, authType) -> sslTrustStrategy)
.build();
HostnameVerifier hostnameVerifier = sslTrustStrategy ? new NoopHostnameVerifier() :
SSLConnectionSocketFactory.getDefaultHostnameVerifier();
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build();
HttpComponentsClientHttpRequestFactory httpComponentsHttpClientFactory =
new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(httpComponentsHttpClientFactory);
return restTemplate;
}
sslTrustStrategy = true이면
- (cert, authType) -> sslTrustStrategy에 의한 모든 증명서 신뢰
- 증명서 내의 호스트만 신뢰하는 것이 아니라 모두 신뢰합니다(NoopHostnameVerifier로 인해 신뢰하지 않으면 인증서 내의 호스트만 신뢰합니다).
언급URL : https://stackoverflow.com/questions/4072585/disabling-ssl-certificate-validation-in-spring-resttemplate
'sourcecode' 카테고리의 다른 글
Rails에서 모든 컨트롤러 파라미터를 camelCase에서 snake_case로 변환하는 가장 좋은 방법은 무엇입니까? (0) | 2023.03.25 |
---|---|
폼 7에 문의하면 HTTP 500 에러가 발생합니다. (0) | 2023.03.25 |
온클릭 속성에 백일해 변수 사용 (0) | 2023.03.25 |
캐시 방법NET Web API 요청( & use with Angular)JS $http) (0) | 2023.03.25 |
브라우저에서 mocha 테스트는 어떻게 실행합니까? (0) | 2023.03.25 |