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

相关文章

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

SpringBoot启动报错的11个高频问题排查与解决终极指南

《SpringBoot启动报错的11个高频问题排查与解决终极指南》这篇文章主要为大家详细介绍了SpringBoot启动报错的11个高频问题的排查与解决,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一... 目录1. 依赖冲突:NoSuchMethodError 的终极解法2. Bean注入失败:No qu

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

MySQL新增字段后Java实体未更新的潜在问题与解决方案

《MySQL新增字段后Java实体未更新的潜在问题与解决方案》在Java+MySQL的开发中,我们通常使用ORM框架来映射数据库表与Java对象,但有时候,数据库表结构变更(如新增字段)后,开发人员可... 目录引言1. 问题背景:数据库与 Java 实体不同步1.1 常见场景1.2 示例代码2. 不同操作

Python GUI框架中的PyQt详解

《PythonGUI框架中的PyQt详解》PyQt是Python语言中最强大且广泛应用的GUI框架之一,基于Qt库的Python绑定实现,本文将深入解析PyQt的核心模块,并通过代码示例展示其应用场... 目录一、PyQt核心模块概览二、核心模块详解与示例1. QtCore - 核心基础模块2. QtWid

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

如何自定义Nginx JSON日志格式配置

《如何自定义NginxJSON日志格式配置》Nginx作为最流行的Web服务器之一,其灵活的日志配置能力允许我们根据需求定制日志格式,本文将详细介绍如何配置Nginx以JSON格式记录访问日志,这种... 目录前言为什么选择jsON格式日志?配置步骤详解1. 安装Nginx服务2. 自定义JSON日志格式各