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() { @Override public void onFailure(Request request, IOException e) { } @Override public 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() { @Override public 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
封装
-
一般的get请求
OkHttpClientManager.getAsyn("https://www.baidu.com", new OkHttpClientManager.ResultCallback<String>() { @Override public void onError(Request request, Exception e) { e.printStackTrace(); } @Override public void onResponse(String u) { mTv.setText(u);//注意这里是UI线程 } });
对于一般的请求,我们希望给个url,然后CallBack里面直接操作控件。 -
文件上传且携带参数
我们希望提供一个方法,传入url,params,file,callback即可。
OkHttpClientManager.postAsyn("http://192.168.1.103:8080/okHttpServer/fileUpload",// new OkHttpClientManager.ResultCallback<String>() { @Override public void onError(Request request, IOException e) { e.printStackTrace(); } @Override public void onResponse(String result) { } },// file,// "mFile",// new OkHttpClientManager.Param[]{ new OkHttpClientManager.Param("username", "zhy"), new OkHttpClientManager.Param("password", "123")} );
对于文件下载,提供url,目标dir,callback即可。
-
展示图片,我们希望提供一个url和一个imageview,如果下载成功,直接帮我们设置上即可。
OkHttpClientManager.displayImage(mImageView, "http://images.****.net/20150817/1.jpg");
服务端返回:
{"username":"zhy","password":"123"}客户端可以如下方式调用:
<span style="font-size:14px;">OkHttpClientManager.getAsyn("http://192.168.56.1:8080/okHttpServer/user!getUser", new OkHttpClientManager.ResultCallback<User>() { @Override public void onError(Request request, Exception e) { e.printStackTrace(); } @Override public 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>>() { @Override public void onError(Request request, Exception e) { e.printStackTrace(); } @Override public 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 { @Override public 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>() { @Override public void onError(Request request, Exception e) { e.printStackTrace(); } @Override public 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-----"; @Override public 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.kjs
和zhy_server.cer
文件。
接下来按照生成证书的方式,再生成一对这样的文件,我们命名为:zhy_client.kjs
,zhy_client.cer
.
(一)配置服务端
看博客:http://blog.****.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); //初始化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()); 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证书?
- 1楼u013424496昨天 22:25
- http://pay.iteye.com/blog/1522784 jks ,bks pkcs12转换nnnorg.bouncycastle.LICENSE