okHttp框架的介绍 和关于https的自定义签名证书的问题

2024-09-07 08:58

本文主要是介绍okHttp框架的介绍 和关于https的自定义签名证书的问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

参考博客:【张鸿洋的博客】 Android Https相关完全解析 当OkHttp遇到Https

1.okhttp的介绍:


 它能够处理:

  • 一般的get请求
  • 一般的post请求
  • 基于Http的文件上传
  • 文件下载
  • 加载图片
  • 支持请求回调,直接返回对象、对象集合
  • 支持session的保持

开发平台使用:

    使用前,对于Android Studio的用户,可以选择添加:

compile 'com.squareup.okhttp:okhttp:2.4.0'

   或者Eclipse的用户,可以下载最新的jar okhttp he latest JAR ,添加依赖就可以用了。

   注意:okhttp内部依赖okio,别忘了同时导入okio:

   gradle: compile 'com.squareup.okio:okio:1.5.0'

   最新的jar地址:okio the latest JAR

基本使用:

(一) Get

<span style="font-size:14px;">//创建okHttpClient对象
OkHttpClient mOkHttpClient = new OkHttpClient();
//创建一个Request
final Request request = new Request.Builder().url("https://github.com/hongyangAndroid").build();
//new call
Call call = mOkHttpClient.newCall(request); 
//请求加入调度
call.enqueue(new Callback(){@Overridepublic void onFailure(Request request, IOException e){}@Overridepublic void onResponse(final Response response) throws IOException{//String htmlStr =  response.body().string();}});  </span>

注意:onResponse回调的参数是response,一般情况下,比如我们希望获得返回的字符串,可以通过response.body().string()获取;如果希望获得返回的二进制字节数组,则调用response.body().bytes();如果你想拿到返回的inputStream,则调用response.body().byteStream()

看到这,你可能会奇怪,竟然还能拿到返回的inputStream,看到这个最起码能意识到一点,这里支持大文件下载,有inputStream我们就可以通过IO的方式写文件。不过也说明一个问题,这个onResponse执行的线程并不是UI线程。的确是的,如果你希望操作控件,还是需要使用handler等,例如:

<span style="font-size:14px;">@Override
public void onResponse(final Response response) throws IOException
{final String res = response.body().string();runOnUiThread(new Runnable(){@Overridepublic void run(){mTv.setText(res);}});
}</span>


(二) Http Post 携带参数
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {RequestBody formBody = new FormEncodingBuilder().add("platform", "android").add("name", "bug").add("subject", "XXXXXXXXXXXXXXX").build();Request request = new Request.Builder().url(url).post(body).build();Response response = client.newCall(request).execute();if (response.isSuccessful()) {return response.body().string();} else {throw new IOException("Unexpected code " + response);}
}
更详细的请看 :http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0106/2275.html

封装

  1. 一般的get请求  

     OkHttpClientManager.getAsyn("https://www.baidu.com", new OkHttpClientManager.ResultCallback<String>(){@Overridepublic void onError(Request request, Exception e){e.printStackTrace();}@Overridepublic void onResponse(String u){mTv.setText(u);//注意这里是UI线程}});

        对于一般的请求,我们希望给个url,然后CallBack里面直接操作控件。

  2. 文件上传且携带参数

    我们希望提供一个方法,传入url,params,file,callback即可。

     OkHttpClientManager.postAsyn("http://192.168.1.103:8080/okHttpServer/fileUpload",//new OkHttpClientManager.ResultCallback<String>(){@Overridepublic void onError(Request request, IOException e){e.printStackTrace();}@Overridepublic void onResponse(String result){}},//file,//"mFile",//new OkHttpClientManager.Param[]{new OkHttpClientManager.Param("username", "zhy"),new OkHttpClientManager.Param("password", "123")});

    </pre><p style="margin-top:0px; margin-bottom:1.1em; padding-top:0px; padding-bottom:0px">对应于http中的</p><p style="margin-top:0px; margin-bottom:1.1em; padding-top:0px; padding-bottom:0px"><code style="font-family:'Source Code Pro',monospace; padding:2px 4px; font-size:12.6px; color:rgb(63,63,63); white-space:nowrap"><input type="file" name="mFile" ></code></p><p style="margin-top:0px; margin-bottom:1.1em; padding-top:0px; padding-bottom:0px">对应的是name后面的值,即mFile.</p></li><li style="margin:0px; padding:0px"><p style="margin-top:0px; margin-bottom:1.1em; padding-top:0px; padding-bottom:0px">文件下载</p><pre code_snippet_id="1651558" snippet_file_name="blog_20160418_7_808136" name="code" class="html">OkHttpClientManager.downloadAsyn("http://192.168.1.103:8080/okHttpServer/files/messenger_01.png",    Environment.getExternalStorageDirectory().getAbsolutePath(), 
    new OkHttpClientManager.ResultCallback<String>(){@Overridepublic void onError(Request request, IOException e){}@Overridepublic void onResponse(String response){//文件下载成功,这里回调的reponse为文件的absolutePath}
    });

    对于文件下载,提供url,目标dir,callback即可。

  3. 展示图片,我们希望提供一个url和一个imageview,如果下载成功,直接帮我们设置上即可。

     OkHttpClientManager.displayImage(mImageView, "http://images.csdn.net/20150817/1.jpg");

5.对返回的数据用GSON进行整合

服务端返回:

{"username":"zhy","password":"123"}
客户端可以如下方式调用:

 <span style="font-size:14px;">OkHttpClientManager.getAsyn("http://192.168.56.1:8080/okHttpServer/user!getUser",
new OkHttpClientManager.ResultCallback<User>()
{@Overridepublic void onError(Request request, Exception e){e.printStackTrace();}@Overridepublic void onResponse(User user){mTv.setText(u.toString());//UI线程}
});</span>

我们传入泛型User,在onResponse里面直接回调User对象。

这里特别要注意的事,如果在json字符串->实体对象过程中发生错误,程序不会崩溃,onError方法会被回调。

注意:这里做了少许的更新,接口命名从StringCallback修改为ResultCallback。接口中的onFailure方法修改为onError

服务端返回

[{"username":"zhy","password":"123"},{"username":"lmj","password":"12345"}]
则客户端可以如下调用:

<span style="font-size:14px;">OkHttpClientManager.getAsyn("http://192.168.56.1:8080/okHttpServer/user!getUsers",
new OkHttpClientManager.ResultCallback<List<User>>()
{@Overridepublic void onError(Request request, Exception e){e.printStackTrace();}@Overridepublic void onResponse(List<User> us){Log.e("TAG", us.size() + "");mTv.setText(us.get(1).toString());}
});</span>

封装的代码下载地址:https://github.com/hongyangAndroid/okhttp-utils


OkHttp遇到Https

okhttp默认情况下是支持https协议的网站的,比如 https://www.baidu.com https://github.com/hongyangAndroid/okhttp-utils 等,你可以直接通过okhttp请求试试。不过要注意的是,支持的https的网站基本都是CA机构颁发的证书,默认情况下是可以信任的。当然我们今天要说的是自签名的网站,什么叫自签名呢?可以点击查看:为你的android App实现自签名的ssl证书(https)

OkHttpClient去信任我们的证书,接下里的例子就是靠12306这个福利站点了。

首先导出12306的证书,这里12306提供了下载地址:12306证书点击下载

下载完成,解压拿到里面的srca.cer,一会需要使用。ps:即使没有提供下载,也可以通过浏览器导出的,自行百度。

1.代码

(一)、访问自签名的网站

首先把我们下载的srca.cer放到assets文件夹下,其实你可以随便放哪,反正能读取到就行。

然后在我们的OkHttpClientManager里面添加如下的方法:

public void setCertificates(InputStream... certificates)
{try{CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());keyStore.load(null);int index = 0;for (InputStream certificate : certificates){String certificateAlias = Integer.toString(index++);keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));try{if (certificate != null)certificate.close();} catch (IOException e){}}SSLContext sslContext = SSLContext.getInstance("TLS");TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore);sslContext.init(   null, trustManagerFactory.getTrustManagers(), new SecureRandom());mOkHttpClient.setSslSocketFactory(sslContext.getSocketFactory());} catch (Exception e){e.printStackTrace();} 
}

 

为了代码可读性,我把异常捕获的部分简化了,可以看到我们提供了一个方法传入InputStream流,InputStream就对应于我们证书的输入流。

代码内部,我们:

  • 构造CertificateFactory对象,通过它的generateCertificate(is)方法得到Certificate。
  • 然后讲得到的Certificate放入到keyStore中。
  • 接下来利用keyStore去初始化我们的TrustManagerFactory
  • trustManagerFactory.getTrustManagers获得TrustManager[]初始化我们的SSLContext
  • 最后,设置我们mOkHttpClient.setSslSocketFactory即可。

这样就完成了我们代码的编写,其实挺短的,当客户端进行SSL连接时,就可以根据我们设置的证书去决定是否新人服务端的证书。

记得在Application中进行初始化:

public class MyApplication extends Application
{@Overridepublic void onCreate(){super.onCreate();try{OkHttpClientManager.getInstance().setCertificates(getAssets().open("srca.cer"));} catch (IOException e){e.printStackTrace();}
}

然后尝试以下代码访问12306的网站:
<span style="font-size:14px;">OkHttpClientManager.getAsyn("https://kyfw.12306.cn/otn/", new OkHttpClientManager.ResultCallback<String>()
{@Overridepublic void onError(Request request, Exception e){e.printStackTrace();}@Overridepublic void onResponse(String u){mTv.setText(u);}
});</span>

 
 

这样即可访问成功。完整代码已经更新至:https://github.com/hongyangAndroid/okhttp-utils,可以下载里面的sample进行测试,里面包含12306的证书。

  使用字符串替代证书

下面继续,有些人可能觉得把证书copy到assets下还是觉得不舒服,其实我们还可以将证书中的内容提取出来,写成字符串常量,这样就不需要证书根据着app去打包了。

zhydeMacBook-Pro:temp zhy$ keytool -printcert -rfc -file srca.cer
-----BEGIN CERTIFICATE-----
MIICmjCCAgOgAwIBAgIIbyZr5/jKH6QwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ04xKTAn
BgNVBAoTIFNpbm9yYWlsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRTUkNBMB4X
DTA5MDUyNTA2NTYwMFoXDTI5MDUyMDA2NTYwMFowRzELMAkGA1UEBhMCQ04xKTAnBgNVBAoTIFNp
bm9yYWlsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRTUkNBMIGfMA0GCSqGSIb3
DQEBAQUAA4GNADCBiQKBgQDMpbNeb34p0GvLkZ6t72/OOba4mX2K/eZRWFfnuk8e5jKDH+9BgCb2
9bSotqPqTbxXWPxIOz8EjyUO3bfR5pQ8ovNTOlks2rS5BdMhoi4sUjCKi5ELiqtyww/XgY5iFqv6
D4Pw9QvOUcdRVSbPWo1DwMmH75It6pk/rARIFHEjWwIDAQABo4GOMIGLMB8GA1UdIwQYMBaAFHle
tne34lKDQ+3HUYhMY4UsAENYMAwGA1UdEwQFMAMBAf8wLgYDVR0fBCcwJTAjoCGgH4YdaHR0cDov
LzE5Mi4xNjguOS4xNDkvY3JsMS5jcmwwCwYDVR0PBAQDAgH+MB0GA1UdDgQWBBR5XrZ3t+JSg0Pt
x1GITGOFLABDWDANBgkqhkiG9w0BAQUFAAOBgQDGrAm2U/of1LbOnG2bnnQtgcVaBXiVJF8LKPaV
23XQ96HU8xfgSZMJS6U00WHAI7zp0q208RSUft9wDq9ee///VOhzR6Tebg9QfyPSohkBrhXQenvQ
og555S+C3eJAAVeNCTeMS3N/M5hzBRJAoffn3qoYdAO1Q8bTguOi+2849A==
-----END CERTIFICATE-----

使用keytool命令,以rfc样式输出。keytool命令是JDK里面自带的。

有了这个字符串以后,我们就不需要srca.cer这个文件了,直接编写以下代码:

<span style="font-size:14px;">public class MyApplication extends Application
{private String CER_12306 = "-----BEGIN CERTIFICATE-----\n" +"MIICmjCCAgOgAwIBAgIIbyZr5/jKH6QwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ04xKTAn\n" +"BgNVBAoTIFNpbm9yYWlsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRTUkNBMB4X\n" +"DTA5MDUyNTA2NTYwMFoXDTI5MDUyMDA2NTYwMFowRzELMAkGA1UEBhMCQ04xKTAnBgNVBAoTIFNp\n" +"bm9yYWlsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRTUkNBMIGfMA0GCSqGSIb3\n" +"DQEBAQUAA4GNADCBiQKBgQDMpbNeb34p0GvLkZ6t72/OOba4mX2K/eZRWFfnuk8e5jKDH+9BgCb2\n" +"9bSotqPqTbxXWPxIOz8EjyUO3bfR5pQ8ovNTOlks2rS5BdMhoi4sUjCKi5ELiqtyww/XgY5iFqv6\n" +"D4Pw9QvOUcdRVSbPWo1DwMmH75It6pk/rARIFHEjWwIDAQABo4GOMIGLMB8GA1UdIwQYMBaAFHle\n" +"tne34lKDQ+3HUYhMY4UsAENYMAwGA1UdEwQFMAMBAf8wLgYDVR0fBCcwJTAjoCGgH4YdaHR0cDov\n" +"LzE5Mi4xNjguOS4xNDkvY3JsMS5jcmwwCwYDVR0PBAQDAgH+MB0GA1UdDgQWBBR5XrZ3t+JSg0Pt\n" +"x1GITGOFLABDWDANBgkqhkiG9w0BAQUFAAOBgQDGrAm2U/of1LbOnG2bnnQtgcVaBXiVJF8LKPaV\n" +"23XQ96HU8xfgSZMJS6U00WHAI7zp0q208RSUft9wDq9ee///VOhzR6Tebg9QfyPSohkBrhXQenvQ\n" +"og555S+C3eJAAVeNCTeMS3N/M5hzBRJAoffn3qoYdAO1Q8bTguOi+2849A==\n" +"-----END CERTIFICATE-----";@Overridepublic void onCreate(){super.onCreate();OkHttpClientManager.getInstance().setCertificates(new Buffer().writeUtf8(CER_12306).inputStream());
}</span>

注意Buffer是okio包下的,okhttp依赖okio。

ok,这样就省去将cer文件一起打包进入apk了。

双向证书验证

首先对于双向证书验证,也就是说,客户端也会有个“kjs文件”,服务器那边会同时有个“cer文件”与之对应。

我们已经生成了zhy_server.kjszhy_server.cer文件。

接下来按照生成证书的方式,再生成一对这样的文件,我们命名为:zhy_client.kjs,zhy_client.cer.

(一)配置服务端

看博客:http://blog.csdn.net/lmj623565791/article/details/48129405

我们将目标来到客户端,即我们的Android端,我们的Android端,如何设置kjs文件呢。

(二)配置app端

目前我们app端依靠的应该是zhy_client.kjs

ok,大家还记得,我们在支持https的时候调用了这么俩行代码:

<span style="font-size:14px;">sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
mOkHttpClient.setSslSocketFactory(sslContext.getSocketFactory());</span>

注意sslContext.init的第一个参数我们传入的是null,第一个参数的类型实际上是KeyManager[] km,主要就用于管理我们客户端的key。

于是代码可以这么写:

<span style="font-size:14px;">public void setCertificates(InputStream... certificates)
{try{CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());keyStore.load(null);int index = 0;for (InputStream certificate : certificates){String certificateAlias = Integer.toString(index++);keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));try{if (certificate != null)certificate.close();} catch (IOException e){}}SSLContext sslContext = SSLContext.getInstance("TLS");TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());trustManagerFactory.init(keyStore);//初始化keystoreKeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());clientKeyStore.load(mContext.getAssets().open("zhy_client.jks"), "123456".toCharArray());KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());keyManagerFactory.init(clientKeyStore, "123456".toCharArray());sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());mOkHttpClient.setSslSocketFactory(sslContext.getSocketFactory());} catch (Exception e){e.printStackTrace();} }</span>

核心代码其实就是:

//初始化keystore
KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
clientKeyStore.load(mContext.getAssets().open("zhy_client.jks"), "123456".toCharArray());KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, "123456".toCharArray());sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());

然而此时启动会报错:java.io.IOException: Wrong version of key store.

为什么呢?

因为:Java平台默认识别jks格式的证书文件,但是android平台只识别bks格式的证书文件。

这么就纠结了,我们需要将我们的jks文件转化为bks文件,怎么转化呢?

这里的方式可能比较多,大家可以百度,我推荐一种方式:

去Portecle下载Download portecle-1.9.zip (3.4 MB)。

解压后,里面包含bcprov.jar文件,使用java -jar C:\portecle\bcprov.jar即可打开GUI界面。

按照上图即可将zhy_client.jks转化为zhy_client.bks

然后将zhy_client.bks拷贝到assets目录下,修改代码为:

//初始化keystore
KeyStore clientKeyStore = KeyStore.getInstance("BKS");
clientKeyStore.load(mContext.getAssets().open("zhy_client.bks"), "123456".toCharArray());KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, "123456".toCharArray());sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());

再次运行即可。然后就成功的做到了双向的验证,关于双向这块大家了解下即可。

源码都在https://github.com/hongyangAndroid/okhttp-utils之中。


注:在上述的.jks转化为.bks会出现\bcprov-ext-jdk16-146.jar中没有主清单属性的错误,查看使用如何用第三方开源免费软件portecle从https网站上导出SSL的CA证书?



这篇关于okHttp框架的介绍 和关于https的自定义签名证书的问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1144638

相关文章

Java中JSON格式反序列化为Map且保证存取顺序一致的问题

《Java中JSON格式反序列化为Map且保证存取顺序一致的问题》:本文主要介绍Java中JSON格式反序列化为Map且保证存取顺序一致的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未... 目录背景问题解决方法总结背景做项目涉及两个微服务之间传数据时,需要提供方将Map类型的数据序列化为co

如何解决Druid线程池Cause:java.sql.SQLRecoverableException:IO错误:Socket read timed out的问题

《如何解决Druid线程池Cause:java.sql.SQLRecoverableException:IO错误:Socketreadtimedout的问题》:本文主要介绍解决Druid线程... 目录异常信息触发场景找到版本发布更新的说明从版本更新信息可以看到该默认逻辑已经去除总结异常信息触发场景复

java中BigDecimal里面的subtract函数介绍及实现方法

《java中BigDecimal里面的subtract函数介绍及实现方法》在Java中实现减法操作需要根据数据类型选择不同方法,主要分为数值型减法和字符串减法两种场景,本文给大家介绍java中BigD... 目录Java中BigDecimal里面的subtract函数的意思?一、数值型减法(高精度计算)1.

Pytorch介绍与安装过程

《Pytorch介绍与安装过程》PyTorch因其直观的设计、卓越的灵活性以及强大的动态计算图功能,迅速在学术界和工业界获得了广泛认可,成为当前深度学习研究和开发的主流工具之一,本文给大家介绍Pyto... 目录1、Pytorch介绍1.1、核心理念1.2、核心组件与功能1.3、适用场景与优势总结1.4、优

VS配置好Qt环境之后但无法打开ui界面的问题解决

《VS配置好Qt环境之后但无法打开ui界面的问题解决》本文主要介绍了VS配置好Qt环境之后但无法打开ui界面的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 目UKeLvb录找到Qt安装目录中designer.UKeLvBexe的路径找到vs中的解决方案资源

MySQL启动报错:InnoDB表空间丢失问题及解决方法

《MySQL启动报错:InnoDB表空间丢失问题及解决方法》在启动MySQL时,遇到了InnoDB:Tablespace5975wasnotfound,该错误表明MySQL在启动过程中无法找到指定的s... 目录mysql 启动报错:InnoDB 表空间丢失问题及解决方法错误分析解决方案1. 启用 inno

在Spring Boot中实现HTTPS加密通信及常见问题排查

《在SpringBoot中实现HTTPS加密通信及常见问题排查》HTTPS是HTTP的安全版本,通过SSL/TLS协议为通讯提供加密、身份验证和数据完整性保护,下面通过本文给大家介绍在SpringB... 目录一、HTTPS核心原理1.加密流程概述2.加密技术组合二、证书体系详解1、证书类型对比2. 证书获

Druid连接池实现自定义数据库密码加解密功能

《Druid连接池实现自定义数据库密码加解密功能》在现代应用开发中,数据安全是至关重要的,本文将介绍如何在​​Druid​​连接池中实现自定义的数据库密码加解密功能,有需要的小伙伴可以参考一下... 目录1. 环境准备2. 密码加密算法的选择3. 自定义 ​​DruidDataSource​​ 的密码解密3

Java使用MethodHandle来替代反射,提高性能问题

《Java使用MethodHandle来替代反射,提高性能问题》:本文主要介绍Java使用MethodHandle来替代反射,提高性能问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录一、认识MethodHandle1、简介2、使用方式3、与反射的区别二、示例1、基本使用2、(重要)

Java实现本地缓存的常用方案介绍

《Java实现本地缓存的常用方案介绍》本地缓存的代表技术主要有HashMap,GuavaCache,Caffeine和Encahche,这篇文章主要来和大家聊聊java利用这些技术分别实现本地缓存的方... 目录本地缓存实现方式HashMapConcurrentHashMapGuava CacheCaffe