使用多个请求刷新访问令牌
Posted
技术标签:
【中文标题】使用多个请求刷新访问令牌【英文标题】:Refreshing access token with multiple requests 【发布时间】:2019-10-13 03:09:48 【问题描述】:我正在努力让 axios 拦截器工作。
当我的令牌过期时,我需要它来刷新访问令牌并在刷新令牌后重试原始请求。 我有这部分工作。
问题是如果我有并发 api 调用,它只会在令牌第一次无效时重试第一个请求。
这是我的拦截器代码:
export default function execute()
let isRefreshing = false
// Request
axios.interceptors.request.use(
config =>
var token = Storage.getAccessToken() //localStorage.getItem("token");
if (token)
console.log('Bearer ' + token)
config.headers['Authorization'] = 'Bearer ' + token
return config
,
error =>
return Promise.reject(error)
)
// Response
axios.interceptors.response.use(
response =>
return response
,
error =>
const originalRequest = error.config
// token expired
if (error.response.status === 401)
console.log('401 Error need to reresh')
originalRequest._retry = true
let tokenModel =
accessToken: Storage.getAccessToken(),
client: 'Web',
refreshToken: Storage.getRefreshToken()
//Storage.destroyTokens();
var refreshPath = Actions.REFRESH
if (!isRefreshing)
isRefreshing = true
return store
.dispatch(refreshPath, tokenModel )
.then(response =>
isRefreshing = false
console.log(response)
return axios(originalRequest)
)
.catch(error =>
isRefreshing = false
console.log(error)
// Logout
)
else
console.log('XXXXX')
console.log('SOME PROBLEM HERE') // <------------------
console.log('XXXXX')
else
store.commit(Mutations.SET_ERROR, error.response.data.error)
return Promise.reject(error)
)
我不确定上面突出显示的 else 块中我需要什么。
编辑:
当我这样做时
return axios(originalRequest)
在 else 块中它可以工作,但是我对这些行为不满意。它基本上一次又一次地重试所有请求,直到刷新令牌。 我宁愿在刷新令牌后重试一次 任何想法
谢谢
【问题讨论】:
如果您在第一个请求上取消设置令牌由于过期而失败,然后将所有下一个请求放入某个队列中直到令牌被刷新,该怎么办。刷新令牌时,处理队列。 【参考方案1】:我不知道您的令牌的架构是什么(解密后),但最好保留的属性之一是 exp“expiration_date”。 也就是说,有了到期日期,您就可以知道何时应该刷新令牌。
如果不了解您的架构,就很难找到正确的解决方案。但是假设你是手动做所有事情,通常onIdle/onActive是我们检查用户会话是否仍然正常的时候,所以这个时候你可以使用令牌信息来知道你是否应该刷新它的值。
了解这个过程很重要,因为只有当用户一直处于活动状态并且即将到期(例如 2 分钟前)时,才应刷新令牌。
【讨论】:
【参考方案2】:请参考我遇到相同问题的代码的角度版本,在更改了许多方法后,这是我的最终代码,它处于最佳状态。
Re Initaite the last failed request after refresh token is provided
【讨论】:
【参考方案3】:您可以只使用额外的拦截器来刷新令牌并执行您的待处理请求。
在这方面,countDownLatch
类可以提供帮助。
这是示例拦截器代码,
class AutoRefreshTokenRequestInterceptorSample() : Interceptor
companion object
var countDownLatch = CountDownLatch(0)
var previousAuthToken = ""
const val SKIP_AUTH_TOKEN = "SkipAccessTokenHeader"
const val AUTHORIZATION_HEADER = "AUTHORIZATION_HEADER_KEY"
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response?
val request = chain.request()
if (shouldExecuteRequest(request))
// Execute Request
val response = chain.proceed(request)
if (!response.isSuccessful)
// Failed Case
val errorBody = response.peekBody(java.lang.Long.MAX_VALUE).string()
val error = parseErrorModel(errorBody)
// Gives Signal to HOLD the Request Queue
countDownLatch = CountDownLatch(1)
handleError(error!!)
// After updating token values, execute same request with updated values.
val updatedRequest = getUpdatedRequest(request)
// Gives Signal to RELEASE Request Queue
countDownLatch.countDown()
//Execute updated request
return chain.proceed(updatedRequest)
else
// success case
return response
// Change updated token values in pending request objects and execute them!
// If Auth header exists, and skip header not found then hold the request
if (shouldHoldRequest(request))
try
// Make this request to WAIT till countdown latch has been set to zero.
countDownLatch.await()
catch (e: Exception)
e.printStackTrace()
// Once token is Updated, then update values in request model.
if (previousAuthToken.isNotEmpty() && previousAuthToken != "newAccessToken")
val updatedRequest = getUpdatedRequest(request)
return chain.proceed(updatedRequest)
return chain.proceed(request)
private fun handleError(error: ErrorDto)
// update your token as per your error code logic
//Here it will make new API call to update tokens and store it in your local preference.
/***
* returns Request object with updated token values.
*/
private fun getUpdatedRequest(request: Request): Request
var updateAuthReqBuilder: Request.Builder = request.newBuilder()
var url = request.url().toString()
if (url.contains(previousAuthToken.trim()) && previousAuthToken.trim().isNotEmpty())
url = url.replace(previousAuthToken, "newAccessToken")
updateAuthReqBuilder = updateAuthReqBuilder.url(url)
// change headers if needed
return updateAuthReqBuilder.build()
private fun shouldExecuteRequest(request: Request) =
shouldHoldRequest(request) && isSharedHoldSignalDisabled()
/**
* If count down latch has any value then it is reported by previous request's error signal to hold the whole pending chain.
*/
private fun isSharedHoldSignalDisabled() = countDownLatch.count == 0L
private fun shouldHoldRequest(request: Request) = !hasSkipFlag(request) && hasAuthorizationValues(request)
private fun hasAuthorizationValues(request: Request) = isHeaderExist(request, AUTHORIZATION_HEADER)
private fun hasSkipFlag(request: Request) = isHeaderExist(request, SKIP_AUTH_TOKEN)
private fun isHeaderExist(request: Request, headerName: String): Boolean
return request.header(headerName) != null
private fun parseErrorModel(errorBody: String): Error?
val parser = JsonParser()
// Change this logic according to your requirement.
val jsonObject = parser.parse(errorBody).asJsonObject
if (jsonObject.has("Error") && jsonObject.get("Error") != null)
val errorJsonObj = jsonObject.get("Error").asJsonObject
return decodeErrorModel(errorJsonObj)
return null
private fun decodeErrorModel(jsonObject: JsonObject): Error
val error = Error()
// decode your error object here
return error
【讨论】:
【参考方案4】:我就是这样做的:
let isRefreshing = false;
let failedQueue = [];
const processQueue = (error, token = null) =>
failedQueue.forEach(prom =>
if (error)
prom.reject(error);
else
prom.resolve(token);
);
failedQueue = [];
;
axios.interceptors.response.use(
response => response,
error =>
const originalRequest = error.config;
if (error.response.status === 400)
// If response is 400, logout
store.dispatch(logout());
// If 401 and I'm not processing a queue
if (error.response.status === 401 && !originalRequest._retry)
if (isRefreshing)
// If I'm refreshing the token I send request to a queue
return new Promise((resolve, reject) =>
failedQueue.push( resolve, reject );
)
.then(() =>
originalRequest.headers.Authorization = getAuth();
return axios(originalRequest);
)
.catch(err => err);
// If header of the request has changed, it means I've refreshed the token
if (originalRequest.headers.Authorization !== getAuth())
originalRequest.headers.Authorization = getAuth();
return Promise.resolve(axios(originalRequest));
originalRequest._retry = true; // mark request a retry
isRefreshing = true; // set the refreshing var to true
// If none of the above, refresh the token and process the queue
return new Promise((resolve, reject) =>
// console.log('REFRESH');
refreshAccessToken() // The method that refreshes my token
.then(( data ) =>
updateToken(data); // The method that sets my token to localstorage/Redux/whatever
processQueue(null, data.token); // Resolve queued
resolve(axios(originalRequest)); // Resolve current
)
.catch(err =>
processQueue(err, null);
reject(err);
)
.then(() =>
isRefreshing = false;
);
);
return Promise.reject(error);
,
);
【讨论】:
以上是关于使用多个请求刷新访问令牌的主要内容,如果未能解决你的问题,请参考以下文章