在刷新身份验证令牌时处理多个未经授权的请求
Posted
技术标签:
【中文标题】在刷新身份验证令牌时处理多个未经授权的请求【英文标题】:Handling multiple unauthorized request while refreshing the auth token 【发布时间】:2016-02-23 20:58:27 【问题描述】:使用 JWT 进行 API 身份验证的 Angular 应用程序会在对 API 的调用返回 401 "Unauthorized"
时启动登录对话框,让用户输入他的凭据并获取新的有效 JWT。然后应用重试失败的未授权请求并保持流程。
此处列出的代码基于 Chris Clarke 的 this solution。
.config(['$httpProvider', function($httpProvider)
$httpProvider.interceptors.push(['$q', '$location', '$injector', function ($q, $location, $injector)
return
'responseError': function(response)
// Keep the response waiting until we get new JWT
var deferred = $q.defer();
if (response.status === 401 && response.data.error && response.data.error.message.toLowerCase() === "unauthorized")
// JWT has expired
// Open login dialog
var cslAuth = $injector.get('cslAuth');
if (cslAuth.isLoggedIn())
// Logout user, next pending request will not trigger auth dialog
cslAuth.logout();
$injector.get('ngDialog').openConfirm(
template: 'web_app/views/login.html',
className: 'ngdialog-theme-default',
showClose: false,
controller: 'LoginCtrl',
cache: false
)
.then(
function(value)
// JWT has been refreshed. Try pending request again
var config = response.config;
// Inject the new token in the Auth header
config.headers.Authentication = cslAuth.getTokenHeader();
$injector.get("$http")(config).then(
function(response)
deferred.resolve(response);
,
function(response)
deferred.reject();
);
,
function(value)
deferred.reject();
);
else
return $q.reject(response);
// Return a promise while waiting for auth refresh
return deferred.promise;
])
])
问题是当有多个请求使用过期令牌时。第一个返回应该触发登录对话框并获取新令牌。但是如何让其他待处理的请求等到新令牌可用? 可以设置一个标志来告诉所有随后的传入响应正在请求一个新令牌。可以返回一个承诺,并且所有配置对象都可以存储在服务的数组中。当新令牌可用时,可以重试所有等待的请求。但是在新令牌可用后返回的未经授权的请求会发生什么?他们将触发一个新的登录对话框。
一些注意事项:
This answer 给出了一个相关问题的解决方案,但是由于这里涉及到一个新的登录,我看不出如何使解决方案适应这种情况。
自动更新令牌不是一个选项。令牌的有效期为 8 小时(一个工作会话),并且必须重新登录。
在配置对象中注入服务(cslAuth
和$http
)安全吗?我的代码正在运行,但我读到它们目前还不能完全准备好。
【问题讨论】:
【参考方案1】:此代码以两种方式改进了问题中发布的代码:
保留一组pendingRequests,以便在令牌刷新成功后重试 在 401 上,它会检查所使用的身份验证令牌是否与当前身份不同,以便在不要求登录的情况下再次重试请求。这样就解决了请求未包含在 pendingRequests 数组中的问题。代码:
/ HTTP Interceptors
.config(['$httpProvider', function($httpProvider)
$httpProvider.interceptors.push(['$q', '$location', '$injector', function ($q, $location, $injector)
var pendingRequests = [];
function retryRequest(deferred, config, cslAuth)
config.headers.Authentication = cslAuth.getTokenHeader();
$injector.get("$http")(config).then(
function(response)
deferred.resolve(response);
,
function(response)
deferred.reject();
);
return
'responseError': function(response)
switch (response.status)
case 401: // JWT has expired
// To keep the response waiting until we get new JWT
var deferred = $q.defer();
var cslAuth = $injector.get('cslAuth');
// Check if a new token exists. Then retry the request with new token
if (response.config.headers.Authentication != cslAuth.getTokenHeader())
retryRequest(deferred, response.config, cslAuth);
// Return a promise while waiting
return deferred.promise;
// Open login dialog
if (cslAuth.isLoggedIn())
// Logout user, next pending request will not trigger auth dialog
cslAuth.logout();
$injector.get('ngDialog').openConfirm(
template: 'web_app/views/login-inner.html',
className: 'ngdialog-theme-default',
showClose: false,
controller: 'LoginCtrl'
)
.then(
function(value)
// JWT has been refreshed. Try pending requests again
for (var i = 0; i < pendingRequests.length; i++)
retryRequest(pendingRequests[i].deferred, pendingRequests[i].config, cslAuth);
,
function(value)
pendingRequests[i].deferred.reject();
);
// Return a promise while waiting for auth refresh
pendingRequests.push('deferred': deferred, 'config': response.config);
return deferred.promise;
break;
default: // What happened?
return $q.reject(response);
break;
])
])
【讨论】:
以上是关于在刷新身份验证令牌时处理多个未经授权的请求的主要内容,如果未能解决你的问题,请参考以下文章
使用 laravel Sanctum 和 api 令牌身份验证获取用户时出现 401(未经授权)[重复]