最近在写一个测试服务器的程序,其中需要使用HTTPS协议,向服务器提交多个请求,由于服务器的证书是自己做的,因此要做证书的验证等工作。在网上查了许多例子,发现都不太合适,因为大部分代码都没做证书验证,所以容易受到中间人攻击。这里查阅了许多文档,并编写了自己的代码,做一个总结。
我这里使用了HttpClient 4.5版本。
参考了Oracle的文档:http://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html,SSL相关概念和协议内容在该文档中已经非常详细的介绍了,这里也不再赘述。
接下来直接上代码了。
首先需要实现一个X509TrustManager接口,这个接口是用来判断服务器提供的证书是否可以被客户端信任。这个类需要一个证书,我们可以通过浏览器的导出证书功能,将服务器上我们自己创建的证书导出在本地。在MyX509TrustManager的构造方法中,利用CertificateFactory生成一个Certificate实例。checkClientTrusted是用来检查客户端的证书的,这里我们只需要检测服务器端的证书就可以了,因此checkClientTrusted方法体就不添加代码了。checkServerTrusted是用来检查服务器端证书的合法性的,我们在这里对它进行一些处理。我这里用了一个非常简单的方法,就是比较服务器端发送来的证书和自己本地的证书是否一致。如果没有一样的证书,就直接抛出异常。
package com.uestc.test.upload;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class MyX509TrustManager implements X509TrustManager {
private Certificate cert = null;
public MyX509TrustManager() {
try {
FileInputStream fis = new FileInputStream(
"/Users/justyoung/Desktop/upload/Cloud3");
BufferedInputStream bis = new BufferedInputStream(fis);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
while (bis.available() > 0) {
cert = cf.generateCertificate(bis);
// System.out.println(cert.toString());
}
bis.close();
} catch (CertificateException | IOException e) {
e.printStackTrace();
}
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
for (X509Certificate cert : chain) {
if (cert.toString().equals(this.cert.toString()))
return;
}
throw new CertificateException("certificate is illegal");
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[] { (X509Certificate) cert };
}
}
然后,又实现了一个HostnameVerifier接口,这个类主要是在SSL握手时验证对方主机名称的,这样做的一个目的也是防止链接被重定向到其他的不安全的地址上去,并且若出现服务器证书上的Hostname和实际的URL不匹配时,也能做一些处理,否则会抛出这样的异常:javax.net.ssl.SSLPeerUnverifiedException: Host name '192.168.2.177' does not match the certificate subject provided by the peer,因此实现HostnameVerifier接口,我们能做一些hostname确认的工作,提高安全性。
package com.uestc.test;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
public class MyVerifyHostname implements HostnameVerifier {
@Override
public boolean verify(String arg0, SSLSession arg1) {
if (arg0.equals("192.168.2.177") || arg0.equals("cyber-space2015.imwork.net"))
return true;
else
return false;
}
}
最后就是利用HttpClient来完成Post请求的提交了。这里我们自定义了SSLContext,需要使用init方法,并传递进我们自己创建的TrustManager实例数组。然后利用HttpClients的custom方法自定义了一些参数,并生成HttpClient实例。其他代码就没什么特殊的了。
package com.uestc.test;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class UploadTest {
private String host;
private String cookie;
private String path;
private int port;
private SSLContext sslContext;
private X509TrustManager tm;
private HttpClient httpClient;
public UploadTest(String host, String cookie, String path, int port)
throws Exception {
this.host = host;
this.cookie = cookie;
this.path = path;
this.port = port;
sslContext = SSLContext.getInstance("TLS");
tm = new MyX509TrustManager();
sslContext.init(null, new TrustManager[] { tm },
new java.security.SecureRandom());
httpClient = HttpClients.custom()
.setSSLHostnameVerifier(new MyVerifyHostname())
.setSSLContext(sslContext).build();
}
public String uploadFile(String localPath, String filename)
throws URISyntaxException, ClientProtocolException, IOException {
URI uri = new URIBuilder().setScheme("https").setHost(host)
.setPath(path).setParameter("filename", filename).setPort(port)
.build();
HttpPost httpPost = new HttpPost(uri);
FileEntity fileEntity = new FileEntity(new File(localPath));
fileEntity.setChunked(true);
httpPost.setEntity(fileEntity);
httpPost.addHeader("Cookie", cookie);
HttpResponse response = httpClient.execute(httpPost);
HttpEntity httpEntity = response.getEntity();
httpEntity = new BufferedHttpEntity(httpEntity);
String resultString = EntityUtils.toString(httpEntity);
return resultString;
}
public static void main(String[] args) {
try {
UploadTest test = new UploadTest(
"192.168.2.177",
"XXXXX",
"XXXXX", 443);
String resultString = test.uploadFile(
"/Users/justyoung/Desktop/upload/test.java", "mytest1");
System.out.println(resultString);
} catch (URISyntaxException | IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
完。