gpt4 book ai didi

android - 为 WebTokens 身份验证改造自定义客户端

转载 作者:塔克拉玛干 更新时间:2023-11-02 19:53:50 26 4
gpt4 key购买 nike

我正在使用 Retrofit 来处理与服务器 API 的通信,API 用户使用 JSON Web Tokens 进行身份验证。 token 有时会过期,我正在寻找实现可在 token 过期时自动刷新 token 的 Retrofit Client 的最佳方法。

这是我想出的初步实现,:

/**
* Client implementation that refreshes JSON WebToken automatically if
* the response contains a 401 header, has there may be simultaneous calls to execute method
* the refreshToken is synchronized to avoid multiple login calls.
*/
public class RefreshTokenClient extends OkClient {


private static final int UNAUTHENTICATED = 401;


/**
* Application context
*/
private Application mContext;



public RefreshTokenClient(OkHttpClient client, Application application) {
super(client);
mContext = application;
}


@Override
public Response execute(Request request) throws IOException {

Timber.d("Execute request: " + request.getMethod() + " - " + request.getUrl());

//Make the request and check for 401 header
Response response = super.execute( request );

Timber.d("Headers: "+ request.getHeaders());

//If we received a 401 header, and we have a token, it's most likely that
//the token we have has expired
if(response.getStatus() == UNAUTHENTICATED && hasToken()) {

Timber.d("Received 401 from server awaiting");

//Clear the token
clearToken();

//Gets a new token
refreshToken(request);

//Update token in the request
Timber.d("Make the call again with the new token");

//Makes the call again
return super.execute(rebuildRequest(request));

}

return response;
}


/**
* Rebuilds the request to be executed, overrides the headers with the new token
* @param request
* @return new request to be made
*/
private Request rebuildRequest(Request request){

List<Header> newHeaders = new ArrayList<>();
for( Header h : request.getHeaders() ){
if(!h.getName().equals(Constants.Headers.USER_TOKEN)){
newHeaders.add(h);
}
}
newHeaders.add(new Header(Constants.Headers.USER_TOKEN,getToken()));
newHeaders = Collections.unmodifiableList(newHeaders);

Request r = new Request(
request.getMethod(),
request.getUrl(),
newHeaders,
request.getBody()
);

Timber.d("Request url: "+r.getUrl());
Timber.d("Request new headers: "+r.getHeaders());

return r;
}

/**
* Do we have a token
*/
private boolean hasToken(){
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
return prefs.contains(Constants.TOKEN);
}

/**
* Clear token
*/
private void clearToken(){
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
prefs.edit().remove(Constants.TOKEN).commit();
}

/**
* Saves token is prefs
*/
private void saveToken(String token){
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
prefs.edit().putString(Constants.TOKEN, token).commit();
Timber.d("Saved new token: " + token);
}

/**
* Gets token
*/
private String getToken(){
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
return prefs.getString(Constants.TOKEN,"");
}




/**
* Refreshes the token by making login again,
* //TODO implement refresh token endpoint, instead of making another login call
*/
private synchronized void refreshToken(Request oldRequest) throws IOException{

//We already have a token, it means a refresh call has already been made, get out
if(hasToken()) return;

Timber.d("We are going to refresh token");

//Get credentials
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
String email = prefs.getString(Constants.EMAIL, "");
String password = prefs.getString(Constants.PASSWORD, "");

//Login again
com.app.bubbles.model.pojos.Response<Login> res = ((App) mContext).getApi().login(
new com.app.bubbles.model.pojos.Request<>(credentials)
);

//Save token in prefs
saveToken(res.data.getTokenContainer().getToken());

Timber.d("Token refreshed");
}


}

我不太了解 Retrofit/OkHttpClient 的架构,但据我了解可以从多个线程多次调用 execute 方法,OkClient 之间共享相同调用 只完成一个浅拷贝。我在 refreshToken() 方法中使用 synchronized 来避免多个线程进入 refreshToken() 并进行多次登录调用,我刷新是只需要一个线程进行 refreshCall,其他线程将使用更新的 token 。

我还没有认真测试过它,但据我所知它运行良好。也许有人已经遇到过这个问题并可以分享他的解决方案,或者对遇到相同/类似问题的人有帮助。

谢谢。

最佳答案

对于任何发现此问题的人,您应该使用 OkHttp 拦截器或使用 Authenticator API

这是来自 Retrofit GitHub 页面的示例

public void setup() {
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new TokenInterceptor(tokenManager));

Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.baseUrl("http://localhost")
.build();
}

private static class TokenInterceptor implements Interceptor {
private final TokenManager mTokenManager;

private TokenInterceptor(TokenManager tokenManager) {
mTokenManager = tokenManager;
}

@Override
public Response intercept(Chain chain) throws IOException {
Request initialRequest = chain.request();
Request modifiedRequest = request;
if (mTokenManager.hasToken()) {
modifiedRequest = request.newBuilder()
.addHeader("USER_TOKEN", mTokenManager.getToken())
.build();
}

Response response = chain.proceed(modifiedRequest);
boolean unauthorized = response.code() == 401;
if (unauthorized) {
mTokenManager.clearToken();
String newToken = mTokenManager.refreshToken();
modifiedRequest = request.newBuilder()
.addHeader("USER_TOKEN", mTokenManager.getToken())
.build();
return chain.proceed(modifiedRequest);
}
return response;
}
}

interface TokenManager {
String getToken();
boolean hasToken();
void clearToken();
String refreshToken();
}

如果您想在身份验证完成之前阻止请求,您可以使用我在回答中使用的相同同步机制,因为拦截器可以在多个线程上并发运行

关于android - 为 WebTokens 身份验证改造自定义客户端,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32926190/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com