Android支持Https总结
整合了一个工具类HttpsUtil,地址:
基本使用
-
全局支持Https只需在Application中调用
HttpsUtil.initHttpsUrlConnection(this);
-
适用的网络请求:
- 直接使用HttpUrlConnection的请求
- 底层实现是HttpUrlConnection的框架(比如Volley(Api 9+)、ImageLoader、Glide等)
-
OkHttp不适用,可以调用
HttpsUtil.getHttpsOkHttpClient(Context context)
来获取支持Https的OkHttpClient。 -
服务端证书放在Assets目录下
修改证书名:
private static final String[] CERTIFICATES = new String[]{"证书名.cer"};
如有多个证书,向CERTIFICATES添加即可。 -
默认支持的是单向验证,安全性已足够
- 如需要不进行验证,将CERTIFICATES元素清空即可,但这样存在极大的安全隐患,慎用!!
- 如需要双向认证,需修改initHttpsUrlConnection的实现:
public static void initHttpsUrlConnection(Context context) {
InputStream[] certificates = getCertificates(context, CERTIFICATES);
SSLSocketFactory sslSocketFactory = getSSLSocketFactory(certificates, null, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
if (certificates == null) {
HttpsURLConnection.setDefaultHostnameVerifier(getUnSafeHostnameVerifier());
}
}
其中getSSLSocketFactory方法的后两个参数需要传入合适的值
具体实现可参考:
http://blog.csdn.net/lmj623565791/article/details/48129405
为什么Https请求需要这样额外配置?
简单来说,Https相比Http多了一层SSL验证,这个验证有很多步骤。其中有一步是对服务器证书的校验,而服务器证书的获得:
- 可以由权威机构颁发,这些机构大多数都已经在Android设备的信任列表中,这样的证书不需要额外配置就能直接访问Https,那些不在设备信任列表的机构颁发的证书就需要进行配置。
注意:不同Android设备的信任列表可能是不同的。 - 可以是自签名证书,这种证书肯定无法通过默认的验证,需要自己实现验证方法,或者直接配置不验证,信任所有证书。
注意:信任所有证书是存在极大的安全隐患的,这意味着你的请求也不会被加密,任何人都可以拦截你的请求并伪装成你的服务器与你通信,并记录你的信息比如登录账号和密码等。 - 可以是由权威机构授权的中间机构颁发的中间证书,这种情况下进行SSL验证时服务器必须返回整个证书链,不能只返回根证书,否则也会验证失败。
深入了解可参考:
http://www.cnblogs.com/P_Chou/archive/2010/12/27/https-ssl-certification.html
其他可能出现的问题
SSL验证时会对证书的有效期进行验证,如果用户设备的时间不在有效期内也会验证失败。如果想忽略有效期,可以使用HttpsUtil中的NotValidateTimeTrustManager类:
private static class NotValidateTimeTrustManager implements X509TrustManager {
private X509TrustManager defaultTrustManager;
public NotValidateTimeTrustManager(X509TrustManager defaultTrustManager) {
this.defaultTrustManager = defaultTrustManager;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
defaultTrustManager.checkClientTrusted(chain, authType);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
defaultTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException e) {
e.printStackTrace();
Throwable t = e;
while (t != null) {
if (t instanceof CertificateExpiredException
|| t instanceof CertificateNotYetValidException)
return;
t = t.getCause();
}
throw e;
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return defaultTrustManager.getAcceptedIssuers();
}
}
在checkServerTrusted方法中捕获了CertificateException,并不断向上寻找原因。如果发现是CertificateExpiredException(证书过期)或者CertificateNotYetValidException(证书还未生效)则通过验证,否则继续抛出异常。
使用方法:
private static TrustManager[] prepareTrustManager(InputStream... certificates) {
...
return trustManagerFactory.getTrustManagers();
// TODO: 2016/11/11 针对有效期异常导致校验失败的情况,目前没有完美的解决方案
// TrustManager[] keyStoreTrustManagers = trustManagerFactory.getTrustManagers();
// return getNotValidateTimeTrustManagers((X509TrustManager[]) keyStoreTrustManagers);
...
}
将第一行注释,放开最后两行注释
但要注意,这个方案仍然有一定的安全隐患,因为查看底层进行SSL验证的源码后发现,对证书有效期的校验并不是最后的环节,例如在校验有效期通过后还会校验设备上的吊销证书列表,确认证书是否不在该列表中,其他还有一些自定义校验项。
上述方案在校验有效期异常后就会通过验证,漏掉了剩下的一些校验项,仍然存在一些隐患,如果安全级别要求非常高,并不推荐使用。
更多SSL验证问题可以参考:
https://developer.android.com/training/articles/security-ssl.html#MissingCa