【发布时间】:2022-08-06 16:29:42
【问题描述】:
这是一个关于将访问令牌添加到请求标头并使用刷新令牌刷新令牌的自我回答问题,我在这个话题上苦苦挣扎了很长时间,现在我正在写这篇文章,希望它可以帮助其他人在同样的情况下 可能会有更好的解决方案,但它以最简单的方式对我有用
标签: header token refresh interceptor authenticator
这是一个关于将访问令牌添加到请求标头并使用刷新令牌刷新令牌的自我回答问题,我在这个话题上苦苦挣扎了很长时间,现在我正在写这篇文章,希望它可以帮助其他人在同样的情况下 可能会有更好的解决方案,但它以最简单的方式对我有用
标签: header token refresh interceptor authenticator
在远程模块中,我在 Hilt 的帮助下遵循此方法:
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun providesRetrofit (okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
@Provides
@Singleton
fun providesOkHttpClient(interceptor: AuthInterceptor, authAuthenticator: AuthAuthenticator): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(interceptor)
.authenticator(authAuthenticator)
.build()
}
我向服务器发送请求并接收访问令牌和刷新令牌,然后使用共享首选项的力量保存它们,如下所示:
class TokenManager @Inject constructor(@ApplicationContext context: Context) {
private var prefs: SharedPreferences =
context.getSharedPreferences(PREFS_TOKEN_FILE, Context.MODE_PRIVATE)
fun saveToken(token: UserAuthModel?) {
val editor = prefs.edit()
token?.let {
editor.putString(USER_TOKEN, token.access_token).apply()
editor.putString(USER_REFRESH_TOKEN,token.refresh_token).apply()
editor.putBoolean(IS_LOGGED_IN,true).apply ()
}
}
fun getToken(): String? {
return prefs.getString(USER_TOKEN, null)
}
fun getRefreshToken(): String? {
return prefs.getString(USER_REFRESH_TOKEN, null)
}
fun getIsLoggedIn():Boolean?{
return prefs.getBoolean(IS_LOGGED_IN,false)
}
fun clearSharedPref(){
val editor = prefs.edit()
editor.clear().apply()
}}
然后我使用.addInterceptor(interceptor) 为所有请求添加标头,如下所示:
class AuthInterceptor @Inject constructor():Interceptor{
@Inject
lateinit var tokenManager: TokenManager
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
val token = tokenManager.getToken()
request.addHeader("Authorization", "Bearer $token")
request.addHeader("Accept","application/json")
return chain.proceed(request.build())
}}
之后,您将可以访问需要访问令牌作为身份验证模式的每种方法,具体取决于您的 API 指令,您的访问令牌将在特定时间(可能 24 小时)过期,并且您需要一个新的访问令牌,该令牌可通过以下方式访问刷新你已经拥有的令牌,然后我将此行添加到 okHttp .authenticator(authAuthenticator)
当您的访问令牌过期时,API 将向您发回 401 或 403 错误代码(它将发生在拦截器部分),并且在那个时候 Authenticator 开始发挥作用,幸运的是它足够聪明地识别这一点并执行任务,
我像这样照顾Authenticator:
class AuthAuthenticator @Inject constructor() : Authenticator {
@Inject lateinit var tokenManager: TokenManager
override fun authenticate(route: Route?, response: Response): Request? {
return runBlocking {
val refreshToken=tokenManager.getRefreshToken()
val refreshTokenR:RequestBody= refreshToken?.toRequestBody() ?: "".toRequestBody()
val grantTypeR:RequestBody= "refresh_token".toRequestBody()
//val newAccessToken = authService.safeRefreshTokenFromApi(refreshToken,grantType)
val newAccessToken = getUpdatedToken(refreshTokenR,grantTypeR)
if (!newAccessToken.isSuccessful){
val intent=Intent(context,MainActivity::class.java)
context.startActivity(intent)
}
tokenManager.saveToken(newAccessToken.body()) // save new access_token for next called
newAccessToken.body()?.let {
response.request.newBuilder()
.header("Authorization", "Bearer ${it.access_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()
}
} }
private suspend fun getUpdatedToken( refreshToken:RequestBody,grantType:RequestBody):
retrofit2.Response<UserAuthModel> {
val okHttpClient = OkHttpClient.Builder()
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
val service=retrofit.create(AuthService::class.java)
return service.refreshTokenFromApi(refreshToken,grantType)
}}
Authenticator 需要发出请求,所以它需要一个改造和 OkHttp 实例(它将运行这个 Authenticator),为了打破这个循环,我创建了另一个实例。
我必须提到的两件事是:
我想使用 runBlocking 是可以的,因为 Authenticator 本身正在另一个线程上运行
请记住,如果是 kotlin,您必须在 API 服务中使用暂停功能来处理 Unable to create call adapter for retrofit2.Response 错误
最后,我不得不提到我正在使用两种不同的 API 服务,如下所示:
interface MovieService {
@GET("api/v1/movies/{movie-id}")
suspend fun getSingleMovie(@Path("movie-id") movieId:Int):Response<NetworkMovieModel>}
interface AuthService:MovieService {
@Multipart
@POST("oauth/token")
fun refreshTokenFromApi (@Part("refresh_token") username: RequestBody,
@Part("grant_type") grantType: RequestBody
): Response<UserAuthModel>}
【讨论】: