为WebTokenauthentication改装自定义客户端

我正在使用Retrofit来处理与服务器API,API用户JSON Web令牌进行身份validation的通信。 该令牌有时到期,我正在寻找实施改造客户端的最好方法,它可以在到期时自动刷新令牌。

这是我想到的初始实现,:

/** * 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的体系结构,但据我所知,执行方法可以从多个线程多次调用, OkClientCalls之间是相同的,只有浅拷贝完成。 我正在使用refreshToken()方法中的synchronized ,以避免多个线程进入refreshToken()并进行多个login调用,ia刷新只需要一个线程应该使refreshCall和其他人将使用更新的令牌。

我还没有认真地testing过,但是我可以看到它工作正常。 也许有人已经有这个问题,可以分享他的解决scheme,或者可以帮助有相同/类似问题的人。

谢谢。

Solutions Collecting From Web of "为WebTokenauthentication改装自定义客户端"

对于任何发现这个问题的人,你应该使用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(); } 

如果要阻止请求,直到完成身份validation,则可以使用与我的答案相同的同步机制,因为拦截器可以在多个线程上同时运行