Retrofit Https踩坑記錄
前言
新司機上路仑扑,坑多,本文重點是踩坑菲宴,不詳細講retrofit用法速蕊,本文不推薦使用信任所有證書的做法途乃。
證書
分為多種格式, bks cer jks等,這里使用的是bks格式證書幻枉。
BKS 做法
1.獲取BKS證書碰声,將證書放到項目raw目錄下
準備.cer文件
點擊網(wǎng)站網(wǎng)址欄前的小鎖按鈕,選擇詳細信息熬甫,選擇view certificate胰挑。
顯示證書之后,點擊詳細信息,然后一直下一步洽腺,直到導(dǎo)出.cer文件脚粟。
[圖片上傳失敗...(image-a8333b-1525252236881)]
[圖片上傳失敗...(image-c1a762-1525252236881)]
[圖片上傳失敗...(image-d3e71b-1525252236881)]
[圖片上傳失敗...(image-47173e-1525252236881)]
[圖片上傳失敗...(image-1de838-1525252236881)]
[圖片上傳失敗...(image-812324-1525252236881)]
[圖片上傳失敗...(image-99f797-1525252236881)]
將.cer轉(zhuǎn)換為.bks
在Android應(yīng)用中使用自定義證書,CER轉(zhuǎn)BKS
做法:1覆旱,下載特定版本的JCE Provider包
http://pan.baidu.com/s/1c1ur13y
or
http://www.bouncycastle.org/download/bcprov-jdk15on-146.jar (現(xiàn)在連接失效)
2蘸朋,命令行輸入以下命令
keytool -importcert -v -trustcacerts -alias 位置1 \
-file 位置2 \
-keystore 位置3 -storetype BKS \
-providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath 位置4 -storepass 位置5
位置1:是個隨便取的別名
位置2:cer或crt證書的全地址
位置3:生成后bks文件的位置,建議寫全地址
位置4:上面下載JCE Provider包的位置
位置5:生成后證書的密碼。下邊獲取sslsocketfactory中會用到密碼
以下例子:
keytool -importcert -v -trustcacerts -alias xx -file E:\bks\xx.cer -keystore E:\bks\xx.bks -storetype BKS -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath E:\bks\bcprov-jdk15on-146.jar -storepass xxxxxx
成功之后會在你指定的位置生成bks文件.然后將文件放到項目raw目錄下扣唱。
2.獲取SSLSocketFactory
這里是https證書認證最關(guān)鍵的代碼藕坯,一定要仔細查看。password和設(shè)置keystore的bks類型一定不要搞錯噪沙。
/**
* 獲取bks文件的sslsocketfactory
* @param context
* @return
*/
public static SSLSocketFactory getSSLSocketFactory(Context context) {
final String CLIENT_TRUST_PASSWORD = "123456";//信任證書密碼炼彪,該證書默認密碼是123456
final String CLIENT_AGREEMENT = "TLS";//使用協(xié)議
final String CLIENT_TRUST_KEYSTORE = "BKS";
SSLContext sslContext = null;
try {
//取得SSL的SSLContext實例
sslContext = SSLContext.getInstance(CLIENT_AGREEMENT);
//取得TrustManagerFactory的X509密鑰管理器實例
TrustManagerFactory trustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
//取得BKS密庫實例
KeyStore tks = KeyStore.getInstance(CLIENT_TRUST_KEYSTORE);
InputStream is = context.getResources().openRawResource(R.raw.traint);
try {
tks.load(is, CLIENT_TRUST_PASSWORD.toCharArray());
} finally {
is.close();
}
//初始化密鑰管理器
trustManager.init(tks);
//初始化SSLContext
sslContext.init(null, trustManager.getTrustManagers(), null);
} catch (Exception e) {
e.printStackTrace();
Log.e("SslContextFactory", e.getMessage());
}
return sslContext.getSocketFactory();
}
3.配置retrofit
String baseUrl = "https://skyish-test.yunext.com";
int[] certificates = {R.raw.traint};
String[] hostUrls = {baseUrl};
OkHttpClient client = new okhttp3.OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.sslSocketFactory(HTTPSUtils.getSSLSocketFactory(context))
//.hostnameVerifier(HTTPSUtils.getHostNameVerifier(hostUrls))
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.build();
Retrofit retrofit = new Retrofit.Builder().baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(client)
.build();
配置好retrofit之后就可以使用了。
坑1:SSLContext is not initialized
03-08 15:17:26.804 21672-21672/com.qiwo.enumlistdemo E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.qiwo.enumlistdemo, PID: 21672
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.qiwo.enumlistdemo/com.qiwo.enumlistdemo.RetrofitHttpsDemoActivity}: java.lang.IllegalStateException: SSLContext is not initialized.
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2650)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2720)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1567)
at android.os.Handler.dispatchMessage(Handler.java:111)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:5917)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:679)
Caused by: java.lang.IllegalStateException: SSLContext is not initialized.
at com.android.org.conscrypt.OpenSSLContextImpl.engineGetSocketFactory(OpenSSLContextImpl.java:107)
at javax.net.ssl.SSLContext.getSocketFactory(SSLContext.java:358)
at com.qiwo.api.HTTPSUtils.getSSLSocketFactory(HTTPSUtils.java:158)
at com.qiwo.api.DemoHttpsApi.<init>(DemoHttpsApi.java:40)
at com.qiwo.enumlistdemo.RetrofitHttpsDemoActivity.initViewAndListener(RetrofitHttpsDemoActivity.java:37)
at com.doudou.common.base.BaseSwipeBackAppcompatActivity.onCreate(BaseSwipeBackAppcompatActivity.java:68)
at android.app.Activity.performCreate(Activity.java:6307)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1113)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2603)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2720)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1567)
at android.os.Handler.dispatchMessage(Handler.java:111)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:5917)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:679)
原因:
- 證書和證書密碼不匹配正歼。
- 使用了錯誤的證書辐马。證書類型不對。應(yīng)該使用bks類型證書加載的確實cer類型的
解決方法:
CLIENT_TRUST_PASSWORD是證書的密碼局义,必須與生成證書步驟里的設(shè)置的證書密碼一致喜爷。如下:
public static SSLSocketFactory getSSLSocketFactory(Context context) {
final String CLIENT_TRUST_PASSWORD = "123456";//信任證書密碼,該證書默認密碼是changeit
final String CLIENT_AGREEMENT = "TLS";//使用協(xié)議
final String CLIENT_TRUST_KEYSTORE = "BKS";
SSLContext sslContext = null;
// ...
}
如果是cer類型證書萄唇,需要使用生成bks方法重新生成bsk類型證書檩帐。
坑2:java.io.IOException: Hostname 'xx.com' was not verified
原因:
服務(wù)器主機名認證失敗
解決方法:
1. 如果okhttpclient中有hostnameverify的配置,加上一個自定義的HostNameVerify,如下
((HttpsURLConnection) urlConnection).setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
2. 如果不需要HostNameVerify直接不設(shè)置就可以另萤。
//.hostnameVerifier(HTTPSUtils.getHostNameVerifier(hostUrls)) 注釋掉這句代碼
坑3:javax.net.ssl.SSLPeerUnverifiedException
原因:
SSL鏈接時主機名驗證失敗
解決方法:
//.hostnameVerifier(HTTPSUtils.getHostNameVerifier(hostUrls)) 注釋掉這句代碼
坑4:javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for cert
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for cert
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:333)
at okhttp3.internal.io.RealConnection.connectTls(RealConnection.java:239)
at okhttp3.internal.io.RealConnection.establishProtocol(RealConnection.java:196)
at okhttp3.internal.io.RealConnection.buildConnection(RealConnection.java:171)
at okhttp3.internal.io.RealConnection.connect(RealConnection.java:111)
at okhttp3.internal.http.StreamAllocation.findConnection(StreamAllocation.java:187)
at okhttp3.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:123)
at okhttp3.internal.http.StreamAllocation.newStream(StreamAllocation.java:93)
at okhttp3.internal.http.HttpEngine.connect(HttpEngine.java:296)
at okhttp3.internal.http.HttpEngine.sendRequest(HttpEngine.java:248)
at okhttp3.RealCall.getResponse(RealCall.java:243)
at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:201)
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:212)
at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:190)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:163)
at okhttp3.RealCall.execute(RealCall.java:57)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.
at rx.internal.operators.OperatorSubscribeOn$1$1$1.request(OperatorSubscribeOn.java:80)
at rx.Subscriber.setProducer(Subscriber.java:211)
at rx.internal.operators.OperatorSubscribeOn$1$1.setProducer(OperatorSubscribeOn.java:76)
at rx.Subscriber.setProducer(Subscriber.java:205)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.ja
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.ja
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:8666)
at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker$1.call(CachedThreadScheduler.java:220
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecut
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust
at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:324)
at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:225)
at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:115)
at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:571)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:329)
... 35 more
Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
原因:
使用了錯誤的證書湃密。證書驗證失敗。
解決
重新生成證書
后記
之前我是看的Tamic的做法四敞,不能走通泛源,不推薦使用它的那種做法。如果是使用它的那種做法忿危,出現(xiàn)錯誤达箍,請按照本文的做法,使用HTTPS癌蚁。