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