We are using Retrofit in our Android app, to communicate with an OAuth2 secured server. Everything works great, we use the RequestInterceptor to include the access token with each call. However there will be times, when the access token will expire, and the token needs to be refreshed. When the token expires, the next call will return with an Unauthorized HTTP code, so that's easy to monitor. We could modify each Retrofit call the following way: In the failure callback, check for the error code, if it equals Unauthorized, refresh the OAuth token, then repeat the Retrofit call. However, for this, all calls should be modified, which is not an easily maintainable, and good solution. Is there a way to do this without modifying all Retrofit calls?
当前回答
正如Brais Gabin在评论中所说,我遇到了TokenAuthenticator依赖于服务类的问题。服务类依赖于OkHttpClient实例,要创建OkHttpClient,我需要TokenAuthenticator。
那么我是如何打破这个循环的呢?
我创建了一个新的okHttpClient对象,一个新的Retrofit对象,并使用该对象调用refreshToken来获得新的令牌(检查getUpdatedToken()函数)
class TokenAuthenticator : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
return runBlocking {
// 1. Refresh your access_token using a synchronous api request
val response = getUpdatedToken(refreshToken)
//2. In my case here I store the new token and refreshToken into SharedPreferences
response.request.newBuilder()
.header("Authorization", "Bearer ${tokenResponse.data?.accessToken}")
.build()
// 3. If there's any kind of error I return null
}
}
private suspend fun getUpdatedToken( refreshToken: String): TokenResponse {
val okHttpClient = OkHttpClient().newBuilder()
.addInterceptor(errorResponseInterceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
val service = retrofit.create(RefreshTokenApi::class.java)
return service.refreshToken(refreshToken)
}
}
RefreshTokenApi
interface RefreshTokenApi {
@FormUrlEncoded
@POST("refreshToken")
suspend fun refreshToken(
@Field("refresh_token") refreshToeken: String
): TokenResponse
}
在这个项目中,我使用Koin,我这样配置:
object RetrofigConfig {
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
fun provideOkHttpClient(
tokenAuthenticator: TokenAuthenticator
): OkHttpClient {
return OkHttpClient().newBuilder()
.authenticator(tokenAuthenticator)
.build()
}
fun provideServiceApi(retrofit: Retrofit): ServiceApi {
return retrofit.create(ServiceApi::class.java)
}
}
重要的一行是OkHttpClient().newBuilder().authenticator(tokenAuthenticator)
因为这是我第一次实现这个,我不知道这是否是最好的方式,但这是它在我的项目中工作的方式。
其他回答
这是我的代码为我工作。可能对某人有帮助
class AuthenticationInterceptorRefreshToken @Inject
constructor( var hIltModules: HIltModules,) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val response = chain.proceed(originalRequest)
if (response.code == 401) {
synchronized(this) {
val originalRequest = chain.request()
val authenticationRequest = originalRequest.newBuilder()
.addHeader("refreshtoken", " $refreshToken")
.build()
val initialResponse = chain.proceed(authenticationRequest)
when (initialResponse.code) {
401 -> {
val responseNewTokenLoginModel = runBlocking {
hIltModules.provideAPIService().refreshToken()
}
when (responseNewTokenLoginModel.statusCode) {
200 -> {
refreshToken = responseNewTokenLoginModel.refreshToken
access_token = responseNewTokenLoginModel.accessToken
val newAuthenticationRequest = originalRequest.newBuilder()
.header("refreshtoken",
" $refreshToken")
.build()
return chain.proceed(newAuthenticationRequest)
}
else -> {
return null!!
}
}
}
else -> return initialResponse
}
}
}; return response
}
给任何人谁想解决并发/并行调用时刷新令牌。这里有一个变通办法
class TokenAuthenticator: Authenticator {
override fun authenticate(route: Route?, response: Response?): Request? {
response?.let {
if (response.code() == 401) {
while (true) {
if (!isRefreshing) {
val requestToken = response.request().header(AuthorisationInterceptor.AUTHORISATION)
val currentToken = OkHttpUtil.headerBuilder(UserService.instance.token)
currentToken?.let {
if (requestToken != currentToken) {
return generateRequest(response, currentToken)
}
}
val token = refreshToken()
token?.let {
return generateRequest(response, token)
}
}
}
}
}
return null
}
private fun generateRequest(response: Response, token: String): Request? {
return response.request().newBuilder()
.header(AuthorisationInterceptor.USER_AGENT, OkHttpUtil.UA)
.header(AuthorisationInterceptor.AUTHORISATION, token)
.build()
}
private fun refreshToken(): String? {
synchronized(TokenAuthenticator::class.java) {
UserService.instance.token?.let {
isRefreshing = true
val call = ApiHelper.refreshToken()
val token = call.execute().body()
UserService.instance.setToken(token, false)
isRefreshing = false
return OkHttpUtil.headerBuilder(token)
}
}
return null
}
companion object {
var isRefreshing = false
}
}
您可以尝试为所有的加载器创建一个基类,在这个基类中您可以捕获特定的异常,然后根据需要进行操作。 让所有不同的加载器从基类扩展,以传播行为。
请不要使用拦截器来处理身份验证。
目前,处理身份验证的最佳方法是使用新的Authenticator API,它是专门为此目的设计的。
当响应为401未授权重试最后一次失败的请求时,OkHttp将自动向验证者请求凭据。
public class TokenAuthenticator implements Authenticator {
@Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
@Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
将验证器附加到OkHttpClient,方法与拦截器相同
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);
在创建您的Retrofit RestAdapter时使用此客户端
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(ENDPOINT)
.setClient(new OkClient(okHttpClient))
.build();
return restAdapter.create(API.class);
如果你正在使用Retrofit >= 1.9.0,那么你可以使用OkHttp的新拦截器,它是在OkHttp 2.2.0中引入的。您可能希望使用Application Interceptor,它允许您重试并进行多个调用。
你的拦截器可以看起来像这样的伪代码:
public class CustomInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = chain.proceed(request);
if (response shows expired token) {
// close previous response
response.close()
// get a new token (I use a synchronous Retrofit call)
// create a new request and modify it accordingly using the new token
Request newRequest = request.newBuilder()...build();
// retry the request
return chain.proceed(newRequest);
}
// otherwise just pass the original response on
return response;
}
}
定义拦截器之后,创建OkHttpClient并将拦截器添加为应用程序拦截器。
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.interceptors().add(new CustomInterceptor());
最后,在创建RestAdapter时使用这个OkHttpClient。
RestService restService = new RestAdapter().Builder
...
.setClient(new OkClient(okHttpClient))
.create(RestService.class);
警告:正如杰西威尔逊(来自Square)在这里提到的,这是一个危险的电量。
话虽如此,我绝对认为这是现在处理这种事情的最好方法。如果你有任何问题,请不要犹豫在评论中提问。
推荐文章
- 警告:API ' variable . getjavacompile()'已过时,已被' variable . getjavacompileprovider()'取代
- 安装APK时出现错误
- 碎片中的onCreateOptionsMenu
- TextView粗体通过XML文件?
- 如何使线性布局的孩子之间的空间?
- DSL元素android.dataBinding。enabled'已过时,已被'android.buildFeatures.dataBinding'取代
- ConstraintLayout:以编程方式更改约束
- PANIC: AVD系统路径损坏。检查ANDROID_SDK_ROOT值
- 如何生成字符串类型的buildConfigField
- Recyclerview不调用onCreateViewHolder
- Android API 21工具栏填充
- Android L中不支持操作栏导航模式
- 如何在TextView中添加一个子弹符号?
- PreferenceManager getDefaultSharedPreferences在Android Q中已弃用
- 在Android Studio中创建aar文件