- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我正在使用 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/
我想在一些计算机之间建立点对点连接,这样用户就可以在没有外部服务器的情况下聊天和交换文件。我的第一个想法如下: 我在服务器上创建了一个中央 ServerSocket,所有应用程序都可以连接到该服务器。
我正在 Unity 中构建多人游戏。为此,我必须将一些值从客户端发送到两个或多个通过服务器连接的客户端。我想将其构建为服务器真实游戏。客户端将使用 Android,他们的数据将通过服务器同步(可能是一
练习 C 网络编程:我正在编写一个简单的 TCP 客户端-服务器应用程序,它应该将消息(在每个客户端的单独线程中)作为字符串从服务器发送到客户端并在客户端(稍后将成为控制台商店应用程序)。我首先发送消
我使用证书身份验证设置了 AWS Client VPN。我正在为客户端-客户端访问系统进行设置,基本上如 this AWS scenario/example 中所述.一切正常,如果我知道他们的 IP
我正在开发一个小型客户端1/客户端2、服务器(线程)TCP 游戏。在尝试处理延迟问题时,我意识到我的 transmitState() 中存在缺陷。它强制将不必要的信息传递到通讯流中,从而造成迟缓,将汽
来自文档:Configurable token lifetimes in Azure Active Directory (Public Preview) 它提到“ secret 客户端”,刷新 tok
Apollo 客户端开发工具无法连接到我的应用程序。我已在 ApolloClient 构造函数中将 connectToDevTools 传递为 true,但没有任何 react 。我也试过this p
我想在 Pod 内使用 Fabric8 kubernetes 客户端 (java)。如何获取部署集群的 kubernetes 客户端? 我可以使用该集群的 kubeconfig 文件获取任何集群的配置
我正在阅读 the security issue with Log4j我了解此产品受此漏洞影响。但是 Oracle 客户端 11.2 和 12 是否受此问题影响? 我找不到这些产品是否使用任何 Log
Eureka 服务器设置 pom.xml 1.8 Hoxton.SR1 org.springframework.cloud spring
我有一个点对点(客户端/服务器)设置(通过本地 LAN),它使用 Netty,一个 Java 网络框架。我使用原始 TCP/IP(例如,没有 HTTP)进行通信和传输。现在,根据要求,我们希望转向 T
上一篇已经实现了ModbusTcp服务器和8个主要的功能码,只是还没有实现错误处理功能。 但是在测试客户端时却发现了上一篇的一个错误,那就是写数据成功,服务器不需要响应。 接下来要做的就是实现Modb
有没有办法将二维十六进制代码数组转换为 png 图像? 数组看起来像这样(只是更大) [ [ '#FF0000', '#00FF00' ], [ '#0000FF'
我是套接字编程的新手。每次我运行客户端程序时,它都会说“无法连接到服务器”。谁能告诉我我在哪里犯了错误。任何帮助将不胜感激。 这是client.c #include #include #inclu
我们在UNIX环境下制作了简单的client.c和server.c程序。我们使用它来传输一个简单的文本文件,首先打开它,然后读取它并使用 open、read 和 send 系统调用发送;在客户端,我接
当我的程序来自 my previous question正在响应客户端,它应该发送加密消息。 当客户端连接时,它会发送一条类似“YourMessage”的消息。现在我想做的是,当客户端连接时,应该以某
我正在使用 C 和 putty 编写客户端/服务器程序。两个 c 文件位于同一系统上。 我目前在向客户端写回其正在使用的框架以及打印我的框架时遇到问题。它打印出 3 0 9 8,但随后开始打印 134
我正在使用 C 中的 select() 制作一个模拟快餐或其他任何东西的客户端服务器。 我有客户随机点 1-5 种“食物”。服务器每 30 秒决定一次。所有客户最喜欢的食物是什么?他为那些客户提供服务
对于单机游戏,基本的游戏循环是(来源:维基百科) while( user doesn't exit ) check for user input run AI move enemies
1、CentOS安装TortoiseSVN 复制代码 代码如下: yum install -y subversion 2、SVN客户端命令
我是一名优秀的程序员,十分优秀!