Okhttp3 Https请求配置自签名证书
2019-11-16 本文已影响0人
王魔王
有关Https请求的原理本篇帖子不做过多赘述,今天要做的就是带领大家配置Https请求中自签名证书的问题
先贴一段OkHttp初始化的代码,这段代码地球人都知道
okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.readTimeout(5, TimeUnit.SECONDS)
.connectTimeout(5,TimeUnit.SECONDS)
.writeTimeout(5,TimeUnit.SECONDS)
`.hostnameVerifier()`//校验主机名,用于域名验证
`.sslSocketFactory()`//校验SSL证书
.build();
上面代码中,绿色部分的代码就是跟Https相关的代码了
先写一个简单的,即校验主机名的部分
这里需要一个HostnameVerifier
对象,这个对象直接new出来即可
new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
//hostname:本次请求的域名,如果域名是我们认可的域名,那么返回true,本次请求继续执行,返回false会中断本次请求
return hostname.equals("xxxxxxxxx");
//return true;如果不进行校验,直接return true,代表信任所有请求
}
}
那么代码就成了下面的样子👇
okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.readTimeout(5, TimeUnit.SECONDS)
.connectTimeout(5,TimeUnit.SECONDS)
.writeTimeout(5,TimeUnit.SECONDS)
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
//hostname:本次请求的域名,如果域名是我们认可的域名,那么返回true,本次请求继续执行,返回false会中断本次请求
return hostname.equals("xxxxxxxxx");
//return true;如果不进行校验,直接return true,代表信任所有请求
}
})`//校验主机名,用于域名验证
.sslSocketFactory()`//校验SSL证书
.build();
接下来是校验SSL证书部分的代码,这部分代码有点繁琐,大家照着步骤来即可
.sslSocketFactory()这里需要两个参数,一个是SSLSocketFactory,一个是X509TrustManager
,大家先不要关注这两个对象的作用,只需要关注这两个对象的初始化步骤即可
1、将下载的证书放到assets目录中
然后复制下面的代码,用于生成证书对象
/**
* 根据asset下证书的名字取出证书,然后变成流,在变成证书对象
*/
private static X509Certificate readCert(Context context, String assetName) {
InputStream inputStream = null;
try {
inputStream = context.getAssets().open(assetName);
} catch (IOException e) {
e.printStackTrace();
return null;
}
X509Certificate cert = null;
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
cert = (X509Certificate) cf.generateCertificate(inputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (Throwable ex) {
}
}
return cert;
}
2、自定义MyTrustManager类,类结构如下
/**
* 实现了 X509TrustManager
* 通过此类中的 checkServerTrusted 方法来确认服务器证书是否正确
*/
private static final class MyTrustManager implements X509TrustManager {
X509Certificate cert;
MyTrustManager(X509Certificate cert) {
this.cert = cert;
}
@Override// 我们在客户端只做服务器端证书校验。
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
/**
* @param chain 服务端返回的证书数组,因为服务器可能有多个https证书,我们在这里的
* 逻辑就是拿到第一个证书,然后和本地证书判断,如果不一致,异常!!!
*/
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// 确认服务器端证书和代码中 hard code 的 CRT 证书相同。
// 这里因为我们服务器只有一个证书,没有遍历,如果有多个,这里是for循环取出挨个判断
if (chain[0].equals(this.cert)) {
return;
}
throw new CertificateException("checkServerTrusted No trusted server cert found!");
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
}
3、定义初始化SslSocketFactory的方法
//通过下载的证书文件、自定义的MyTrustManager对象来初始化SslSocketFactory
private void initSslSocketFactory(){
try {
sslContext = SSLContext.getInstance("TLS");
//从assets文件夹下根据证书名字读取证书,变成一个可用的证书对象
x509Certificate = readCert(App.context, CERTIFICATE_NAME);
//校验服务端和本地证书是否一致
mTrustManager = new MyTrustManager(x509Certificate);
//初始化必要的对象,固定格式直接使用即可
sslContext.init(null, new TrustManager[]{
mTrustManager
}, new java.security.SecureRandom());
mSslSocketFactory = sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
}
到此,有关SslSocketFactory的构建工作就完成了,我们只需要在OkHttp的初始化代码中使用有关对象即可
为了方便使用,我们应该把上面的代码都统一封装到一个工具类中
于是代码就成了现在的样子
/**
* @Author:AbnerMing
* @Description:
* @Date:2019/11/7 8:53
*/
public class SSLSocketFactoryUtils {
private static final String HOST_NAME = "xxxxxx";//请求服务器的主机名称
private static String CERTIFICATE_NAME = "xxxx.crt";//下载的证书的文件名称
//证书
private X509Certificate x509Certificate;
//需要配置给ok的SSLSocketFactory
private SSLSocketFactory mSslSocketFactory;
private SSLContext sslContext;
//证书管理者
private MyTrustManager mTrustManager;
private static SSLSocketFactoryUtils instance;
private SSLSocketFactoryUtils() {
initSslSocketFactory();
}
private void initSslSocketFactory(){
try {
sslContext = SSLContext.getInstance("TLS");
//从assets文件夹下根据证书名字读取证书,变成一个可用的证书对象
x509Certificate = readCert(App.context, CERTIFICATE_NAME);
//校验服务端和本地证书是否一致
mTrustManager = new MyTrustManager(x509Certificate);
//初始化必要的对象,固定格式直接使用即可
sslContext.init(null, new TrustManager[]{
mTrustManager
}, new java.security.SecureRandom());
mSslSocketFactory = sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
}
public static SSLSocketFactoryUtils getInstance() {
if (instance == null) {
instance=new SSLSocketFactoryUtils();
}
return instance;
}
/**
* 根据asset下证书的名字取出证书,然后变成流,在变成证书对象
*/
private static X509Certificate readCert(Context context, String assetName) {
InputStream inputStream = null;
try {
inputStream = context.getAssets().open(assetName);
} catch (IOException e) {
e.printStackTrace();
return null;
}
X509Certificate cert = null;
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
cert = (X509Certificate) cf.generateCertificate(inputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (Throwable ex) {
}
}
return cert;
}
public HostnameVerifier getHostnameVerifier() {
return hostnameVerifier;
}
public SSLSocketFactory getmSslSocketFactory() {
return mSslSocketFactory;
}
public MyTrustManager getmTrustManager() {
return mTrustManager;
}
/**
* 实现了 X509TrustManager
* 通过此类中的 checkServerTrusted 方法来确认服务器证书是否正确
*/
private static final class MyTrustManager implements X509TrustManager {
X509Certificate cert;
MyTrustManager(X509Certificate cert) {
this.cert = cert;
}
@Override// 我们在客户端只做服务器端证书校验。
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
/**
* @param chain 服务端返回的证书数组,因为服务器可能有多个https证书,我们在这里的
* 逻辑就是拿到第一个证书,然后和本地证书判断,如果不一致,异常!!!
*/
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// 确认服务器端证书和代码中 hard code 的 CRT 证书相同。
// 这里因为我们服务器只有一个证书,没有遍历,如果有多个,这里是for循环取出挨个判断
if (chain[0].equals(this.cert)) {
return;
}
throw new CertificateException("checkServerTrusted No trusted server cert found!");
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
}
/**
* 服务器域名验证,拿到请求接口的域名和本地配的域名进行比较,如果一样返回True
*/
private final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return hostname.equals(HOST_NAME);
//return true;
}
};
}
最后使用
okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.readTimeout(5, TimeUnit.SECONDS)
.connectTimeout(5,TimeUnit.SECONDS)
.writeTimeout(5,TimeUnit.SECONDS)
.hostnameVerifier(SSLSocketFactoryUtils.getInstance().getHostnameVerifier())
.sslSocketFactory(SSLSocketFactoryUtils.getInstance().getmSslSocketFactory(),
SSLSocketFactoryUtils.getInstance().getmTrustManager())
.build();