spring RestTemplate封装,发送http、https以及https携带证书请求

2023-01-10 11:08:51

RestTemplate发送http,https默认信任所有链接以及https携带证书的请求工具。不依赖apache的httpcomponents


这里主要是处理https携带证书,其他的http和常规https不是很复杂
通常情况下接口提供方都会提供证书,基本都是 cer格式证书(也可能会有其他格式),需要自行转换java直接可以调用的证书 keystore格式,直接使用jdk提供工具:

keytool -importcert -keystore [自定义证书名称].keystore -file ./[需要转换的cer证书].cer -alias [别名] -storepass [设置密码] -noprompt 如:
keytool -importcert -keystore yxt.keystore -file ./xxxx.server.cer -alias ydy -storepass yxt_123456 -noprompt

需要注意的是jdk的版本不同使用的TLS版本也不一样,这里使用的是jdk1.8
在这里插入图片描述

加载证书发起https访问,主要怎么去配置https携带证书

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.client.SimpleClientHttpRequestFactory;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
 * 自主携带https的自定义证书进行访问通信
 *
 * @author Yang douya
 * @date 2021/5/27 9:57
 */
@Slf4j
public class HttpsClientRequestFactory2SSLCert extends SimpleClientHttpRequestFactory {
    /**
     * 证书位置
     */
    private String certPath;

    /**
     * 证书密码 未知直接为 null
     */
    private String certPassword;

    public HttpsClientRequestFactory2SSLCert(String certPath, String certPassword) {
        this.certPassword = certPassword;
        this.certPath = certPath;
    }

    @Override
    protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
        try {
            if (!(connection instanceof HttpsURLConnection)) {
                throw new RuntimeException("An instance of HttpsURLConnection is expected");
            }
            HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
            TrustManager[] trustManagers = {new SSLCert509TrustManager(certPath, certPassword)};
            // 强制使用 TLSv1.2 解决 Received fatal alert: protocol_version; nested exception is javax.net.ssl.SSLException: Received fatal alert: protocol_version 异常情况
            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, trustManagers, new SecureRandom());
            httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));

            httpsConnection.setHostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String s, SSLSession sslSession) {
                    return true;
                }
            });

            super.prepareConnection(httpsConnection, httpMethod);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * SSLSocketFactory用于创建 SSLSockets
     */
    private class MyCustomSSLSocketFactory extends SSLSocketFactory {

        private final SSLSocketFactory delegate;

        public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
            this.delegate = delegate;
        }

        // 返回默认启用的密码套件。除非一个列表启用,对SSL连接的握手会使用这些密码套件。
        // 这些默认的服务的最低质量要求保密保护和服务器身份验证
        @Override
        public String[] getDefaultCipherSuites() {
            return delegate.getDefaultCipherSuites();
        }

        // 返回的密码套件可用于SSL连接启用的名字
        @Override
        public String[] getSupportedCipherSuites() {
            return delegate.getSupportedCipherSuites();
        }


        @Override
        public Socket createSocket(final Socket socket, final String host, final int port, final boolean autoClose) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
            return overrideProtocol(underlyingSocket);
        }


        @Override
        public Socket createSocket(final String host, final int port) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port);
            return overrideProtocol(underlyingSocket);
        }

        @Override
        public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
            return overrideProtocol(underlyingSocket);
        }

        @Override
        public Socket createSocket(final InetAddress host, final int port) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port);
            return overrideProtocol(underlyingSocket);
        }

        @Override
        public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
            return overrideProtocol(underlyingSocket);
        }

        private Socket overrideProtocol(final Socket socket) {
            if (!(socket instanceof SSLSocket)) {
                throw new RuntimeException("An instance of SSLSocket is expected");
            }
            ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"});
            return socket;
        }
    }

    /**
     * 配置https携带证书
     */
    private class SSLCert509TrustManager implements X509TrustManager {
        private X509TrustManager sunJSSEX509TrustManager;

        /**
         * 初始化数据
         *
         * @param certPath 证书位置
         * @param certPwd  证书使用密码
         */
        SSLCert509TrustManager(String certPath, String certPwd) {
            ClassPathResource resource = new ClassPathResource(certPath);
            if (resource.exists()) {
                InputStream inputStream = null;
                try {
                    inputStream = resource.getInputStream();
                    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                    keyStore.load(inputStream, (null == certPwd) ? null : certPwd.toCharArray());
                    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509", "SunJSSE");
                    trustManagerFactory.init(keyStore);
                    TrustManager trustManagers[] = trustManagerFactory.getTrustManagers();
                    for (int i = 0; i < trustManagers.length; i++) {
                        if (trustManagers[i] instanceof X509TrustManager) {
                            sunJSSEX509TrustManager = (X509TrustManager) trustManagers[i];
                            break;
                        }
                    }
                    inputStream.close();
                } catch (KeyStoreException e) {
                    log.error("restTemplate ssl cert KeyStoreException {}", e);
                } catch (CertificateException e) {
                    log.error("restTemplate ssl cert CertificateException {}", e);
                } catch (NoSuchAlgorithmException e) {
                    log.error("restTemplate ssl cert NoSuchAlgorithmException {}", e);
                } catch (IOException e) {
                    log.error("restTemplate ssl cert IOException {}", e);
                } catch (NoSuchProviderException e) {
                    log.error("restTemplate ssl cert NoSuchProviderException {}", e);
                } finally {
                    if (null != inputStream) {
                        try {
                            inputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
            try {
                sunJSSEX509TrustManager.checkClientTrusted(x509Certificates, s);
            } catch (CertificateException e) {
                log.error("checkClientTrusted ssl certException {}", e);
            }

        }

        @Override
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
            try {
                sunJSSEX509TrustManager.checkServerTrusted(x509Certificates, s);
            } catch (CertificateException e) {
                log.error("checkServerTrusted ssl certException {}", e);
            }
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return sunJSSEX509TrustManager.getAcceptedIssuers();
        }
    }
}

绕过证书发起https访问,主要是设置信任所有链接


import org.springframework.http.client.SimpleClientHttpRequestFactory;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Socket;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

/**
 * HTTPS请求 = 超文本传输协议HTTP + 安全套接字层SSL 不自主携带证书请求访问
 *
 * @author Yang douya
 * @date 2021/2/3 10:53
 */
public class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory {

    @Override
    protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
        try {
            if (!(connection instanceof HttpsURLConnection)) {
                throw new RuntimeException("An instance of HttpsURLConnection is expected");
            }

            HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;

            TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
                @Override
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                }

            }
            };
            // 强制使用 TLSv1.2 解决 Received fatal alert: protocol_version; nested exception is javax.net.ssl.SSLException: Received fatal alert: protocol_version 异常情况
            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, trustAllCerts, new SecureRandom());
            httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));

            httpsConnection.setHostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String s, SSLSession sslSession) {
                    return true;
                }
            });

            super.prepareConnection(httpsConnection, httpMethod);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * SSLSocketFactory用于创建 SSLSockets
     */
    private class MyCustomSSLSocketFactory extends SSLSocketFactory {

        private final SSLSocketFactory delegate;

        public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
            this.delegate = delegate;
        }

        // 返回默认启用的密码套件。除非一个列表启用,对SSL连接的握手会使用这些密码套件。
        // 这些默认的服务的最低质量要求保密保护和服务器身份验证
        @Override
        public String[] getDefaultCipherSuites() {
            return delegate.getDefaultCipherSuites();
        }

        // 返回的密码套件可用于SSL连接启用的名字
        @Override
        public String[] getSupportedCipherSuites() {
            return delegate.getSupportedCipherSuites();
        }


        @Override
        public Socket createSocket(final Socket socket, final String host, final int port, final boolean autoClose) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
            return overrideProtocol(underlyingSocket);
        }


        @Override
        public Socket createSocket(final String host, final int port) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port);
            return overrideProtocol(underlyingSocket);
        }

        @Override
        public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
            return overrideProtocol(underlyingSocket);
        }

        @Override
        public Socket createSocket(final InetAddress host, final int port) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port);
            return overrideProtocol(underlyingSocket);
        }

        @Override
        public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
            return overrideProtocol(underlyingSocket);
        }

        private Socket overrideProtocol(final Socket socket) {
            if (!(socket instanceof SSLSocket)) {
                throw new RuntimeException("An instance of SSLSocket is expected");
            }
            ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"});
            return socket;
        }
    }
}

封装请求工具RestTemplateUtil由于可能对接是多个平台的API,要携带不同cer证书,或者是不需要证书


import com.alibaba.fastjson.JSONObject;
import com.zyxx.rrp.pojo.ResponseMessage;
import com.zyxx.utils.http.ssl.HttpsClientRequestFactory;
import com.zyxx.utils.http.ssl.HttpsClientRequestFactory2SSLCert;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * http或者https请求
 *
 * @author Yang douya
 * @date 2021/2/3 10:23
 */
@Slf4j
public class RestTemplateUtil {

    /**
     * POST请求
     */
    public static final String REQUEST_METHOD_POST = "post";
    /**
     * get 请求
     */
    public static final String REQUEST_METHOD_GET = "get";

    private static RestTemplate restTemplate = null;

    /**
     * 获取 RestTemplate
     *
     * @param requestUrl   请求路径
     * @param useSSl       是否携带自定义证书
     * @param certPath     证书位置 useSSl-true 有效
     * @param certPassword 证书密码 useSSl-true 有效
     * @return
     */
    private static RestTemplate getRestTemplate(String requestUrl, boolean useSSl, String certPath, String certPassword) {
        if (requestUrl.startsWith("http:")) {
            restTemplate = new RestTemplate();
        } else if (requestUrl.startsWith("https:")) {
            if (useSSl) {
                restTemplate = new RestTemplate(new HttpsClientRequestFactory2SSLCert(certPath, certPassword));
            } else {
                restTemplate = new RestTemplate(new HttpsClientRequestFactory());
            }
        }
        if (null != restTemplate && !ObjectUtils.isEmpty(restTemplate.getMessageConverters())) {
            // 强制处理字符集 utf-8
            List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
            HttpMessageConverter<?> httpMessageConverter = null;
            for (int i = 0, size = messageConverters.size(); i < size; i++) {
                httpMessageConverter = messageConverters.get(i);
                if (httpMessageConverter instanceof StringHttpMessageConverter) {
                    ((StringHttpMessageConverter) httpMessageConverter).setDefaultCharset(StandardCharsets.UTF_8);
                    messageConverters.set(i, new StringHttpMessageConverter(StandardCharsets.UTF_8));
                }
            }
        }
        return restTemplate;
    }

    /**
     * 默认请求类型 Content-Type:application/x-www-form-urlencoded
     *
     * @param requestUrl   路径
     * @param useSSl       是否携带自定义证书
     * @param certPath     证书位置 useSSl-true 有效
     * @param certPassword 证书密码 useSSl-true 有效
     * @param method       请求方式
     * @param paramData    请求参数
     * @return 0-失败 1-成功 msgData-响应数据
     */
    public static ResponseMessage sendDefault(String requestUrl, boolean useSSl, String certPath, String certPassword, String method, Map<String, String> paramData) {
        ResponseMessage responseMessage = new ResponseMessage();
        try {
            String responseData = "";
            if (REQUEST_METHOD_POST.equals(method.toLowerCase())) {
                HttpHeaders headers = new HttpHeaders();
                // HttpMethod httpMethod = HttpMethod.POST;
                // 以表单的方式提交
                headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
                MultiValueMap<String, String> multiValueMap = new LinkedMultiValueMap<>();
                if (null != paramData && 0 < paramData.size()) {
                    Set<Map.Entry<String, String>> entries = paramData.entrySet();
                    for (Map.Entry<String, String> entry : entries) {
                        multiValueMap.add(entry.getKey(), entry.getValue());
                    }
                }
                HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<MultiValueMap<String, String>>(multiValueMap, headers);
                ResponseEntity<String> stringResponseEntity = getRestTemplate(requestUrl, useSSl, certPath, certPassword).postForEntity(requestUrl, httpEntity, String.class);
                if (null != stringResponseEntity) {
                    responseData = stringResponseEntity.getBody();
                }
            } else if (REQUEST_METHOD_GET.equals(method.toLowerCase())) {
                StringBuilder builder = new StringBuilder();
                builder.append(requestUrl).append("?");
                for (String key : paramData.keySet()) {
                    builder.append(key).append("=").append(paramData.get(key)).append("&");
                }
                responseData = getRestTemplate(requestUrl, useSSl, certPath, certPassword).getForObject(builder.toString(), String.class);
            }
            responseMessage.setCode(1);
            responseMessage.setMsg("success");
            responseMessage.setData(responseData);
        } catch (Exception e) {
            responseMessage.setCode(0);
            log.error("RestTemplateUtil sendDefault 添加请求异常 {}", e);
        }
        return responseMessage;
    }

    /**
     * 请求提交json数据 post请求
     *
     * @param requestUrl   路径
     * @param useSSl       是否携带自定义证书
     * @param certPath     证书位置 useSSl-true 有效
     * @param certPassword 证书密码 useSSl-true 有效
     * @param paramData    请求参数
     * @return 0-失败 1-成功 msgData-响应数据
     */
    public static ResponseMessage sendForJson(String requestUrl, boolean useSSl, String certPath, String certPassword, Object paramData) {
        ResponseMessage responseMessage = new ResponseMessage();
        HttpHeaders headers = new HttpHeaders();
        // 消息格式
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
        HttpEntity<String> formEntity = new HttpEntity<>(JSONObject.toJSONString(paramData), headers);
        try {
            String responseData = getRestTemplate(requestUrl, useSSl, certPath, certPassword).postForEntity(requestUrl, formEntity, String.class).getBody();
            responseMessage.setCode(1);
            responseMessage.setMsg("success");
            responseMessage.setData(responseData);
        } catch (Exception e) {
            responseMessage.setCode(0);
            log.error("RestTemplateUtil sendForJson请求异常 {}", e);
        }
        return responseMessage;
    }

    /**
     * 请求提交json数据 post请求
     *
     * @param requestUrl   路径
     * @param useSSl       是否携带自定义证书
     * @param certPath     证书位置 useSSl-true 有效
     * @param certPassword 证书密码 useSSl-true 有效
     * @param headerMap    消息头
     * @param paramData
     * @return 0-失败 1-成功 msgData-响应数据
     */
    public static ResponseMessage sendForJson(String requestUrl, boolean useSSl, String certPath, String certPassword, Map<String, String> headerMap, Object paramData) {
        ResponseMessage responseMessage = new ResponseMessage();
        HttpHeaders headers = new HttpHeaders();
        // 消息格式
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
        if (null != headerMap && 0 < headerMap.size()) {
            Set<Map.Entry<String, String>> entries = headerMap.entrySet();
            for (Map.Entry<String, String> map : entries) {
                headers.add(map.getKey(), map.getValue());
            }
        }

        HttpEntity<String> formEntity = new HttpEntity<>(JSONObject.toJSONString(paramData), headers);
        try {
            String responseData = getRestTemplate(requestUrl, useSSl, certPath, certPassword).postForEntity(requestUrl, formEntity, String.class).getBody();
            responseMessage.setCode(1);
            responseMessage.setMsg("success");
            responseMessage.setData(responseData);
        } catch (Exception e) {
            responseMessage.setCode(0);
            log.error("RestTemplateUtil sendForJson请求异常 {}", e);
        }
        return responseMessage;
    }

    /**
     * 请求提交json数据 post请求
     *
     * @param requestUrl   路径
     * @param useSSl       是否携带自定义证书
     * @param certPath     证书位置 useSSl-true 有效
     * @param certPassword 证书密码 useSSl-true 有效
     * @param xml          xml数据
     * @return 0-失败 1-成功 msgData-响应数据
     */
    public static ResponseMessage sendForXml(String requestUrl, boolean useSSl, String certPath, String certPassword, String xml) {
        ResponseMessage responseMessage = new ResponseMessage();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_XML);
        headers.add("Accept", MediaType.APPLICATION_XML_VALUE);
        HttpEntity<String> formEntity = new HttpEntity<>(xml, headers);
        try {
            String responseData = getRestTemplate(requestUrl, useSSl, certPath, certPassword).postForEntity(requestUrl, formEntity, String.class).getBody();
            responseMessage.setCode(1);
            responseMessage.setMsg("success");
            responseMessage.setData(responseData);
        } catch (Exception e) {
            responseMessage.setCode(0);
            log.error("RestTemplateUtil 添加请求异常 {}", e);
        }
        return responseMessage;
    }

    /**
     * get请求
     *
     * @param useSSl       是否携带自定义证书
     * @param certPath     证书位置 useSSl-true 有效
     * @param certPassword 证书密码 useSSl-true 有效
     * @param requestUrl
     * @return
     */
    public static ResponseMessage sendForGet(String requestUrl, boolean useSSl, String certPath, String certPassword) {
        ResponseMessage responseMessage = new ResponseMessage();
        try {
            ResponseEntity<String> forEntity = getRestTemplate(requestUrl, useSSl, certPath, certPassword).getForEntity(requestUrl, String.class);
            if (null != forEntity) {
                String responseData = forEntity.getBody();
                responseMessage.setCode(1);
                responseMessage.setMsg("success");
                responseMessage.setData(responseData);
                //log.info("get 请求响应数据 {}", responseData);
            }
        } catch (Exception e) {
            log.error("get 请求异常 url {} 异常 {}", requestUrl, e);
        }
        return responseMessage;
    }
}

使用工具即可

  • 作者:杨豆芽
  • 原文链接:https://blog.csdn.net/yxt625520/article/details/117324190
    更新时间:2023-01-10 11:08:51