Utiliser TokenAuthenticator
une réponse comme @theblang est une manière correcte de gérer refresh_token
.
Voici mon outil (j'utilise Kotlin, Dagger, RX mais vous pouvez utiliser cette idée pour implémenter votre cas)
TokenAuthenticator
class TokenAuthenticator @Inject constructor(private val noneAuthAPI: PotoNoneAuthApi, private val accessTokenWrapper: AccessTokenWrapper) : Authenticator {
override fun authenticate(route: Route, response: Response): Request? {
val newAccessToken = noneAuthAPI.refreshToken(accessTokenWrapper.getAccessToken()!!.refreshToken).blockingGet()
accessTokenWrapper.saveAccessToken(newAccessToken) // save new access_token for next called
return response.request().newBuilder()
.header("Authorization", newAccessToken.token) // just only need to override "Authorization" header, don't need to override all header since this new request is create base on old request
.build()
}
}
Pour éviter le cycle de dépendance comme le commentaire @Brais Gabin, je crée 2 interfaces comme
interface PotoNoneAuthApi { // NONE authentication API
@POST("/login")
fun login(@Body request: LoginRequest): Single<AccessToken>
@POST("refresh_token")
@FormUrlEncoded
fun refreshToken(@Field("refresh_token") refreshToken: String): Single<AccessToken>
}
et
interface PotoAuthApi { // Authentication API
@GET("api/images")
fun getImage(): Single<GetImageResponse>
}
AccessTokenWrapper
classe
class AccessTokenWrapper constructor(private val sharedPrefApi: SharedPrefApi) {
private var accessToken: AccessToken? = null
// get accessToken from cache or from SharePreference
fun getAccessToken(): AccessToken? {
if (accessToken == null) {
accessToken = sharedPrefApi.getObject(SharedPrefApi.ACCESS_TOKEN, AccessToken::class.java)
}
return accessToken
}
// save accessToken to SharePreference
fun saveAccessToken(accessToken: AccessToken) {
this.accessToken = accessToken
sharedPrefApi.putObject(SharedPrefApi.ACCESS_TOKEN, accessToken)
}
}
AccessToken
classe
data class AccessToken(
@Expose
var token: String,
@Expose
var refreshToken: String)
Mon intercepteur
class AuthInterceptor @Inject constructor(private val accessTokenWrapper: AccessTokenWrapper): Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val authorisedRequestBuilder = originalRequest.newBuilder()
.addHeader("Authorization", accessTokenWrapper.getAccessToken()!!.token)
.header("Accept", "application/json")
return chain.proceed(authorisedRequestBuilder.build())
}
}
Enfin, ajoutez Interceptor
et Authenticator
à votre OKHttpClient
lors de la création du service PotoAuthApi
Démo
https://github.com/PhanVanLinh/AndroidMVPKotlin
Remarque
Flux d'authentificateur
- Exemple de
getImage()
code d'erreur de retour d' API 401
authenticate
la méthode à l'intérieur TokenAuthenticator
sera tirée
- Synchroniser
noneAuthAPI.refreshToken(...)
appelé
- Après la
noneAuthAPI.refreshToken(...)
réponse -> un nouveau jeton s'ajoutera à l'en-tête
getImage()
sera AUTO appelé avec un nouvel en-tête ( HttpLogging
ne journalisera pas cet appel) (à l' intercept
intérieur AuthInterceptor
ne sera pas appelé )
Si getImage()
toujours échoué avec l'erreur 401, la authenticate
méthode à l'intérieur TokenAuthenticator
sera déclenchée à NOUVEAU et à NOUVEAU, puis elle lancera une erreur sur la méthode d'appel plusieurs fois ( java.net.ProtocolException: Too many follow-up requests
). Vous pouvez l'empêcher en comptant la réponse . Par exemple, si vous return null
en authenticate
après 3 fois nouvelle tentative, getImage()
va finir etreturn response 401
Si getImage()
réponse réussie => nous obtiendrons le résultat normalement (comme vous appelez getImage()
sans erreur)
J'espère que ça aide