【原文】https://blog.csdn.net/leng_wen_rou/article/details/58596142
本篇是Android 客戶端基於okhttp3的網絡框架 和后台服務器之間的雙向驗證
分為三個階段
一:簡單的后台服務器搭建
二:客戶端接入okhttp3,並進行的網絡請求
三:服務器和客戶端的雙向驗證
第一步: 搭建簡單的服務器
1:下載tomcat
2:配置tomcat
3:部署自己的web項目到tomcat
首先准備工具 eclipse 這個網上都有不多說,然后下載tomcat.
http://tomcat.apache.org/
下載后建立一個文件夾方便以后查找,解壓進入文件夾
進入bin文件夾中找到startup.bat 雙擊 會彈出命令框
有兩種情況
一種是命令框打開后正常啟動並一直存在
這種情況證明你的tomcat已經啟動了.打開你的瀏覽器輸入 localhost:8080 就可以看到默認的tomcat的頁面了
出現上面情況的朋友 tomcat已經配置好了接下來就是把自己的web項目部署到tomcat上
第二種情況就是點擊startup.bat 后 命令框一閃而過,點擊幾次都是同樣的情況.出現這種情況的朋友是需要配置tomcat的環境變量.給出如何配置tomcat環境變量的鏈接這里就不做過多的贅述.
http://jingyan.baidu.com/article/154b4631aad2bb28ca8f4191.html
環境變量配好后 啟動startup.bat 命令框就不會一閃而過了.然后輸入 localhost:8080 就能出現tomcat默認的頁面了
到這里tomcat已經可以使用接下來就是把自己的web項目部署到tomcat上了
打開eclipse 由於博主下成了中文版的eclipse了 和英文的 位置都是一樣的
點擊右鍵新建--->其他---->web---->動態Web項目 (英文是Dynamic Web Project)----->下一步
選擇你下載的對應tomcat 的版本就行
輸入自己的項目名稱 (我的是LH)點擊下一步,再點擊下一步出現下面的頁面 打鈎箭頭的部分(這是可選項 打鈎了會自動創建xml文件 新手建議都打),然后點擊完成
在左邊的目錄欄就可以看到剛剛創建的項目
在菜單欄里面選擇window----->show View----->Others
找server 點擊確定
點擊確定之后在日志欄那里會出現server 的頁面 紅色箭頭就是
點擊紫色箭頭
點擊紫色箭頭指的地方后選擇自己的tomcat版本點擊next
點擊完成 server頁面會出現剛添加的tomcat
右鍵找到 添加或刪除 點擊進入頁面 在左邊找到自己的web項目點擊添加后再點擊完成
在server頁面點擊tomcat后會出現添加后的web項目
點擊右邊的運行按鈕
接着控制台會輸入一大堆的日志
出現上面的情況就代表你的tomcat啟動成功了 然后在瀏覽器輸入 http://localhost:8080/ 就可以看到默認的tomcat頁面 這個時候可能會說了 我想進入我的項目頁面不想進入默認的頁面.
好的
在你eclipse 目錄下找到WebContent創建一個indext.html
點擊運行tomcat 在瀏覽器輸入 http://localhost:8080/LH/index.html LH (你的項目名) index.html (你創建的html文件 這個html文件里面有簡單的html標簽 我是網上隨便復制的)
到這里 第一步 搭建簡單的服務器 已經完成了
下面是搭服務器時我遇到的問題
在創建Web項目的時候在eclipse里面找不到Web選項.
這個問題是你的下載的eclispe是純凈版的 沒有Web的這個插件
點擊菜單欄里面的幫助按鈕選擇 安裝新的插件
點擊安裝新的插件后會進入新的頁面 在輸入連接的地方輸入 Eclipse Kepler repository - http://download.eclipse.org/releases/kepler 連接
輸入連接后 會等一會(根據個人網絡而定)會出現紫色箭頭的各種插件
向下拉找到Web插件
勾選
- Eclipse Java EE Developer Tools
- Eclipse Java Web Developer Tools
- Eclipse Web Developer Tools
- Eclipse XML Editors and Tools
這幾個 點擊下一步等待下載安裝,安裝成功后會提示你需要重新啟動eclispe .重新啟動以后點擊new others 里面就有Web項目了
第二步 Android 端接入okHttp3
我的用的是Android Strudio 所以本篇是以Android Studio為主的
首先創建自己的Demo項目(很簡單這里就不多說了)
在build.gradle添加okhttp3的依賴
compile 'com.squareup.okhttp3:okhttp:3.5.0'
eclipse下直接在github上下載對應的jar關聯到項目里面就行了
寫一個負責網絡請求的工具類 GetHttpRequest
private OkHttpClient okHttpClient ; private Request.Builder builder; private Handler handler; private Context context; public GetHttpRequest(Context context,Handler handler) { this.handler = handler; this.context = context;
this.okHttpClient = new OkHttpClient.Builder() //初始化全局 okhttpclient .connectTimeout(10, TimeUnit.SECONDS)//設置超時時間 .readTimeout(10, TimeUnit.SECONDS) .build();
builder = new Request.Builder(); }
public void doGet (String url , int type) throws IOException {
Request request = builder.get().url(url).build();//發送get請求
executeResponse(request,type); }
public void executeResponse(final Request request , final int type) {
Call call = okHttpClient.newCall(request);
// call.execute()
//SocketTimeoutException連接超時
call.enqueue( new Callback() {
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public void onFailure(Call call, IOException e) {
Log.i("lhh","Get e :"+e.toString()); }
@Override
public void onResponse(Call call, Response response) throws IOException {
Message message = new Message(); message.what = type; String json = response.body().string(); Log.i("lhh","Get json :"+json); message.obj = json; handler.sendMessage(message); } }); } }
使用的是okhttp3里面的get請求 里面簡單封裝了一下 (菜鳥一個 隨便封的 大神勿噴 還請大神指點封裝技巧)
使用get請求,收到結果后通過handler發送到ui線程顯示返回來的數據
onResponse 返回結果
onFailure 返回異常
在ui線程里面調用get請求
try { if(editText.getText().toString().trim() != null) { getHttpRequest.doGet(editText.getText().toString().trim(),1); }else { Toast.makeText(this, "不能為空", Toast.LENGTH_SHORT).show(); } } catch (IOException e) { e.printStackTrace(); }
記得在配置清單里面加入網絡權限哦
運行項目 接口就用csdn的地址 http://bbs.csdn.net/home
ok get請求成功
第三步:客戶端與服務器連接
現在把連接換成我們自己搭建的tomcat服務器的
需要注意的是 測試真機必須和你tomcat服務器在同一個網段內才能訪問
訪問的ip地址也不是localhost而是你本機的ip.
點擊開始輸入cmd 進入命令輸入框 輸入 ipconfig 復制IPv4 地址 替換localhost
現在連接你自己的tomcat 的地址是 http://192.168.1.60:8080/LH/index.html
瀏覽器也是可以正常訪問的
給APP換成新的接口,運行
可以很清晰的看到 LH Web 的字樣 說明我們自己搭建的服務器和Android 客戶端已經可以連接起來了
第三步也完成了!
細心的朋友發現了 說到現在連雙向驗證毛都沒看到就完了? 當然沒完 現在才開始准備講雙向驗證,之前都是基本的配置.在講雙向驗證之前,請允許我在費幾句話.
雙向驗證是https有的,也就是說http是沒有的.
之前我們的接口都是普通的http接口 不是https.那什么是https呢?和http有什么區別呢?
簡單的說就是https比http更安全,安全的原因就是添加ssl證書的驗證.
想知道詳細區別的朋友請出門左手百度.
首先我們要把自己搭建的tomcat服務器加一個https的接口
1:要是我們自己的tomcat支持https首先需要一個證書,這個證書可以是第三方機構認證的這個是正版的.還有一個就是我們自己生成的一個我們自己簽名的證書,接下來就是生成我們自己的證書
2:進入命令輸入框doc頁面 輸入下面的命令
keytool -genkey -alias lh_server -keyalg RSA -keystore lh_server.jks -validity 3600 -storepass 000000
-alias lh_server 是文件別名 -keystore lh_server.jks 生成的證書名字 -storepass 000000 密鑰密碼
按照提示輸入在你的C:\Users\Trust 底下就會出現剛生成的jks文件了
3:接着用剛生成的jks文件簽發證書在doc下輸入
keytool -export -alias lh_server -file lh_server.cer -keystore lh_server.jks -storepass000000
在你的C:\Users\Trust 目錄下會生成一個 lh_server.cer的證書文件 這個就是我們服務器的自簽名證書了
4:配置我們的服務器.找到你的tomcat的文件,在tomcat的文件夾下找到conf的文件點進去找到server.xml文件
5:使用文本編輯打開 搜索 port="8443" 可以看到默認是注釋掉的
把上下的注釋刪掉后添加這樣的屬性
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
keystoreFile="C:\Users\Trust\lh_server.jks" keystorePass="000000"
clientAuth="false" sslProtocol="TLS" />
可以直接復制替換
注意keystoreFile的值為我們剛才生成的jks文件的路徑:C:\Users\Trust\lh_server.jks
(填寫你的路徑).keystorePass值為密鑰庫密碼:000000
點擊保存 啟動tomcat 在瀏覽器里面輸入新的地址 和之前不一樣了 https://localhost:8443/LH/index.html 就會出現不安全的提示
這是因為證書不是第三方機構簽發的而是我們自己簽發的點擊高級里面的繼續進入
'
可以很清晰看到 顯示的不安全,到這一步服務器的配置稍微告一段落.現在直接用APP試着登錄我們經過自簽名證書的服務器看看
我們會發現APP上點擊按鈕后沒反應,在看看Android Studio 后台日志
我們可以看到后台打印出了一條錯誤信息仔細看錯誤信息
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
沒有找到證書,因為我們的APP還沒有做任何驗證證書的處理
ok現在處理我們的APP
上面這種處理有兩種方法可以解決
1:讓我們的APP默認信任所有的證書
2:把想要訪問的服務器的證書獲取到,手動把證書添加到okhttp3信任證書的白名單里面
這里兩種都講一下
第一種:
默認信任所有證書
首先在我們的Android項目里面新添加一個工具類 TrustALLCerts
public class TrustAllCerts implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws
CertificateException { } @Override public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws
CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } public static class TrustAllHostnameVerifier implements HostnameVerifier { @Override public boolean verify(String hostname, SSLSession session) { return true; } } public static SSLSocketFactory createSSLSocketFactory() { SSLSocketFactory ssfFactory = null; try { SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null, new TrustManager[] { new TrustAllCerts() }, new SecureRandom()); ssfFactory = sc.getSocketFactory(); } catch (Exception e) { } return ssfFactory; } }
上面是信任所有證書的代碼
然后修改GetHttpRequest里面的代碼
public class GetHttpRequest { private OkHttpClient okHttpClient ; private Request.Builder builder; private Handler handler; private Context context; public GetHttpRequest(Context context,Handler handler) { this.handler = handler; this.context = context;
this.okHttpClient = new OkHttpClient.Builder() .sslSocketFactory(TrustAllCerts.createSSLSocketFactory()) //這個aip已經過時 后面會用新的api替代 .hostnameVerifier(new TrustAllCerts.TrustAllHostnameVerifier()) .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build();
builder = new Request.Builder(); }
public void doGet (String url , int type) throws IOException {
Request request = builder.get().url(url).build();
executeResponse(request,type); }
public void executeResponse(final Request request , final int type) {
Call call = okHttpClient.newCall(request);
// call.execute()
//SocketTimeoutException連接超時
call.enqueue( new Callback() {
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public void onFailure(Call call, IOException e) {
Log.i("lhh","Get e :"+e.toString());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Message message = new Message();
message.what = type;
String json = response.body().string();
Log.i("lhh","Get json :"+json);
message.obj = json;
handler.sendMessage(message);
} }); } }
可以看到 我們在okhttpClient初始化的時候添加了一個
sslSocketFactory()api
把我們寫的默認信任所有證書的工具類加入到okhttpClient里面
好了現在運行項目;
ok 成功了 現在我們來解決 okhttp3提供的過時api用新的api替換
this.okHttpClient = new OkHttpClient.Builder() .sslSocketFactory(TrustAllCerts.createSSLSocketFactory(),
new TrustAllCerts()
) .hostnameVerifier(new TrustAllCerts.TrustAllHostnameVerifier())
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build();
在我們初始化okHttpClient 的時候使用新的api
sslSocketFactory();
和之前的api一樣的那是參數是兩個,之前的是一個參數.這個參數可以直接像我寫的那樣,還有一種方法比較麻煩的就是創建一個工具類Myx509
public class Myx509 implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws
CertificateException { } @Override public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws
CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
里面的參數就變成
.sslSocketFactory(TrustAllCerts.createSSLSocketFactory(),new Myx509())
這兩種都是可以使用的
然后就是獲取到指定服務器的證書后,把服務器證書添加到okHttpClinet中了,原理很簡單
獲取服務器證書(由於我們是自簽名證書所以不需要專門獲取服務器的證書)如果要使用指定的服務器的證書話,出門左拐百度謝謝
把證書添加到Android 項目里面的assets文件下
放到assets文件后修改網絡請求的代碼
基本代碼不變 新添加一個api setCertificates(InputStream ... certificates)
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() ); this.okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(),new Myx509()) .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build();
} catch (Exception e) { e.printStackTrace(); } }
可以看到新添加了我們自己寫的一個方法setCertificates(InputStream ... certificates) 這個方法是把assets中服務器證書通過流的形式讀取出來並把流添加到okHttpClient中. 把在構造方法里面初始化okHttpClient的操作放到這個方法里面並把證書流添加進去.
接着在ui線程進行網絡請求操作的時候先調用這個方法,在進行網絡請求
getHttpRequest = new GetHttpRequest(MainActivity.this,handler); try { getHttpRequest.setCertificates(getAssets().open("lh_server.cer")); } catch (IOException e) { e.printStackTrace(); }
然后運行項目
- - ! 怎么沒反應? 接着看as日志
錯誤貼出來
javax.net.ssl.SSLPeerUnverifiedException: Hostname 192.168.1.60 not verified:
certificate: sha256/mtDQaP/u/+7qzPWTX2YjVD+gWR5t0k5bAtcGOUtwnns=
DN: CN=lh,OU=lh,O=lh,L=sh,ST=sh,C=cn
subjectAltNames: []
- - ! 什么情況? 網上搜索一堆沒有找到相應的辦法,最后在借鑒了一位博主的博文 附上鏈接
http://blog.csdn.net/notHeadache/article/details/52133468
主要是講如何忽略所有證書 這和我們上面說的是默認信任所有證書的效果一樣,非常感謝這位博主,我從中借鑒了一個API.
hostnameVerifier()
這個api 這個API是驗證證書的api 借鑒的文章是重寫里面的方法 強制返回true
回到我們的代碼中,在初始化okHttpClient的地方添加這個api
this.okHttpClient = new OkHttpClient.Builder() .hostnameVerifier(new TrustAllCerts.TrustAllHostnameVerifier()) .sslSocketFactory(sslContext.getSocketFactory(),new Myx509()) .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build();
可以看到我們新添加的這個api hostnameVerifie(newTrustAllCerts.TrustAllHostnameVerifier())
我們看一下 new TrustAllCerts.TrustAllHostnameVerifier()這個類
public static class TrustAllHostnameVerifier implements HostnameVerifier { @Override public boolean verify(String hostname, SSLSession session) { return true; } }
這個類和之前說的一樣讓返回true.但我們這樣添加以后不是默認通過所有證書而是把我們的證書設置信任
好運行項目
可以正常訪問到我們的服務器了 ,如果覺得這樣寫可能是默認通過所有證書的朋友你可以隨便拿一個get請求的https的網站 我用的是 12306網站 https://kyfw.12306.cn/otn/
之后運行項目會報錯 提示你的 javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found. 不是一個信任的證書
當然也可以把證書用String的形式來用 把證書用String的形式保存只需要在調用setCertificates()方法修改一下參數格式就行了 setCertificates(new Buffer().writeUtf8(CER_lh).inputStream()) 其中CER_lh 是你保存證書的String變量名 然后運行項目 效果是一樣的
好了現在到了我們重點 雙向認證了 做到前面的這些操作只是完成了 單向認證 基本上多數應用已經夠用 但比如一
些金融機構就會用到
雙向認證.
雙向認證 就是 服務器生成自己的.jks和.cer證書文件 客戶端生成自己的.jks和.cer文件 服務器需要把客戶端的
證書密鑰添加到自己的密鑰庫里 接着客戶端做請求的時候帶着自己的證書和服務器的證書,服務器收到后與自己密鑰
庫里面的白名單做匹配 成功可以正常訪問失敗就拒絕訪問
我們之前已經生成 服務器的.jks 和.cer文件了 所以我們還用上面的方法創建客戶端的證書文件 取名 lh_client
生成好了后 我們要修改自己的服務器了 使我們的服務器支持雙向驗證
打開我們的server.xml文件繼續到 配置https 的地方配置
找到clientAuth="false" 改成 true 然后 新屬性
truststoreFile="C:\Users\Trust\lh_client.cer"
添加以后啟動tomcat會發現報錯了Invalid keystore format
這個錯是因為keystore的格式不合法
我們需要將客戶端證書添加到 .jks中
在doc下輸入命令
keytool -import -alias lh_client -file lh_clinet.cer -keystore lh_client_for_sever.jks
好了 在目下可以找到生成好的lh_client_for_sever.jks文件
在server.xml中配置路徑 truststoreFile="C:\Users\Trust\lh_client_for_sever.jks"
最后的配置是這樣的
<Connector SSLEnabled="true" clientAuth="true"
keystoreFile="C:\Users\Trust\lh_server.jks"
keystorePass="000000" maxThreads="150" port="8443"
protocol="org.apache.coyote.http11.Http11Protocol"
scheme="https" secure="true" sslProtocol="TLS"
truststoreFile="C:\Users\Trust\lh_client_for_sever.jks"/>
啟動我們的tomcat服務器 在瀏覽器輸入我們的鏈接 https://localhost:8443/LH/index.html 會出現
不接受您的登錄證書,或者您可能沒有提供登錄證書。
這個時候服務器設置成功了
使用我們的APP輸入上面的網址點擊確定 沒有反應,后台爆出一個錯
javax.net.ssl.SSLHandshakeException: Handshake failed
現在我們給APP添加雙向驗證的代碼 修改我們網路請求的代碼
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("BKS"); clientKeyStore.load(context.getAssets().open("lh_clinet.bks"), "000000".toCharArray()); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory
.getDefaultAlgorithm()); keyManagerFactory.init(clientKeyStore, "000000".toCharArray()); sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers()
, new SecureRandom()); // okHttpClient.setSslSocketFactory(sslContext.getSocketFactory()); this.okHttpClient = new OkHttpClient.Builder().sslSocketFactory(sslContext.
getSocketFactory()) .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build(); this.okHttpClient = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .sslSocketFactory(sslContext.getSocketFactory(),new TrustAllCerts()) .hostnameVerifier(new TrustAllCerts.TrustAllHostnameVerifier()) .readTimeout(10, TimeUnit.SECONDS) .build(); Log.i("lhh", "setCertificates: "); } catch (Exception e) { e.printStackTrace(); } }
可以看到我們添加了一段 Keystore的代碼 這個代碼里面有一個
lh_clinet.bks
這是什么東西呢? 這個就是我們的lh_clinet.jks 因為.jks在java 里面是默認支持的 但是在Android 支持的
是.bks文件 所以我們要把,jks文件轉換成.bks文件
轉換工具是 portecle-1.9 我會上傳或者大家可以百度進官網下載 使用步驟也很簡單就是安裝好后點擊
portecle.jar 文件 會自動打開相應的操作頁面 點擊File---->Open Keystore file 選擇客戶端的證書文件
lh_client.jks 輸入密鑰 在點擊 Tools Change Keystore Type ----->BKS 設置密鑰 會提示成功然后關閉
當前頁面 會提示你保存的位置 注意 保存的時候 必須帶上.bks 不然是無效的
http://download.csdn.net/detail/leng_wen_rou/9767218 工具鏈接
把生成的.bks文件放到項目的assets文件中
在項目中不要用.jks的要用.bks
不然會報錯
Java.io.IOException: Wrong version of key store
在代碼里面把服務器證書以及客戶端證書的密鑰寫入
運行項目:
ok 請求成功
感謝看到這里的朋友,篇幅比較長從頭到尾都講了一遍如果還有什么不明白的可以留言.寫這么長就是想把我遇到
的問題以及解決辦法都說出來,讓大家少走點彎路.
從AndroudStudio復制的代碼有點多看的很費勁,因為我不太會弄,希望大家多多包含.
謝謝
本文借鑒了 張洪洋大哥的博客 再次感謝
附上鏈接
http://blog.csdn.net/lmj623565791/article/details/48129405
END