使用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;
}
}
}])
}])
问题是当过期令牌有多个请求时。第一个返回的应该触发登录对话框并获取新令牌。但是如何让其他待处理请求等到新令牌可用? 可以设置标志以告知所有后续传入的响应,即正在请求新令牌。可以返回promise,并且所有配置对象都可以存储在Service中的数组中。当新令牌可用时,可以重试所有等待请求。但是在新令牌可用后返回未授权请求会发生什么?它们将触发新的登录对话框。
有些注意事项:
This answer提供了相关问题的解决方案,但由于此处涉及新登录,因此无法看到如何使解决方案适应此情况。
这不是自动更新令牌的选项。令牌将有8小时到期(工作会话),并且必须进行新登录。
cslAuth
和$http
)是否安全?我的代码正在运行,但我已经知道他们现在还不能完全准备好。答案 0 :(得分:2)
此代码以两种方式改进了问题中发布的代码:
代码:
/ 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;
}
}
}
}])
}])