关于HttpClient绕过SSL认证以及NTLM认证

前言

本篇文章只涉及本人在工做上使用HttpClient遇到的状况,并不会详细地展开讲如何使用HttpClient.html

image

1. 为何使用HttpClient?

一开始实际上是考虑使用RestTemplate的,但遇到的难题天然是SSL认证以及NTLM的认证.以目前的RestTemplate还作不到NTLM认证.并且使用SSL认证的过程也是挺复杂的.复杂的是:竟然仍是要借助HttpClient.apache

@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的证书信任管理器,咱们能够在这里添加咱们的证书,让该管理器知道咱们有那些证书是能够信任的.安全

该接口会有三个方法:bash

void checkClientTrusted(X509Certificate[] xcs, String str)
 
 void checkServerTrusted(X509Certificate[] xcs, String str)
 
  X509Certificate[] getAcceptedIssuers()
复制代码
  1. 第一个方法checkClientTrusted.该方法检查客户端的证书,若不信任该证书则抛出异常。因为咱们不须要对客户端进行认证,所以咱们只须要执行默认的信任管理器的这个方法。JSSE中,默认的信任管理器类为TrustManager。服务器

  2. 第二个方法checkServerTrusted.该方法检查服务器的证书,若不信任该证书一样抛出异常。经过本身实现该方法,可使之信任咱们指定的任何证书。在实现该方法时,也能够简单的不作任何处理,即一个空的函数体,因为不会抛出异常,它就会信任任何证书。dom

  3. 第三个方法getAcceptedIssusers,返回受信任的X509证书数组。socket

而咱们只须要重写这三个方法,而且不须要修改里面的内容.而后再交给HttpClient就能够实现绕过SSL认证了.ide

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();
复制代码

回顾一下步骤:函数

  1. 建立X509TrustManager对象并重写方法.
  2. 建立SSLContext实例,并交到工厂管理.
  3. 注册到HttpClient中.经过ConnectionManager最后生成httpClient.

3. 什么是NTLM?

NTLM是NT LAN Manager的缩写,这也说明了协议的来源。NTLM 是 Windows NT 早期版本的标准安全协议,Windows 2000 支持 NTLM 是为了保持向后兼容。Windows 2000内置三种基本安全协议之一。

NTLM的原理

NTLM的工做原理描述

其实我对这个了解得不是很深,由于赶上这种状况的感受不会不少,因此网上的资源也不太多. 这里只是针对HttpClient赶上NTLM认证的状况详细描述一下.有兴趣的朋友能够经过以上的连接了解下.

4. 如何使用HttpClient进行NTLM认证?

这个查阅了官网的文档.官网也给出了解决方案.

hc.apache.org/httpcompone…

须要把这几个类编写一下.

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();
}
}
复制代码
参考资料

www.jianshu.com/p/8d4e7dcb2…

blog.csdn.net/cj649934578…