http https 原生实现 restTemplate实现

http请求 通常用httpclient

https请求 通常是基于httpclient的基础上自建信任中心

下面分享两种实现方式:原生实现<<<>>>RestTemplate实现(基于下载远程文件的例子)

原生的实现方案:

大体思路:

判断url是否以https开头

1、是-->>https请求

>>构建安全证书方式1

>>构建安全证书方式2

2、否-->>http请求

package com.rj.knowledgepro.util;

import com.rj.knowledgepro.util.https.HTTPSTrustManager;
import com.rj.knowledgepro.util.https.MyX509TrustManager;
import sun.misc.BASE64Encoder;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;

/**
 * 图像工具
 * @author 魏霖涛
 * @date 2018/6/1 20:10
 */
public class ImageUtil {

    /**
     * 将网络图片转成Base64码,此方法可以解决解码后图片显示不完整的问题
     * @param imgURL 图片地址。
     * 例如:http://***.com/271025191524034.jpg
     * 例如:https://***.com/271025191524034.jpg
     * @return
     */
    public static String getImageStrFromUrl(String imgURL){
        ByteArrayOutputStream outPut = new ByteArrayOutputStream();
        byte[] data = new byte[1024];
        try {
            // 获取图片
            // 创建URL url转码 - 400错误 url已经是url编码了,如果再decode会转回中文,url不能存在中文
            URL url = new URL(imgURL);
            InputStream inStream;
            // 创建链接
            // 如果是https链接,那么需要构建安全证书
            if (imgURL.matches("^https.*$")) {
                // 启用安全访问方式1:
//                HTTPSTrustManager.allowAllSSL();
                HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

                // 启用安全访问方式2:
                //创建SSLContext
                SSLContext sslContext=SSLContext.getInstance("SSL");
                TrustManager[] tm={new MyX509TrustManager()};
                //初始化
                sslContext.init(null, tm, new java.security.SecureRandom());
                //获取SSLSocketFactory对象
                SSLSocketFactory ssf=sslContext.getSocketFactory();
                //设置当前实例使用的SSLSoctetFactory
                conn.setSSLSocketFactory(ssf);

                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5 * 1000);
                //设置用户代理 - 403错误
                conn.setRequestProperty("User-agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/2010");
                int responseCode = conn.getResponseCode();
                if(responseCode != HttpURLConnection.HTTP_OK) {
                    //连接失败/链接失效/图片不存在
                    return IMG_NOT_FOUND;
                }
                inStream = conn.getInputStream();
            } else {
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5 * 1000);
                //设置用户代理 - 403错误
                conn.setRequestProperty("User-agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/2010");
                if(conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
                    //连接失败/链接失效/图片不存在
                    return "";
                }
                inStream = conn.getInputStream();
            }

            int len;
            while ((len = inStream.read(data)) != -1) {
                outPut.write(data, 0, len);
            }
            inStream.close();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
        // 对字节数组Base64编码
        BASE64Encoder encoder = new BASE64Encoder();
        return "data:image/jpeg;base64," + encoder.encode(outPut.toByteArray());
    }
   

>>构建安全证书方式1

原理:

https信任中心 其中allowAllSSL作用:默认将所有的的https请求都设置为安全受信任的
package com.rj.knowledgepro.util.https;

import javax.net.ssl.*;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

/**
 * https信任中心 其中allowAllSSL作用:默认将所有的的https请求都设置为安全受信任的。作用跟{@link MyX509TrustManager 作用一样}
 */
public class HTTPSTrustManager implements X509TrustManager {

    private static TrustManager[] trustManagers;
    private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[] {};

    @Override
    public void checkClientTrusted(
            java.security.cert.X509Certificate[] x509Certificates, String s)
            throws java.security.cert.CertificateException {
        // To change body of implemented methods use File | Settings | File
        // Templates.
    }

    @Override
    public void checkServerTrusted(
            java.security.cert.X509Certificate[] x509Certificates, String s)
            throws java.security.cert.CertificateException {
        // To change body of implemented methods use File | Settings | File
        // Templates.
    }

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

    public static void allowAllSSL() {
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {

            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                // TODO Auto-generated method stub
                return true;
            }

        });

        SSLContext context = null;
        if (trustManagers == null) {
            trustManagers = new TrustManager[] { new HTTPSTrustManager() };
        }

        try {
            context = SSLContext.getInstance("TLS");
            context.init(null, trustManagers, new SecureRandom());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
    }
}

>>构建安全证书方式2

原理:

https是对链接加了安全证书SSL的,如果服务器中没有相关链接的SSL证书,它就不能够信任那个链接,也就不会访问到了。所以我们第一步是自定义一个信任管理器。自要实现自带的X509TrustManager接口就可以了
* 注:1)需要的包都是java自带的,所以不用引入额外的包。
2.)可以看到里面的方法都是空的,当方法为空是默认为所有的链接都为安全,也就是所有的链接都能够访问到。当然这样有一定的安全风险,可以根据实际需要写入内容。
package com.rj.knowledgepro.util.https;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;  
import javax.net.ssl.X509TrustManager;

/**
 * https是对链接加了安全证书SSL的,如果服务器中没有相关链接的SSL证书,它就不能够信任那个链接,也就不会访问到了。所以我们第一步是自定义一个信任管理器。自要实现自带的X509TrustManager接口就可以了
 * 注:1)需要的包都是java自带的,所以不用引入额外的包。
 2.)可以看到里面的方法都是空的,当方法为空是默认为所有的链接都为安全,也就是所有的链接都能够访问到。当然这样有一定的安全风险,可以根据实际需要写入内容。
 */
public class MyX509TrustManager implements X509TrustManager {  
  
    @Override  
    public void checkClientTrusted(X509Certificate[] chain, String authType)  
            throws CertificateException {  
        // TODO Auto-generated method stub  
  
    }  
  
    @Override  
    public void checkServerTrusted(X509Certificate[] chain, String authType)  
            throws CertificateException {  
        // TODO Auto-generated method stub  
  
    }  
  
    @Override  
    public X509Certificate[] getAcceptedIssuers() {  
        // TODO Auto-generated method stub  
        return null;  
    }  
  
}

上述两种方式的添加信任证书都有在ImageUtil里头做体现:

// 启用安全访问方式1:
// 启用安全访问方式2:

restTemplate方式实现

基于spring的工具restTemplate

package com.rj.kdb.util.restutil;

import com.rj.kdb.util.ExceptionUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;

import java.io.*;
import java.util.Objects;

/**
 * 通过restTemplate来下载远程文件
 * @author 魏霖涛
 * @date 2018/7/9 19:39
 */
@Slf4j
public class RestDownloadUtil {
    public void downLoadByUrl (String url, String fileName, String savePath) throws Exception {
        log.info("要下载的记录url:{}", url);
        log.info("本地保留路径:{}", savePath);
        log.info("文件名:{}", fileName);

        //文件保存位置
        File saveDir = new File(savePath);
        if (!saveDir.exists()) {
            saveDir.mkdirs();
        }

        InputStream inputStream = null;
        OutputStream outputStream = null;

        // 通过设置clientRequset兼容https请求
        RestTemplate restTemplate;
        if (url.matches("^https.*$")) {
            restTemplate = new RestTemplate(new HttpsClientRequestFactory());
        } else {
            restTemplate = new RestTemplate();
        }
        try {
            HttpHeaders headers = new HttpHeaders();
            headers.set(HttpHeaders.USER_AGENT,"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/2010");
            ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity(headers), byte[].class);
            if (!Objects.equals(response.getStatusCode(), HttpStatus.OK)) {
                throw new Exception("链接异常:" + response.getStatusCode().value() + "<<<>>>" + response.getStatusCode().getReasonPhrase() + ">>>" + url);
            }

            byte[] result = response.getBody();
            inputStream = new ByteArrayInputStream(result);
            File file = new File(saveDir + File.separator + fileName);
            outputStream = new FileOutputStream(file);

            int len;
            byte[] buf = new byte[1024];
            while ((len = inputStream.read(buf, 0, 1024)) != -1) {
                outputStream.write(buf, 0, len);
            }
            outputStream.flush();
            log.info("消息:{}--download success", file.getAbsoluteFile());
        } catch (Exception e) {
            log.info(ExceptionUtil.getExceptionAllinformation(e));
            throw e;
        } finally {
            try {
                if(inputStream != null) {
                    inputStream.close();
                }
                if(outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                log.info(ExceptionUtil.getExceptionAllinformation(e));
                throw e;
            }
        }

    }
}
 
package com.rj.kdb.util.restutil;

import org.springframework.http.client.SimpleClientHttpRequestFactory;

import javax.net.ssl.*;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Socket;
import java.security.cert.X509Certificate;

/**
 * Desc: 使用Spring RestTemplete实现 Https需要自定义ClientHttpRequestFactory;
 * 

* 参考链接:https://stackoverflow.com/questions/17619871/access-https-rest-service-using-spring-resttemplate */ 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) { } } }; SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustAllCerts, new java.security.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(); } } /** * We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"}); * see http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html (Java 8 section) */ private static class MyCustomSSLSocketFactory extends SSLSocketFactory { private final SSLSocketFactory delegate; public MyCustomSSLSocketFactory(SSLSocketFactory delegate) { this.delegate = delegate; } @Override public String[] getDefaultCipherSuites() { return delegate.getDefaultCipherSuites(); } @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"}); return socket; } } }

废话不说,直接看上面代码。

同时多分享一个我都爆粗口的坑:

原生的http请求,url含中文在本地ide中做测试没任何问题,但是springboot工程打包成jar放在本地通过命令行的模式启动,却访问不成功,报错的代码:403。

403错误正常都是因为没有设置user-agent导致的,但是作者都做了设定还是报错,后面即将奔溃之前,做了尝试:把url利用在线转码工具,把uri部分转码成url编码(即把中文部分转码成url编码),成功了。笔者认为这个有点坑。后面放弃了原生的,采用restTemplate方式,就再没出现403问题了,而且url也不用转码。



1I


你可能感兴趣的:(https)