Android 客戶端 okhttp3 與伺服器之間的雙向驗證
本篇是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環境變數的連結這裡就不做過多的贅述.
環境變數配好後 啟動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
第二步 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 會出現
不接受您的登入證書,或者您可能沒