我是Android编程和Retrofit的新手,我正在制作一个示例应用程序,我必须使用访问令牌进行两个并行网络调用。 当访问令牌过期并返回401状态代码时出现问题,如果我看到401 HTTP状态代码我必须使用此访问令牌调用刷新令牌,但并行调用的问题是它会导致竞争条件以刷新刷新令牌,有没有最好的方法来避免这种情况,以及如何智能地刷新令牌而不会发生任何冲突。
答案 0 :(得分:2)
当响应 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);
答案 1 :(得分:0)
尝试为刷新令牌操作创建队列,如:
class TokenProcessor {
private List<Listener> queue = new List<Listener>();
private final Object synch = new Object();
private State state = State.None;
private String token;
private long tokenExpirationDate;
public void getNewToken(Listener listener){
synchronized(synch) {
// check token expiration date
if (isTokenValid()){
listener.onSuccess(token);
return;
}
queue.add(listener);
if (state != State.Working) {
sendRefreshTokenRequest();
}
}
}
private void sendRefreshTokenRequest(){
// get token from your API using Retrofit
// on the response call onRefreshTokenLoaded() method with the token and expiration date
}
private void onRefreshTokenLoaded(String token, long expirationDate){
synchronized(synch){
this.token = token;
this.tokenExpirationDate = expirationDate;
for(Listener listener : queue){
try {
listener.onTokenRefreshed(token);
} catch (Throwable){}
}
queue.clear();
}
}
}
这是一个示例代码,它是如何实现的。
答案 2 :(得分:0)
为了避免竞争条件,您可以使用 ReentrantLock 同步刷新令牌代码。例如,如果请求 A 和请求 B 同时尝试刷新令牌,由于代码是同步的,因此刷新 A 会实际刷新令牌。一旦完成,请求 B 将运行 refreshToken() 并且应该有一些逻辑告诉请求 B 令牌已经刷新。一个例子可能是存储令牌刷新发生的时间戳,然后检查令牌是否在过去 10 秒内被刷新。
val lock = ReentrantLock(true)
fun refreshToken(): Boolean {
lock.lock()
if (token has been refreshed in last 10 seconds): return true
api.refresh()
lock.unlock()
}
如果您不想使用最后 10 秒的逻辑,这里有另一种方法。每当您刷新令牌时,后端都会返回 {accessToken, expire-timestamp}。现在,请求 A 将这个令牌和过期时间保存在磁盘中。请求 B 只需要使用时间戳检查以确保令牌没有过期。如果请求 B 得到 401 并且令牌没有过期,则表示请求 A 刷新了令牌。示例代码:
val lock = ReentrantLock(true)
fun refreshToken(): Boolean {
lock.lock()
if (token has not expired): return true
api.refresh()
lock.unlock()
}
否则,您可能需要为上面提到的刷新令牌操作创建一个队列。