HttpClient配置SSL

先定义一个properties属性类:

@Component
@ConfigurationProperties(prefix = "dc.security.https.httpclient")
public class HttpClientProperties {
    /**
     * 是否开启服务端HTTPS证书校验
     */
    private boolean enabled = true;
    /**
     * 是否发送客户端证书
     */
    private boolean clientCert = true;
    /**
     * 是否支持eureka的HTTPS注册
     */
    private boolean eureka = true;
    /**
     * CA根证书密钥库文件
     */
    private String caRootCertKeyStore;
    /**
     * CA根证书密钥库密码
     */
    private String caRootCertPassword;
    /**
     * 客户端证书库文件
     */
    private String clientCertKeyStore;
    /**
     * 客户端证书库密码
     */
    private String clientCertPassword;
    /**
     * 建立连接的超时时间
     */
    private int connectTimeout = 20000;
    /**
     * 连接不够用的等待时间
     */
    private int requestTimeout = 20000;
    /**
     * 每次请求等待返回的超时时间
     */
    private int socketTimeout = 30000;
    /**
     * 每个主机最大连接数
     */
    private int defaultMaxPerRoute = 100;
    /**
     * 最大连接数
     */
    private int maxTotalConnections = 300;
    /**
     * 连接保持活跃的时间(Keep-Alive)
     */
    private int defaultKeepAliveTimeMillis = 20000;
    /**
     * 空闲连接的生存时间
     */
    private int closeIdleConnectionWaitTimeSecs = 30;
}

然后定义Spring配置类,同时支持普通服务间HTTPS调用以及Eureka服务的HTTS注册:

@Configuration
@ConditionalOnProperty(value = "dc.security.https.httpclient.enabled", havingValue = "true")
@EnableScheduling
@EnableConfigurationProperties({HttpClientProperties.class})
@Order(100)
public class SecurityHttpClientConfig {

    private static final Logger logger = LoggerFactory.getLogger(SecurityHttpClientConfig.class);

    @Autowired
    private HttpClientProperties properties;
    @Autowired
    private ICrlService crlService;

    @Bean
    @LoadBalanced
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
        return restTemplateBuilder.build();
    }

    @Bean
    public DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs() throws Exception {
        logger.info("DiscoveryClient init ...");
        EurekaJerseyClientImpl.EurekaJerseyClientBuilder builder = new EurekaJerseyClientImpl.EurekaJerseyClientBuilder();
        builder.withClientName("eureka-client");
        builder.withCustomSSL(sslContextEureka());
        builder.withMaxTotalConnections(10);
        builder.withMaxConnectionsPerHost(10);
        DiscoveryClient.DiscoveryClientOptionalArgs args = new DiscoveryClient.DiscoveryClientOptionalArgs();
        args.setEurekaJerseyClient(builder.build());
        return args;
    }

    @Bean
    @DependsOn(value = {"customRestTemplateCustomizer"})
    public RestTemplateBuilder restTemplateBuilder(CloseableHttpClient httpClient,
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) {
        RestTemplateBuilder builder = new RestTemplateBuilder(customRestTemplateCustomizer(httpClient));

        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(
            Charset.forName("utf-8"));
        FormHttpMessageConverter formMessageConverter = new FormHttpMessageConverter();
        messageConverters.add(stringHttpMessageConverter);
        messageConverters.add(jackson2HttpMessageConverter);
        messageConverters.add(formMessageConverter);
        builder.messageConverters(messageConverters);

        return builder;
    }

    @Bean
    public RestTemplateCustomizer customRestTemplateCustomizer(CloseableHttpClient httpClient) {
        return restTemplate -> {
            HttpComponentsClientHttpRequestFactory rf = new HttpComponentsClientHttpRequestFactory();
            rf.setHttpClient(httpClient);
            restTemplate.setRequestFactory(rf);
        };
    }

    @Bean
    public PoolingHttpClientConnectionManager poolingConnectionManager(SSLContext sslContext) {
        SSLConnectionSocketFactory sslsf;
        try {
            HostnameVerifier hostnameVerifier = (s, sslSession) -> {
                try {
                    Certificate[] certs = sslSession.getPeerCertificates();
                    X509Certificate x509 = (X509Certificate) certs[0];
                } catch (SSLPeerUnverifiedException e) {
                    return false;
                }
                return true;
            };
            sslsf = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
        } catch (Exception e) {
            logger.error("Pooling Connection Manager Initialisation failure");
            throw new RuntimeException("Pooling Connection Manager Initialisation failure", e);
        }
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
            .<ConnectionSocketFactory>create()
            .register("https", sslsf)
            .register("http", new PlainConnectionSocketFactory())
            .build();

        PoolingHttpClientConnectionManager poolingConnectionManager
            = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        poolingConnectionManager.setMaxTotal(properties.getMaxTotalConnections());  //最大连接数
        poolingConnectionManager.setDefaultMaxPerRoute(properties.getDefaultMaxPerRoute());  //同路由并发数
        return poolingConnectionManager;
    }

    private SSLContext sslContextEureka() throws Exception {
        // 加载服务端信任Keystore
        X509TrustManager origTrustmanager = getX509TrustManager();
        TrustManager[] wrappedTrustManagers = new TrustManager[]{
            new X509TrustManager() {
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    logger.info(">>>>>>>>>>>>>> sslContextEureka getAcceptedIssuers 00000000000000000 start ...");
                    return origTrustmanager.getAcceptedIssuers();
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
                    logger.info(">>>>>>>>>>>>>> sslContextEureka checkClientTrusted 111111111111 start ...");
                }

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
                    logger.info(">>>>>>>>>>>>>> sslContextEureka checkServerTrusted 222222222222222 start ...");
                }
            }
        };

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, wrappedTrustManagers, new java.security.SecureRandom());
        return sslContext;
    }

    private X509TrustManager getX509TrustManager()
        throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
        // 加载服务端信任根证书库
        KeyStore trustKeyStore = KeyStore.getInstance("PKCS12");
        trustKeyStore.load(new FileInputStream(
            properties.getCaRootCertKeyStore()), properties.getCaRootCertPassword().toCharArray());
        // 初始化服务端信任证书管理器
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(trustKeyStore);
        TrustManager[] trustManagers = tmf.getTrustManagers();
        return (X509TrustManager) trustManagers[0];
    }

    private KeyManager[] getX509KeyManagers()
        throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
        // 加载客户端证书库
        KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");
        clientKeyStore.load(new FileInputStream(
            properties.getClientCertKeyStore()), properties.getClientCertPassword().toCharArray());
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(clientKeyStore, properties.getClientCertPassword().toCharArray());
        return keyManagerFactory.getKeyManagers();
    }

    @Bean
    public SSLContext sslContext() throws Exception {
        // 加载服务端信任Keystore
        X509TrustManager origTrustmanager = getX509TrustManager();
        TrustManager[] wrappedTrustManagers = new TrustManager[]{
            new X509TrustManager() {
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    logger.info(">>>>>>>>>>>>>> getAcceptedIssuers 00000000000000000 start ...");
                    return origTrustmanager.getAcceptedIssuers();
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
                    logger.info(">>>>>>>>>>>>>> checkClientTrusted 111111111111 start ...");
                    origTrustmanager.checkClientTrusted(certs, authType);
                }

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
                    logger.info(">>>>>>>>>>>>>> checkServerTrusted 222222222222222 start ...");
                    try {
                        //Original trust checking
                        origTrustmanager.checkServerTrusted(certs, authType);

                        //Check revocation with each cert
                        //from docs: CertificateException - if the certificate chain is not trusted by this TrustManager.
                        for (X509Certificate cert : certs) {
                            String certSerial = cert.getSerialNumber().toString(16).toUpperCase();
                            if (crlService.getCrlList().contains(certSerial)) {
                                logger.error("cert serial={} is in crl list, bad", certSerial);
                                throw new CertificateException();
                            }
                        }
                    } catch (CertificateExpiredException e) {
                        logger.error("cert expired error");
                        throw new CertificateExpiredException();
                    }
                }
            }
        };

        SSLContext sslContext = SSLContext.getInstance("TLS");
        if (properties.isClientCert()) { // 如果开启客户端证书校验,则需要发送客户端证书
            sslContext.init(getX509KeyManagers(), wrappedTrustManagers, new java.security.SecureRandom());
        } else { // 否则不需要发送客户端证书
            sslContext.init(null, wrappedTrustManagers, new java.security.SecureRandom());
        }
        return sslContext;
    }

    @Bean
    public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
        return (response, httpContext) -> {
            HeaderElementIterator it = new BasicHeaderElementIterator
                (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (it.hasNext()) {
                HeaderElement he = it.nextElement();
                String param = he.getName();
                String value = he.getValue();
                if (value != null && param.equalsIgnoreCase("timeout")) {
                    return Long.parseLong(value) * 1000;
                }
            }
            return properties.getDefaultKeepAliveTimeMillis();
        };
    }

    @Bean
    public CloseableHttpClient httpClient(SSLContext sslContext) {
        RequestConfig requestConfig = RequestConfig.custom()
            .setConnectionRequestTimeout(properties.getRequestTimeout())
            .setConnectTimeout(properties.getConnectTimeout())
            .setSocketTimeout(properties.getSocketTimeout()).build();

        return HttpClients.custom()
            .setDefaultRequestConfig(requestConfig)
            .setConnectionManager(poolingConnectionManager(sslContext))
            .setKeepAliveStrategy(connectionKeepAliveStrategy())
            .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true))
            .build();
    }

    /**
     * You can't set an idle connection timeout in the config for Apache HTTP Client.
     * The reason is that there is a performance overhead in doing so.
     * <properties>
     * The documentation clearly states why, and gives an example of an idle connection monitor implementation you can
     * copy.
     * Essentially this is another thread that you run to periodically call closeIdleConnections on
     * HttpClientConnectionManager
     * <properties>
     * http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html
     *
     * @param connectionManager 连接管理器
     * @return 线程
     */
    @Bean
    public Runnable idleConnectionMonitor(PoolingHttpClientConnectionManager connectionManager) {
        return new Runnable() {
            @Override
            @Scheduled(fixedDelay = 10000)
            public void run() {
                try {
                    if (connectionManager != null) {
                        logger.trace("run IdleConnectionMonitor - Closing expired and idle connections...");
                        connectionManager.closeExpiredConnections();
                        connectionManager
                            .closeIdleConnections(properties.getCloseIdleConnectionWaitTimeSecs(), TimeUnit.SECONDS);
                    } else {
                        logger.trace("run IdleConnectionMonitor - Http Client Connection manager is not initialised");
                    }
                } catch (Exception e) {
                    logger.error("run IdleConnectionMonitor - Exception occurred. msg={}, e={}", e.getMessage(), e);
                }
            }
        };
    }

    @Bean
    @ConditionalOnMissingBean(TaskScheduler.class)
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setThreadNamePrefix("poolScheduler");
        scheduler.setPoolSize(10);
        return scheduler;
    }

    @Bean
    @ConditionalOnMissingBean(ICrlService.class)
    public ICrlService crlService() {
        return HashSet::new;
    }
}

转载于:https://my.oschina.net/yidao620c/blog/3044043

  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值