tomcat & java 强制使用tlsv1.2协议

1 修改代码解决:

# "TLSv1", "TLSv1.1", "TLSv1.2"

static {
    // 初始化线程池
    RequestConfig params = RequestConfig.custom().setConnectTimeout(3000).setConnectionRequestTimeout(1000).setSocketTimeout(4000)
            .setExpectContinueEnabled(true).build();

    SSLContext sslContext = createTLS12();
    // 关闭了主机名验证: (原因:不关闭的情况下请求https需要公网域名,不能直接是ip地址的方式请求)
	//SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);

    //2020年3月23日 22:57 lifeng
    SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE);

    // 设置协议http和https对应的处理socket链接工厂的对象
    Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
            .register("http", PlainConnectionSocketFactory.INSTANCE)
            .register("https", sslFactory)
            .build();

    PoolingHttpClientConnectionManager pccm = new PoolingHttpClientConnectionManager(registry);
    // 连接池最大并发连接数
    pccm.setMaxTotal(300);
    // 单路由最大并发数
    pccm.setDefaultMaxPerRoute(100);

    HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {
        @Override
        public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
            // 重试1次,从1开始
            if (executionCount > 1) {
                return false;
            }
            if (exception instanceof NoHttpResponseException) {
                LOGGER.info("[NoHttpResponseException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]");
                return true;
            } else if (exception instanceof SocketException) {
                LOGGER.info("[SocketException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]");
                return true;
            }
            return false;
        }
    };
    httpclient = HttpClients.custom().setConnectionManager(pccm).setDefaultRequestConfig(params).setRetryHandler(retryHandler)
            .build();
}

2 整个类的代码

package cn.richinfo.common.util;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLInitializationException;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.SocketException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Map.Entry;

/**
 * http请求工具类
 */
public class HttpClientUtilSSL {

    private final static Logger LOGGER = LoggerFactory.getLogger(HttpClientUtilSSL.class);

    private final static String UTF8 = "UTF-8";

    public static CloseableHttpClient httpclient = null;


//    /**
//     *  参考 httpclient的 {@link org.apache.http.ssl.SSLContexts}<br/>
//     *
//     * Creates default SSL context based on system properties. This method obtains
//     * default SSL context by calling {@code SSLContext.getInstance("Default")}.
//     * Please note that {@code Default} algorithm is supported as of Java 6.
//     * This method will fall back onto {@link #createDefault()} when
//     * {@code Default} algorithm is not available.
//     *
//     * @return default system SSL context
//     */
//    public static SSLContext createSystemDefault() throws SSLInitializationException {
//        SSLContext context = null;
//        try {
//            context = createDefault();
//        } catch (final Exception ex) {
//            ex.printStackTrace();
//            //return createDefault();
//        }
//        return context;
//    }

    /**
     * 参考 httpclient的 {@link org.apache.http.ssl.SSLContexts}<br/>
     *
     * Creates default factory based on the standard JSSE trust material
     * ({@code cacerts} file in the security properties directory). System properties
     * are not taken into consideration.
     *
     * @return the default SSL socket factory
     */
    public static SSLContext createTLS12() {
        try {
            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
            // 创建信任管理器,绕过证书认证:(空实现所有方法)
            X509TrustManager trustManager = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}

                @Override
                public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };
            // 初始化SSLContext:
            sslContext.init(null, new TrustManager[]{trustManager}, null);
            return sslContext;
        }  catch (NoSuchAlgorithmException ex) {
            throw new SSLInitializationException(ex.getMessage(), ex);
        } catch (KeyManagementException ex) {
            throw new SSLInitializationException(ex.getMessage(), ex);
        }
    }


    static {
        // 初始化线程池
        RequestConfig params = RequestConfig.custom().setConnectTimeout(3000).setConnectionRequestTimeout(1000).setSocketTimeout(4000)
                .setExpectContinueEnabled(true).build();

        SSLContext sslContext = createTLS12();
        // 关闭了主机名验证: (原因:不关闭的情况下请求https需要公网域名,不能直接是ip地址的方式请求)
//        SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);

        //2020年3月23日 22:57 lifeng
        SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE);

        // 设置协议http和https对应的处理socket链接工厂的对象
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", sslFactory)
                .build();

        PoolingHttpClientConnectionManager pccm = new PoolingHttpClientConnectionManager(registry);
        // 连接池最大并发连接数
        pccm.setMaxTotal(300);
        // 单路由最大并发数
        pccm.setDefaultMaxPerRoute(100);

        HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                // 重试1次,从1开始
                if (executionCount > 1) {
                    return false;
                }
                if (exception instanceof NoHttpResponseException) {
                    LOGGER.info("[NoHttpResponseException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]");
                    return true;
                } else if (exception instanceof SocketException) {
                    LOGGER.info("[SocketException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]");
                    return true;
                }
                return false;
            }
        };
        httpclient = HttpClients.custom().setConnectionManager(pccm).setDefaultRequestConfig(params).setRetryHandler(retryHandler)
                .build();
    }

    /**
     * 发送POST请求
     *
     * @param urlToRequest             请求地址
     * @param parameters               Form形式参数键值对
     * @param headers                  请求头设置的集合键值对
     * @param connectionRequestTimeout 请求超时
     * @param connectTimeout           连接超时
     * @param socketTimeout            socket超时
     * @return
     * @throws RuntimeException 当请求阻塞,或关闭流异常时
     * @throws IOException 当请求阻塞,或关闭流异常时
     */
    public static String post(String urlToRequest, Map<String, String> parameters, Map<String, String> headers, Integer connectionRequestTimeout,
                              Integer connectTimeout, Integer socketTimeout) {

        Long startTs = System.currentTimeMillis();
        JSONObject json = new JSONObject();

        if (parameters != null && !parameters.isEmpty()) {
            for (Entry<String, String> entry : parameters.entrySet()) {
                json.put(entry.getKey(), String.valueOf(entry.getValue()));
            }
        }
        // LOGGER.info("post-req:url:{},param:{}", urlToRequest, JSONUtil.toJsonStr(parameters));
        HttpPost post = new HttpPost(urlToRequest);

        RequestConfig requestConfig = getRequestConfig(connectionRequestTimeout, connectTimeout, socketTimeout);
        post.setConfig(requestConfig);

        if (headers != null && !headers.isEmpty()) {
            // LOGGER.info("请求头的属性集合 headers: [{}]" + JSONUtil.toJsonStr(headers));
            for (Entry<String, String> entity : headers.entrySet()) {
                post.setHeader(entity.getKey(), entity.getValue());
            }
        }
        post.setEntity(new StringEntity(json.toString(), "UTF-8"));
        CloseableHttpResponse response = null;
        try {
            response = invoke(post);
        } catch (IOException e) {
            // 异常后必须释放链接,否则并发下会一直获取不到链接
            post.releaseConnection();
            LOGGER.error("[HttpClientUtils][invoke][method:" + post.getMethod() + " URI:" + post.getURI() + "] is request exception", e);
        }
        String result = getResponseString(response);
        Long endTs = System.currentTimeMillis();
        Long currentMethodCallTime = endTs - startTs;
        if (currentMethodCallTime > 5000) {
            LOGGER.warn("url:{},call time {} ms", urlToRequest, currentMethodCallTime);
            LOGGER.info("所有存活线程=" + Thread.getAllStackTraces().size());
        }
        LOGGER.info("post-rps:{}", result);
        return result;
    }

    /**
     *  构建请求超时等配置
     * @param connectionRequestTimeout
     * @param connectTimeout
     * @param socketTimeout
     * @return
     */
    private static RequestConfig getRequestConfig(Integer connectionRequestTimeout, Integer connectTimeout, Integer socketTimeout) {
        RequestConfig.Builder builder = RequestConfig.custom();
        if (connectionRequestTimeout != null) {
            builder = builder.setConnectionRequestTimeout(connectionRequestTimeout);
        }
        if (connectTimeout != null) {
            builder = builder.setConnectTimeout(connectTimeout);
        }
        if (socketTimeout != null) {
            builder = builder.setSocketTimeout(socketTimeout);
        }
        return builder.build();
    }

    /**
     * 发送POST请求
     *
     * @param urlToRequest             接口服务地址
     * @param parameters               发送的字符串报文参数
     * @param headers                  请求头设置的集合
     * @param connectionRequestTimeout 请求超时设定值
     * @param connectTimeout           连接超时设定值
     * @param socketTimeout            socket超时设定值
     * @return
     * @throws RuntimeException 当请求阻塞,或关闭流异常时
     * @throws IOException 当请求阻塞,或关闭流异常时
     */
    public static CloseableHttpResponse post(String urlToRequest, String parameters, Map<String, String> headers, Integer connectionRequestTimeout,
                              Integer connectTimeout, Integer socketTimeout) {
        Long startTs = System.currentTimeMillis();

        //LOGGER.info("post-request: 请求地址url: {}, 请求参数param: {}", urlToRequest, parameters);
        HttpPost post = new HttpPost(urlToRequest);
        RequestConfig requestConfig = getRequestConfig(connectionRequestTimeout, connectTimeout, socketTimeout);
        post.setConfig(requestConfig);

        if (headers != null && !headers.isEmpty()) {
            for (Entry<String, String> entity : headers.entrySet()) {
                post.setHeader(entity.getKey(), entity.getValue());
            }
        }
        post.setEntity(new StringEntity(parameters, UTF8));
        CloseableHttpResponse response = null;
        try {
            response = invoke(post);
        } catch (IOException e) {
            // 异常后必须释放链接,否则并发下会一直获取不到链接
            post.releaseConnection();
            LOGGER.error("[HttpClientUtils][invoke][method:" + post.getMethod() + " URI:" + post.getURI() + "] is request exception", e);
        }
        Long endTs = System.currentTimeMillis();
        Long currentMethodCallTime = endTs - startTs;
        if (currentMethodCallTime > 5000) {
            LOGGER.warn("请求地址url: {}, 调用耗时call time: {} ms, 所有存活线程: {}", urlToRequest, currentMethodCallTime, Thread.getAllStackTraces().size());
        }
        return response;
    }

    /**
     * 发送POST请求
     *
     * @param urlToRequest             接口服务地址
     * @param parameters               发送的字符串报文参数
     * @param headers                  请求头设置的集合
     * @param connectionRequestTimeout 请求超时设定值
     * @param connectTimeout           连接超时设定值
     * @param socketTimeout            socket超时设定值
     * @return
     * @throws RuntimeException 当请求阻塞,或关闭流异常时
     * @throws IOException 当请求阻塞,或关闭流异常时
     */
    public static int postGSSync(String urlToRequest, String parameters, Map<String, String> headers, Integer connectionRequestTimeout,
                                 Integer connectTimeout, Integer socketTimeout) {
        Long startTs = System.currentTimeMillis();

        LOGGER.info("post-req:url:[{}],param:[{}]", urlToRequest, JSONUtil.toJsonStr(parameters));
        HttpPost post = new HttpPost(urlToRequest);
        RequestConfig requestConfig = getRequestConfig(connectionRequestTimeout, connectTimeout, socketTimeout);
        post.setConfig(requestConfig);

        if (headers != null && !headers.isEmpty()) {
            for (Entry<String, String> entity : headers.entrySet()) {
                post.setHeader(entity.getKey(), entity.getValue());
            }
        }

        post.setEntity(new StringEntity(parameters, UTF8));
        int result = -1;
        try {
            result = invoke200(post);
        } catch (IOException e) {
            // 异常后必须释放链接,否则并发下会一直获取不到链接
            post.releaseConnection();
            LOGGER.error("[HttpClientUtils][invoke][method:" + post.getMethod() + " URI:" + post.getURI() + "] is request exception", e);
        }
        Long endTs = System.currentTimeMillis();
        Long currentMethodCallTime = endTs - startTs;
        if (currentMethodCallTime > 5000) {
            LOGGER.warn("url:[{}],call time [{}] ms", urlToRequest, currentMethodCallTime);
            LOGGER.info("所有存活线程=" + Thread.getAllStackTraces().size());
        }
        LOGGER.info("post-rps:{}", result);
        return result;
    }

    /**
     * 发送POST请求:小红点推送接口只返回状态码204
     *
     * @param urlToRequest             接口服务地址
     * @param parameters               发送的字符串报文参数
     * @param headers                  请求头设置的集合
     * @param connectionRequestTimeout 请求超时设定值
     * @param connectTimeout           连接超时设定值
     * @param socketTimeout            socket超时设定值
     * @return
     * @throws RuntimeException 当请求阻塞,或关闭流异常时
     * @throws IOException 当请求阻塞,或关闭流异常时
     */
    public static int postRedPointSync(String urlToRequest, String parameters, Map<String, String> headers, Integer connectionRequestTimeout,
                                       Integer connectTimeout, Integer socketTimeout) {
        Long startTs = System.currentTimeMillis();

        LOGGER.info("post-req:url:[{}],param:[{}]", urlToRequest, JSONUtil.toJsonStr(parameters));
        HttpPost post = new HttpPost(urlToRequest);

        RequestConfig requestConfig = getRequestConfig(connectionRequestTimeout, connectTimeout, socketTimeout);
        post.setConfig(requestConfig);

        if (headers != null && !headers.isEmpty()) {
            for (Entry<String, String> entity : headers.entrySet()) {
                post.setHeader(entity.getKey(), entity.getValue());
            }
        }
        post.setEntity(new StringEntity(parameters, UTF8));
        int result = -1;
        try {
            result = invoke204(post);
        } catch (IOException e) {
            // 异常后必须释放链接,否则并发下会一直获取不到链接
            post.releaseConnection();
            LOGGER.error("[HttpClientUtils][invoke][method:" + post.getMethod() + " URI:" + post.getURI() + "] is request exception", e);
        }
        Long endTs = System.currentTimeMillis();
        Long currentMethodCallTime = endTs - startTs;
        if (currentMethodCallTime > 5000) {
            LOGGER.warn("url:[{}],call time [{}] ms", urlToRequest, currentMethodCallTime);
            LOGGER.info("所有存活线程数:[{}]", Thread.getAllStackTraces().size());
        }
        LOGGER.info("post-rps:[{}]", result);
        return result;
    }

    /**
     * 发送 POST 请求
     *
     * @param urlToRequest             请求地址
     * @param parameters               请求 Byte Array 报文参数
     * @param headers                  设置请求头集合参数
     * @param connectionRequestTimeout 请求超时
     * @param connectTimeout           连接超时
     * @param socketTimeout            socket超时
     * @return
     * @throws RuntimeException 当请求阻塞,或关闭流异常时
     * @throws IOException 当请求阻塞,或关闭流异常时
     */
    public static String post(String urlToRequest, byte[] parameters, Map<String, String> headers, Integer connectionRequestTimeout,
                              Integer connectTimeout, Integer socketTimeout) {
        Long startTs = System.currentTimeMillis();

        LOGGER.info("post-req:url:[{}], param:[{}]", urlToRequest, JSONUtil.toJsonStr(parameters));
        HttpPost post = new HttpPost(urlToRequest);
        RequestConfig requestConfig = getRequestConfig(connectionRequestTimeout, connectTimeout, socketTimeout);
        post.setConfig(requestConfig);

        if (headers != null && !headers.isEmpty()) {
            for (Entry<String, String> entity : headers.entrySet()) {
                post.setHeader(entity.getKey(), entity.getValue());
            }
        }

        post.setEntity(new ByteArrayEntity(parameters));
        CloseableHttpResponse response = null;
        try {
            response = invoke(post);
        } catch (IOException e) {
            // 异常后必须释放链接,否则并发下会一直获取不到链接
            post.releaseConnection();
            LOGGER.error("[HttpClientUtils][invoke][method:" + post.getMethod() + " URI:" + post.getURI() + "] is request exception", e);
        }
        String result = getResponseString(response);
        Long endTs = System.currentTimeMillis();
        Long currentMethodCallTime = endTs - startTs;
        if (currentMethodCallTime > 5000) {
            LOGGER.warn("url:[{}],call time [{}] ms", urlToRequest, currentMethodCallTime);
            LOGGER.info("所有存活线程数: [{}]", Thread.getAllStackTraces().size());
        }
        LOGGER.info("post-rps:[{}]", result);
        return result;
    }

    /**
     * 发送 GET 请求
     *
     * @param urlToRequest 请求地址
     * @param headers      请求头设置参数集合
     * @return
     * @throws RuntimeException 当请求阻塞,或关闭流异常时
     * @throws IOException 当请求阻塞,或关闭流异常时
     */
    public static String get(String urlToRequest, Map<String, String> headers) {
        Long startTs = System.currentTimeMillis();

        HttpGet httpGet = new HttpGet(urlToRequest);
        if (headers != null && !headers.isEmpty()) {
            for (Entry<String, String> entity : headers.entrySet()) {
                httpGet.setHeader(entity.getKey(), entity.getValue());
            }
        }
        CloseableHttpResponse response = null;
        try {
            response = invoke(httpGet);
        } catch (IOException e) {
            // 异常后必须释放链接,否则并发下会一直获取不到链接
            httpGet.releaseConnection();
            LOGGER.error("[HttpClientUtils][invoke][method:" + httpGet.getMethod() + " URI:" + httpGet.getURI() + "] is request exception", e);
        }
        String result = getResponseString(response);
        Long endTs = System.currentTimeMillis();
        Long currentMethodCallTime = endTs - startTs;
        if (currentMethodCallTime > 5000) {
            LOGGER.warn("url:[{}],call time [{}] ms", urlToRequest, currentMethodCallTime);
            LOGGER.info("所有存活线程数:[{}]" + Thread.getAllStackTraces().size());
        }
        LOGGER.info("get-rps:[{}]", result);
        return result;
    }

    /**
     * 执行请求
     *
     * @param request
     * @return
     */
    private static CloseableHttpResponse invoke(HttpUriRequest request) throws IOException {
        CloseableHttpResponse response = null;
        System.setProperty("javax.net.debug", "ssl,handshake");   //查看SSL握手流程日志
        response = httpclient.execute(request);
        return response;
    }

    /**
     *  获取响应字符串
     * @param response
     * @return
     * @throws IOException
     */
    public static String getResponseString(CloseableHttpResponse response) {
        String res = null;
        if (response != null && response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            HttpEntity entity = response.getEntity();
            try {
                if (entity != null) {
                    res = EntityUtils.toString(entity, UTF8);
                }
                // 确保响应已经被消费,以释放连接
                EntityUtils.consume(response.getEntity());
            } catch (IOException e) {
                throw new RuntimeException("httpclient获取响应字符串异常", e);
            } finally {
                if (response != null) {
                    try {
                        response.close();
                    } catch (IOException e) {
                        LOGGER.error("[HttpClientUtils][method:getResponseString] is closed exception", e);
                        e.printStackTrace();
                    }
                }
            }
        }
        return res;
    }


    /**
     * 执行请求
     *
     * @param request
     * @return
     */
    private static int invoke200(HttpUriRequest request) throws IOException {
        CloseableHttpResponse response = null;
        int isOk = -1;
        try {
            response = httpclient.execute(request);
            if (response != null && (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK)) {
                isOk = HttpStatus.SC_OK;
            }
            // 确保响应已经被消费,以释放连接
            EntityUtils.consume(response.getEntity());
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    LOGGER.error("[HttpClientUtils][invoke][method:" + request.getMethod() + " URI:" + request.getURI() + "] is closed exception", e);
                }
            }
        }
        return isOk;
    }

    /**
     * 执行请求
     *
     * @param request
     * @return
     */
    private static int invoke204(HttpUriRequest request) throws IOException {
        CloseableHttpResponse response = null;
        int isOK = -1;
        try {
            response = httpclient.execute(request);
            if (response != null && (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK)) {
                 isOK = HttpStatus.SC_OK;
            } else {
                isOK = response.getStatusLine().getStatusCode();
            }
            // 确保响应已经被消费,以释放连接
            EntityUtils.consume(response.getEntity());
        }  finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    LOGGER.error("[HttpClientUtils][invoke][method:" + request.getMethod() + " URI:" + request.getURI() + "] is closed exception", e);
                }
            }
        }
        return isOK;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值