今年年初就已经吵吵着要把大部分的服务端由http转成https了,但是由于很多公司还是比较懒,而且有的公司可能不想再多掏一些钱去对自己的网址加入CA认证,所以这件事就一直拖下来了,但是随着用户数据越来越多暴露在一些不法分子眼前,所以信息安全越来越被用户重视,一些金融、贷款公司已经开始使用这种技术了,今天就来讲解一下android上面的通过https对服务器进行请求。
首先先来解答一下疑惑,我们在做测试的时候经常会对https://www.baidu.com或者https://www.github.com进行一个网络工具类的测试,但是不知道大家有没有注意到,这些网址链接之前全都是https://的,为什么我们在访问的时候完全没有报错,也能够正常的访问呢。ok,其实这两个大网站已经被CA认证过了,而CA证书已经被android设备默认支持了,所以这些CA认证的网站可以直接通过HttpUrlConnection进行连接,当然也可以通过HttpsUrlConnection进行连接。
但是除了被CA认证的网址就不能够正常的连接了,比如我们访问神奇的网站12306时,在他的注册页面其实已经升级成了Https协议的了,而且这个网站的证书是没有被CA认证过的。我们来分别对比下百度的 和github 还有我们神奇的网站12306的 签名文件~~~
见图:
第一张是百度网站的 证书文件信息
这张是github的证书信息
这张当然就是12306的喽
大家可以自己打开电脑浏览器,打开开发者工具(或者F12),然后点击security就能看到这个信息了,如果对证书感兴趣,还可以点击上边的“view certificate”去查看证书的信息
我们通过上面的三个图,不难分析出baidu 和github使用的是CA的认证的证书,而12306并没有,12306用的是自己证书
这就导致了12306需要我们特别去写一下网络请求,而百度和github则不需要。
那我们怎么去正常的访问12306呢,这里我封装了一个网络请求类,核心代码在这里:
try {
URL url = new URL(path);
//1.改成s
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
//2.SSLContext 初始化
SSLContext tls = SSLContext.getInstance("TLS");
MyX509TrustManager myX509TrustManager = new MyX509TrustManager(getX509Certificate(context));
TrustManager[] trustManagers={myX509TrustManager};
tls.init(null,trustManagers,new SecureRandom());
//3.ssl工厂
SSLSocketFactory factory = tls.getSocketFactory();
//4.添加一个主机名称校验器
conn.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
if (hostname.equals("kyfw.12306.cn")) {
return true;
}else{
return false;
}
}
});
conn.setSSLSocketFactory(factory);
conn.setRequestMethod("GET");
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
conn.connect();
InputStream inputStream = conn.getInputStream();
StringBuilder sb=new StringBuilder();
int flag;
byte[] buf=new byte[1024];
while((flag=inputStream.read(buf))!=-1){
sb.append(new String(buf,0,flag));
}
String s = sb.toString();
//调用对方传入callback完成回调操作
callBack.onSuccess(s);
} catch (Exception e) {
e.printStackTrace();
callBack.onFail(e);
}
主要的思路是:
1.先将我们常用的httpurlconnection变成HttpsConnection
2.初始化我们的SSLContext 这个类是ssl的一个帮助类,能够帮助我们去验证我们的证书文件
这个环节中其实稍微多一些操作,比如获取SSLContext的实例
SSLContext tls = SSLContext.getInstance("TLS");还有创建SSLContext的初始化参数,这个MyX509TrustManager是我自己封装的类
MyX509TrustManager myX509TrustManager = new MyX509TrustManager(getX509Certificate(context));
封装的类如下
package com.guaju.httpsrequesttest.http;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
/**
* Created by guaju on 2017/11/7.
*/
public class MyX509TrustManager implements X509TrustManager {
//如果需要对证书进行校验,需要这里去实现,如果不实现的话是不安全
X509Certificate mX509Certificate;
public MyX509TrustManager(X509Certificate mX509Certificate) {
this.mX509Certificate = mX509Certificate;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (X509Certificate certificate:chain){
//检查证书是否有效
certificate.checkValidity();
try {
certificate.verify(mX509Certificate.getPublicKey());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
而上个类中传入的构造方法中的证书文件
X509Certificate mX509Certificate;
这个证书文件需要通过我们下载的证书去获得,12306的证书在官网首页,而我们如果开发自己的公司的网站的话,后台应该会给我们一份,我们拿着这个证书放到assets目录下(或者后台的服务器上)就可以转变成我们自己的签名了,下午是12306下载证书的地方:
下方是提供的将assets中的证书文件流转化成证书的方法,很简单:
//拿到自己的证书
X509Certificate getX509Certificate(Context context) throws IOException, CertificateException {
InputStream in = context.getAssets().open("srca.cer");
CertificateFactory instance = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) instance.generateCertificate(in);
return certificate;
}
创建TrustManager数组,将上方的MyX509TrustManager放进数组中去
TrustManager[] trustManagers={myX509TrustManager};
然后调用SSLContext的初始化方法
tls.init(null,trustManagers,new SecureRandom());3.上方的sslcontext初始化好之后,我们就可以做SSLSocketFactory的创建了
SSLSocketFactory factory = tls.getSocketFactory();
然后把这个工厂为我们的连接conn设置上
conn.setSSLSocketFactory(factory);
4.添加主机名称校验器,记住这里的主机名称地址需要查看我们的证书的配置,不能瞎写
证书配置如下图
下方的名称及上方的常用名称
conn.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { if (hostname.equals("kyfw.12306.cn")) { return true; }else{ return false; } } });这样的话就可以访问12306的注册页面了。
ok,为了方便大家使用,我已经提交到github了,如果大家觉得不错,记得star 或者fork哦 ,多谢~~~
github:https://github.com/guaju/HttpsRequestTest