前言
本篇文章只涉及本人在工作上使用HttpClient遇到的情況,並不會詳細地展開講如何使用HttpClient.
1. 為什麼使用HttpClient?
一開始其實是考慮使用RestTemplate的,但遇到的難題自然是SSL認證以及NTLM的認證.以目前的RestTemplate還做不到NTLM認證.而且使用SSL認證的過程也是挺複雜的.複雜的是:居然還是要藉助HttpClient.
@Bean
public RestTemplate buildRestTemplate(List<CustomHttpRequestInterceptor> interceptors) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
HttpComponentsClientHttpRequestFactory factory = new
HttpComponentsClientHttpRequestFactory();
factory.setConnectionRequestTimeout(requestTimeout);
factory.setConnectTimeout(connectTimeout);
factory.setReadTimeout(readTimeout);
// https
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, (X509Certificate[] x509Certificates, String s) -> true);
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(builder.build(), new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE);
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", new PlainConnectionSocketFactory())
.register("https", socketFactory).build();
PoolingHttpClientConnectionManager phccm = new PoolingHttpClientConnectionManager(registry);
phccm.setMaxTotal(200);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).setConnectionManager(phccm).setConnectionManagerShared(true).build();
factory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(factory);
List<ClientHttpRequestInterceptor> clientInterceptorList = new ArrayList<>();
for (CustomHttpRequestInterceptor i : interceptors) {
ClientHttpRequestInterceptor interceptor = i;
clientInterceptorList.add(interceptor);
}
restTemplate.setInterceptors(clientInterceptorList);
return restTemplate;
}
複製程式碼
2. 為什麼要繞過SSL認證?
至於為什麼要繞過SSL認證,因為裝證書的這些操作我並不會.同時也想試試能不能忽略這個證書認證呼叫介面.
- 首先如果想繞過證書,都必先建立X509TrustManager這個物件並且重寫它的方法.
X509TrustManager該介面是一個用於Https的證書信任管理器,我們可以在這裡新增我們的證書,讓該管理器知道我們有那些證書是可以信任的.
該介面會有三個方法:
void checkClientTrusted(X509Certificate[] xcs, String str)
void checkServerTrusted(X509Certificate[] xcs, String str)
X509Certificate[] getAcceptedIssuers()
複製程式碼
-
第一個方法checkClientTrusted.該方法檢查客戶端的證書,若不信任該證書則丟擲異常。由於我們不需要對客戶端進行認證,因此我們只需要執行預設的信任管理器的這個方法。JSSE中,預設的信任管理器類為TrustManager。
-
第二個方法checkServerTrusted.該方法檢查伺服器的證書,若不信任該證書同樣丟擲異常。通過自己實現該方法,可以使之信任我們指定的任何證書。在實現該方法時,也可以簡單的不做任何處理,即一個空的函式體,由於不會丟擲異常,它就會信任任何證書。
-
第三個方法getAcceptedIssusers,返回受信任的X509證書陣列。
而我們只需要重寫這三個方法,並且不需要修改裡面的內容.然後再交給HttpClient就可以實現繞過SSL認證了.
X509TrustManager trustManager = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] xcs, String str) {
}
@Override
public void checkServerTrusted(X509Certificate[] xcs, String str) {
}
SSLContext ctx = SSLContext.getInstance(SSLConnectionSocketFactory.SSL);
ctx.init(null, new TrustManager[]{trustManager}, null);
//生成工廠
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(ctx, NoopHostnameVerifier.INSTANCE);
//並註冊到HttpClient中
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", socketFactory).build();
HttpClientBuilder httpClientBuilder = HttpClients.custom().setConnectionManager(connectionManager);
CloseableHttpClient httpClient = httpClientBuilder.build();
複製程式碼
回顧一下步驟:
- 建立X509TrustManager物件並重寫方法.
- 建立SSLContext例項,並交到工廠管理.
- 註冊到HttpClient中.通過ConnectionManager最後生成httpClient.
3. 什麼是NTLM?
NTLM是NT LAN Manager的縮寫,這也說明了協議的來源。NTLM 是 Windows NT 早期版本的標準安全協議,Windows 2000 支援 NTLM 是為了保持向後相容。Windows 2000內建三種基本安全協議之一。
其實我對這個瞭解得不是很深,因為遇上這種情況的感覺不會很多,所以網上的資源也不太多. 這裡只是針對HttpClient遇上NTLM認證的情況詳細描述一下.有興趣的朋友可以通過以上的連結瞭解下.
4. 如何使用HttpClient進行NTLM認證?
這個查閱了官網的文件.官網也給出瞭解決方案.
需要把這幾個類編寫一下.
JCIFSEngine:
public final class JCIFSEngine implements NTLMEngine {
private static final int TYPE_1_FLAGS =
NtlmFlags.NTLMSSP_NEGOTIATE_56 |
NtlmFlags.NTLMSSP_NEGOTIATE_128 |
NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2 |
NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
NtlmFlags.NTLMSSP_REQUEST_TARGET;
@Override
public String generateType1Msg(final String domain, final String workstation) throws NTLMEngineException {
final Type1Message type1Message = new Type1Message(TYPE_1_FLAGS, domain, workstation);
return Base64.encode(type1Message.toByteArray());
}
@Override
public String generateType3Msg(final String username, final String password,
final String domain, final String workstation, final String challenge)
throws NTLMEngineException {
Type2Message type2Message;
try {
type2Message = new Type2Message(Base64.decode(challenge));
} catch (final IOException exception) {
throw new NTLMEngineException("Invalid NTLM type 2 message", exception);
}
final int type2Flags = type2Message.getFlags();
final int type3Flags = type2Flags
& (0xffffffff ^ (NtlmFlags.NTLMSSP_TARGET_TYPE_DOMAIN | NtlmFlags.NTLMSSP_TARGET_TYPE_SERVER));
final Type3Message type3Message = new Type3Message(type2Message, password, domain,
username, workstation, type3Flags);
return Base64.encode(type3Message.toByteArray());
}
}
複製程式碼
JCIFSNTLMSchemeFactory:
public class JCIFSNTLMSchemeFactory implements AuthSchemeProvider {
public AuthScheme create(final HttpContext context){
return new NTLMScheme(new JCIFSEngine());
}
}
複製程式碼
最後就在HttpClient註冊:
Registry<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.NTLM, new JCIFSNTLMSchemeFactory())
.register(AuthSchemes.BASIC, new BasicSchemeFactory())
.register(AuthSchemes.DIGEST, new DigestSchemeFactory())
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory())
.register(AuthSchemes.KERBEROS, new KerberosSchemeFactory())
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultAuthSchemeRegistry(authSchemeRegistry)
.build();
複製程式碼
最後就同時使用繞過SSL驗證以及NTLM驗證:
private static PoolingHttpClientConnectionManager connectionManager;
private static RequestConfig requestConfig;
private static Registry<AuthSchemeProvider> authSchemeRegistry;
private static Registry<ConnectionSocketFactory> socketFactoryRegistry;
private static CredentialsProvider credsProvider;
public void init() {
try {
X509TrustManager trustManager = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] xcs, String str) {
}
@Override
public void checkServerTrusted(X509Certificate[] xcs, String str) {
}
};
SSLContext ctx = SSLContext.getInstance(SSLConnectionSocketFactory.SSL);
ctx.init(null, new TrustManager[]{trustManager}, null);
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(ctx, NoopHostnameVerifier.INSTANCE);
NTCredentials creds = new NTCredentials("使用者名稱", "密碼", "工作站(workstation)", "域名");
credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY, creds);
socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", socketFactory).build();
connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
connectionManager.setMaxTotal(18);
connectionManager.setDefaultMaxPerRoute(6);
requestConfig = RequestConfig.custom()
.setSocketTimeout(30000)
.setConnectTimeout(30000)
.build();
authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.NTLM, new JCIFSNTLMSchemeFactory())
.register(AuthSchemes.BASIC, new BasicSchemeFactory())
.register(AuthSchemes.DIGEST, new DigestSchemeFactory())
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory())
.register(AuthSchemes.KERBEROS, new KerberosSchemeFactory())
.build();
} catch (Exception e) {
e.printStackTrace();
}
}
複製程式碼