RxJava2 + Retrofit2 完全指南 之 Authenticator處理與Token靜默重新整理
前言
今年是9102年了,應該沒有還在用 userId
來鑑權了吧,也應該很少人使用 cookie
來保持會話了吧?而現在更常用的是 Authorization
,
關於Authorization
簡略的講一講Authorization,如果要深入瞭解的話請看底部的參考文章連結。Authorization的認證方式在我接觸中有兩種
- Basic
- Bearer
Basic
HTTP基本認證,在請求的時候加上以下請求頭:
Authorization : basic base64encode(username+":"+password))
將使用者名稱和密碼用英文冒號(:)拼接起來,並進行一次Base64編碼。服務端拿到basic碼,然後自己查詢相關資訊再按照 base64encode(username+":"+password))
的方式得出當前使用者的basic進行對比。
Bearer
授權完成後會返回類似下面的資料結構:
{ "token_type": "Bearer", "access_token": "xxxxx", "refresh_token": "xxxxx" }
而其中的 refresh_token
的作用是在 access_token
失效的時候進行重新重新整理傳入的引數,具體怎麼傳要看各自專案的實現方式。
access_token
就是我們的認證令牌。token_type是令牌的型別,而我現在使用到的只有 bearer
,其它型別未碰到,希望各位看官能補充一下。
在使用的時候需要加上以下請求頭:
Authorization : token_type access_token
也就是這樣:
Authorization: Bearer xxxxx
實現
方式1 :authenticator
authenticator
是在建立 OkHttpClient
的時候能夠設定的一個方法,接收的是一個 okhttp3.Authenticator
的interface,預設不設定的話是一個 NONE
的空實現,而回調的地方是在 okhttp3.internal.http.RetryAndFollowUpInterceptor.followUpRequest()

Authenticator

followUpRequest
編碼
相關程式碼也比較簡單,在 okhttp3.Authenticator
的註釋上面也寫有簡單的例子,核心程式碼就以下幾行:
private Authenticator authorization = new Authenticator() { @Override public Request authenticate(Route route, Response response) throws IOException { //-----------核心程式碼------- // 這裡丟擲的錯誤會直接回調 onError // 這裡發起的請求是同步的,重新整理完成token後再增加到header中 // String token = refreshToken(); String token = Credentials.basic("userName", "password", Charset.forName("UTF-8")); return response.request() .newBuilder() .header("Authorization", token) .build(); //-----------核心程式碼------- } };
以上就是主要程式碼,其中演示的是 basic
方式的認證模式, bearer
方式的沒實現,其實也只是 refreshToken()
中發起一個 同步請求
去重新整理一下 token
並儲存,後面的步驟都是一樣的。
如何使用
建立OkHttpClient呼叫,當然,也可以直接寫匿名內部類的實現,都是可以的。
retrofit = new Retrofit.Builder() .client(new OkHttpClient.Builder() .authenticator(authorization)// 增加重試 .addInterceptor(getHttpLoggingInterceptor()) .build()) .baseUrl("https://api.github.com/") .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build();
演示
為了演示方便看到結果,我在 Authenticator
的實現類中增加了一些回撥主執行緒的方法,具體看一下原始碼即可,對於主要結果沒什麼影響。

Authenticator
總結
使用官方提供的 Authenticator
有一個很明顯的問題,那就是會佔用 重試
,像示例中,我並沒有傳入一個正確的 token
,就導致一直在回撥 Authenticator
,直到達到了最大重試次數為止。而往往需求是token失效以後選擇重試一次,成功了繼續請求,再次失敗則提示登入,所以這個方法使用得不多。
方式2 :Interceptor
上面 okhttp3.Authenticator
的實現方式其實是在 RetryAndFollowUpInterceptor
中判斷和回撥的,由此,可以自定義一個 Interceptor
,由開發者來自行判斷和跳轉。
編碼
詳細程式碼如下:
Interceptor mAuthenticatorInterceptor = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { // 獲取請求 Request request = chain.request(); // 獲取響應 Response response = chain.proceed(request); // 在這裡判斷是不是是token失效 // 當然,判斷條件不會這麼簡單,會有更多的判斷條件的 if (response.code() == 401) { // 這裡應該呼叫自己的重新整理token的介面 // 這裡發起的請求是同步的,重新整理完成token後再增加到header中 // 這裡丟擲的錯誤會直接回調 onError //String token = refreshToken(); String token = Credentials.basic("userName", "password", Charset.forName("UTF-8")); // 建立新的請求,並增加header Request retryRequest = chain.request() .newBuilder() .header("Authorization", token) .build(); // 再次發起請求 return chain.proceed(retryRequest); } return response; } }
使用
和方法一相同,在建立 HttpClient
的時候 addInterceptor(mAuthenticatorInterceptor)
,將我們自己的攔截器加入進行即可。
演示

AuthenticatorInterceptor
總結
從演示中可以看出,在第一次返回 401
的時候,進行了一次token的獲取,並且再次進行了請求,圓滿符合我們的預期,只重試一次。
最後
分析
可能會有疑問:為什麼使用 Interceptor
就能達到我們預期的效果? Interceptor
到底是如何工作的?
首先 Interceptor
新增是有先後順序的,首先新增的是我們設定的 Interceptor
,然後新增的才是 okhttp
的 Interceptor
。如原始碼中:

Add Interceptor
總的來說, okhttp
的實現方式就是通過 Interceptor
來組成一個一個的 chian
來實現的。每個 Interceptor
裡面的 intercept()
方法內部都會呼叫 Chain.proceed()
方法,將請求交給下一個 Interceptor
,由此類推,一直到最後一個 Interceptor
請求完成。
需要注意的是 proceed
是同步的,也就是呼叫 proceed
方法之後需要等等下一個 Interceptor
進行處理,當最後一個 Interceptor
請求到資料,經過自己的處理之後,再往上返回 Response
,直到第一個 Interceptor
為止,返回資料。主要關係如下圖:

Interceptor ex
這些所有的 Interceptor
裡面的 proceed
都是呼叫了一次,那麼我們增加一個 Interceptor
,等到 proceed
返回了 Response
之後,對 Response
進行判斷,如果是認證失敗,我們則重新整理一下token,重新建立Request,再呼叫一次 proceed
方法。如果再失敗了,就不會再回調到當前的 Interceptor
,如下圖:

AuthenticatorInterceptor