Apache HttpClient 4.3의 SSL 인증서 무시
Apache HttpClient 4.3의 SSL 인증서를 무시하는 방법(모두 신뢰)
SO에서 찾은 답변은 모두 이전 버전을 다루고 API가 변경되었습니다.
관련:
- Apache HttpClient 4.0에서 SSL 인증서 오류를 무시하는 방법
- Apache HttpClient를 사용하여 잘못된 SSL 인증서를 처리하는 방법
- 스프링을 사용하여 개발 시 모든 인증서를 신뢰해야 함
- Java에서의 SSL 증명서 오류 무시
편집:
- 테스트용입니다.어린이 여러분, 집에서(또는 프로덕션에서) 시도하지 마십시오.
다음 코드는 자체 서명된 인증서를 신뢰하는 데 사용됩니다.클라이언트를 작성할 때는 TrustSelfSignedStrategy를 사용해야 합니다.
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
builder.build());
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(
sslsf).build();
HttpGet httpGet = new HttpGet("https://some-server");
CloseableHttpResponse response = httpclient.execute(httpGet);
try {
System.out.println(response.getStatusLine());
HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
} finally {
response.close();
}
는 음음 i i i i i i 、 。SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
를 취득하지 를 할 수 였습니다.인증기관으로부터 적절한 증명서를 취득할 필요가 없도록 자체 서명된 증명서를 사용한 테스트를 허용하는 것이 포인트였습니다.할 수 에, 「」를 하는 에, 「」를 .SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
위의 PoolingHttpClientConnectionManager 프로시저가 작동하지 않으면 커스텀 SSLContext는 무시됩니다.PoolingHttpClientConnectionManager를 작성할 때는 컨스트럭터로 socketFactoryRegistry를 전달해야 합니다.
SSLContextBuilder builder = SSLContexts.custom();
builder.loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
return true;
}
});
SSLContext sslContext = builder.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext, new X509HostnameVerifier() {
@Override
public void verify(String host, SSLSocket ssl)
throws IOException {
}
@Override
public void verify(String host, X509Certificate cert)
throws SSLException {
}
@Override
public void verify(String host, String[] cns,
String[] subjectAlts) throws SSLException {
}
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
.<ConnectionSocketFactory> create().register("https", sslsf)
.build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
socketFactoryRegistry);
CloseableHttpClient httpclient = HttpClients.custom()
.setConnectionManager(cm).build();
@mavroprovato의 답변과 더불어 자기서명뿐만 아니라 모든 증명서를 신뢰하려면 (코드 스타일로) 해야 합니다.
builder.loadTrustMaterial(null, new TrustStrategy(){
public boolean isTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
return true;
}
});
또는 (내 코드에서 직접 복사):
import javax.net.ssl.SSLContext;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.ssl.SSLContexts;
// ...
SSLContext sslContext = SSLContexts
.custom()
//FIXME to contain real trust store
.loadTrustMaterial(new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
return true;
}
})
.build();
호스트명 검증도 건너뛰려면 , 다음의 설정을 실시할 필요가 있습니다.
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(
sslsf).setSSLHostnameVerifier( NoopHostnameVerifier.INSTANCE).build();
(ALLLOW_ALL_HOSTNAME_VERIFIER는 권장되지 않습니다).
필수 경고: 이렇게 하면 안 됩니다. 모든 인증서를 수락하는 것은 좋지 않습니다.그러나 이 작업을 수행하고자 하는 드문 사용 사례가 있습니다.
앞서 설명한 코드화 메모로서 httpclient.execute()가 예외를 발생시킨 경우에도 응답을 닫을 수 있습니다.
CloseableHttpResponse response = null;
try {
response = httpclient.execute(httpGet);
System.out.println(response.getStatusLine());
HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
}
finally {
if (response != null) {
response.close();
}
}
위의 코드는 다음 방법으로 테스트되었습니다.
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
그리고 관심 있는 분들을 위해 저의 전체 테스트 세트를 소개합니다.
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.junit.Test;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLPeerUnverifiedException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class TrustAllCertificatesTest {
final String expiredCertSite = "https://expired.badssl.com/";
final String selfSignedCertSite = "https://self-signed.badssl.com/";
final String wrongHostCertSite = "https://wrong.host.badssl.com/";
static final TrustStrategy trustSelfSignedStrategy = new TrustSelfSignedStrategy();
static final TrustStrategy trustAllStrategy = new TrustStrategy(){
public boolean isTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
return true;
}
};
@Test
public void testSelfSignedOnSelfSignedUsingCode() throws Exception {
doGet(selfSignedCertSite, trustSelfSignedStrategy);
}
@Test(expected = SSLHandshakeException.class)
public void testExpiredOnSelfSignedUsingCode() throws Exception {
doGet(expiredCertSite, trustSelfSignedStrategy);
}
@Test(expected = SSLPeerUnverifiedException.class)
public void testWrongHostOnSelfSignedUsingCode() throws Exception {
doGet(wrongHostCertSite, trustSelfSignedStrategy);
}
@Test
public void testSelfSignedOnTrustAllUsingCode() throws Exception {
doGet(selfSignedCertSite, trustAllStrategy);
}
@Test
public void testExpiredOnTrustAllUsingCode() throws Exception {
doGet(expiredCertSite, trustAllStrategy);
}
@Test(expected = SSLPeerUnverifiedException.class)
public void testWrongHostOnTrustAllUsingCode() throws Exception {
doGet(wrongHostCertSite, trustAllStrategy);
}
@Test
public void testSelfSignedOnAllowAllUsingCode() throws Exception {
doGet(selfSignedCertSite, trustAllStrategy, NoopHostnameVerifier.INSTANCE);
}
@Test
public void testExpiredOnAllowAllUsingCode() throws Exception {
doGet(expiredCertSite, trustAllStrategy, NoopHostnameVerifier.INSTANCE);
}
@Test
public void testWrongHostOnAllowAllUsingCode() throws Exception {
doGet(expiredCertSite, trustAllStrategy, NoopHostnameVerifier.INSTANCE);
}
public void doGet(String url, TrustStrategy trustStrategy, HostnameVerifier hostnameVerifier) throws Exception {
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(trustStrategy);
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
builder.build());
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(
sslsf).setSSLHostnameVerifier(hostnameVerifier).build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse response = httpclient.execute(httpGet);
try {
System.out.println(response.getStatusLine());
HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
} finally {
response.close();
}
}
public void doGet(String url, TrustStrategy trustStrategy) throws Exception {
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(trustStrategy);
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
builder.build());
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(
sslsf).build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse response = httpclient.execute(httpGet);
try {
System.out.println(response.getStatusLine());
HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
} finally {
response.close();
}
}
}
baskt의 답변에 대한 작은 추가 사항:
SocketFactoryRegistry와 함께 제공된 솔루션은 PoolingHttpClientConnectionManager를 사용할 때 작동합니다.
단, 플레인 http를 통한 접속은 더 이상 기능하지 않습니다.http 프로토콜용으로 PlainConnectionSocketFactory를 추가하여 다시 작동해야 합니다.
Registry<ConnectionSocketFactory> socketFactoryRegistry =
RegistryBuilder.<ConnectionSocketFactory> create()
.register("https", sslsf)
.register("http", new PlainConnectionSocketFactory()).build();
다양한 옵션을 시험해 본 결과, 다음의 설정이 http와 https 양쪽에 대해 기능했습니다.
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
builder.build(), SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
Registry<ConnectionSocketFactory> registry = RegistryBuilder.
<ConnectionSocketFactory> create()
.register("http", new PlainConnectionSocketFactory())
.register("https", sslsf)
.build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
cm.setMaxTotal(2000);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.setConnectionManager(cm)
.build();
http-client 4.3.3을 사용하고 있습니다.compile 'org.apache.httpcomponents:httpclient:4.3.3'
보다 심플하고 짧은 작업 코드:
HTTP Client 4.3.5를 사용하고 있으며 스택오버플로우에는 거의 모든 솔루션이 존재하지만 아무것도 존재하지 않습니다.문제를 생각하고 파악한 후 완벽하게 동작하는 다음 코드에 도달합니다.HttpClient 인스턴스를 작성하기 전에 추가만 하면 됩니다.
포스트 리퀘스트에 사용하는 방법...
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
});
SSLConnectionSocketFactory sslSF = new SSLConnectionSocketFactory(builder.build(),
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslSF).build();
HttpPost postRequest = new HttpPost(url);
호출을 계속하고 일반 형식으로 HttpPost 인스턴스를 사용합니다.
다음은 "curl --insecure"에 해당하는 위의 기술을 증류한 것입니다.
HttpClient getInsecureHttpClient() throws GeneralSecurityException {
TrustStrategy trustStrategy = new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) {
return true;
}
};
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
return HttpClients.custom()
.setSSLSocketFactory(new SSLConnectionSocketFactory(
new SSLContextBuilder().loadTrustMaterial(trustStrategy).build(),
hostnameVerifier))
.build();
}
http client 4.5를 사용할 때는 javasx.net.ssl을 사용해야 했습니다.HostnameVerifier: 임의의 호스트명을 허가합니다(테스트용).결국 이렇게 된 겁니다.
CloseableHttpClient httpClient = null;
try {
SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
sslContextBuilder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
HostnameVerifier hostnameVerifierAllowAll = new HostnameVerifier()
{
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContextBuilder.build(), hostnameVerifierAllowAll);
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope("192.168.30.34", 8443),
new UsernamePasswordCredentials("root", "password"));
httpClient = HttpClients.custom()
.setSSLSocketFactory(sslSocketFactory)
.setDefaultCredentialsProvider(credsProvider)
.build();
HttpGet httpGet = new HttpGet("https://192.168.30.34:8443/axis/services/getStuff?firstResult=0&maxResults=1000");
CloseableHttpResponse response = httpClient.execute(httpGet);
int httpStatus = response.getStatusLine().getStatusCode();
if (httpStatus >= 200 && httpStatus < 300) { [...]
} else {
throw new ClientProtocolException("Unexpected response status: " + httpStatus);
}
} catch (Exception ex) {
ex.printStackTrace();
}
finally {
try {
httpClient.close();
} catch (IOException ex) {
logger.error("Error while closing the HTTP client: ", ex);
}
}
class ApacheHttpClient {
/***
* This is a https get request that bypasses certificate checking and hostname verifier.
* It uses basis authentication method.
* It is tested with Apache httpclient-4.4.
* It dumps the contents of a https page on the console output.
* It is very similar to http get request, but with the additional customization of
* - credential provider, and
* - SSLConnectionSocketFactory to bypass certification checking and hostname verifier.
* @param path String
* @param username String
* @param password String
* @throws IOException
*/
public void get(String path, String username, String password) throws IOException {
final CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultCredentialsProvider(createCredsProvider(username, password))
.setSSLSocketFactory(createGenerousSSLSocketFactory())
.build();
final CloseableHttpResponse response = httpClient.execute(new HttpGet(path));
try {
HttpEntity entity = response.getEntity();
if (entity == null)
return;
System.out.println(EntityUtils.toString(entity));
} finally {
response.close();
httpClient.close();
}
}
private CredentialsProvider createCredsProvider(String username, String password) {
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
AuthScope.ANY,
new UsernamePasswordCredentials(username, password));
return credsProvider;
}
/***
*
* @return SSLConnectionSocketFactory that bypass certificate check and bypass HostnameVerifier
*/
private SSLConnectionSocketFactory createGenerousSSLSocketFactory() {
SSLContext sslContext;
try {
sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[]{createGenerousTrustManager()}, new SecureRandom());
} catch (KeyManagementException | NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
return new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
}
private X509TrustManager createGenerousTrustManager() {
return new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] cert, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] cert, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
}
}
를 사용하고 있는 경우, 코드는 다음과 같습니다.
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null,
TrustSelfSignedStrategy.INSTANCE).build();
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
sslContext, NoopHostnameVerifier.INSTANCE);
HttpClient httpClient = HttpClients.custom()
.setDefaultCookieStore(new BasicCookieStore())
.setSSLSocketFactory(sslSocketFactory)
.build();
그 위에PoolingHttpClientConnectionManager
와 함께Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create().register("https", sslFactory).build();
다음을 사용하여 비동기 httpclient를 사용하는 경우PoolingNHttpClientConnectionManager
코드는 다음과 같아야 합니다.
SSLContextBuilder builder = SSLContexts.custom();
builder.loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
return true;
}
});
SSLContext sslContext = builder.build();
SchemeIOSessionStrategy sslioSessionStrategy = new SSLIOSessionStrategy(sslContext,
new HostnameVerifier(){
@Override
public boolean verify(String hostname, SSLSession session) {
return true;// TODO as of now allow all hostnames
}
});
Registry<SchemeIOSessionStrategy> sslioSessionRegistry = RegistryBuilder.<SchemeIOSessionStrategy>create().register("https", sslioSessionStrategy).build();
PoolingNHttpClientConnectionManager ncm = new PoolingNHttpClientConnectionManager(new DefaultConnectingIOReactor(),sslioSessionRegistry);
CloseableHttpAsyncClient asyncHttpClient = HttpAsyncClients.custom().setConnectionManager(ncm).build();
asyncHttpClient.start();
(바케스트의 답변에 직접 코멘트를 추가했을지도 모르지만, 평가 포인트가 부족합니다(논리에는 불명확합니다).
아무튼...제가 말씀드리고 싶은 것은 Pooling Connection을 명시적으로 작성/요청하지 않더라도 Pooling Connection을 이용할 수 없다는 것은 아니라는 것입니다.
왜 원래의 솔루션이 나에게 효과가 없는지 알아내려고 미칠 것 같았지만, "내 경우에는 해당되지 않는다"는 바섹트의 답변을 무시했습니다.틀렸습니다!
스택 트레이스가 낮았을 때 스택 트레이스를 보고 있었는데 중간에 Pooling Connection이 있었습니다.쾅 - 나는 그의 추가와 성공을 지쳤어!!(데모가 내일인데 너무 힘들어서) :-)
Apache HTTP 클라이언트의 모든 증명서 신뢰
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sc);
httpclient = HttpClients.custom().setSSLSocketFactory(
sslsf).build();
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
다음 코드 스니펫을 사용하여 SSL 인증 확인 없이 HttpClient 인스턴스를 가져올 수 있습니다.
private HttpClient getSSLHttpClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
LogLoader.serverLog.trace("In getSSLHttpClient()");
SSLContext context = SSLContext.getInstance("SSL");
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
context.init(null, new TrustManager[] { tm }, null);
HttpClientBuilder builder = HttpClientBuilder.create();
SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory(context);
builder.setSSLSocketFactory(sslConnectionFactory);
PlainConnectionSocketFactory plainConnectionSocketFactory = new PlainConnectionSocketFactory();
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", sslConnectionFactory).register("http", plainConnectionSocketFactory).build();
PoolingHttpClientConnectionManager ccm = new PoolingHttpClientConnectionManager(registry);
ccm.setMaxTotal(BaseConstant.CONNECTION_POOL_SIZE);
ccm.setDefaultMaxPerRoute(BaseConstant.CONNECTION_POOL_SIZE);
builder.setConnectionManager((HttpClientConnectionManager) ccm);
builder.disableRedirectHandling();
LogLoader.serverLog.trace("Out getSSLHttpClient()");
return builder.build();
}
위의 @divbyzero에서 응답하도록 약간 조정하여 음파탐지 보안 경고를 수정합니다.
CloseableHttpClient getInsecureHttpClient() throws GeneralSecurityException {
TrustStrategy trustStrategy = (chain, authType) -> true;
HostnameVerifier hostnameVerifier = (hostname, session) -> hostname.equalsIgnoreCase(session.getPeerHost());
return HttpClients.custom()
.setSSLSocketFactory(new SSLConnectionSocketFactory(new SSLContextBuilder().loadTrustMaterial(trustStrategy).build(), hostnameVerifier))
.build();
}
처음에는 신뢰 전략을 사용하여 localhost에 대해 비활성화할 수 있었지만 나중에 Noop Hostname Verifier를 추가했습니다.이제 localhost와 임의의 머신명 모두에서 동작합니다.
SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
언급URL : https://stackoverflow.com/questions/19517538/ignoring-ssl-certificate-in-apache-httpclient-4-3
'programing' 카테고리의 다른 글
팬더는 열 이름만 사용하여 빈 데이터 프레임을 생성합니다. (0) | 2022.11.07 |
---|---|
PHP 추상 속성 (0) | 2022.11.07 |
PHP 페이지 리다이렉트 (0) | 2022.11.07 |
어레이를 JSON으로 변환 (0) | 2022.11.07 |
matplotlib 범례 마커 한 번만 (0) | 2022.11.07 |