AngularJS:在 401 响应时刷新过期的 JWT 令牌
AngularJS: Refresh expired JWT token on 401 response
我在根据 401(未授权)header 响应刷新过期的 JWT 令牌时遇到问题。我想要的是当用户获得 401 (header) 响应时,应该通过调用特定服务 (api) 生成新的(刷新)JWT。
我在 header 响应中发送了 XSRF-TOKEN 和 access_token (JWT),它们工作正常。我什至还可以通过手动调用 api 来获取刷新(过期)令牌。但无法使用 401 (header) 响应。
我有一个工厂负责处理这个承诺并拦截 header 请求。我的(工厂)代码如下所示。
angular.module('myApp').factory('httpRequestInterceptor', httpRequestInterceptor);
function httpRequestInterceptor($cookies, $rootScope, $q, $location, $injector) {
var replays = [];
var refreshTokenPromise;
var factory = {
request: request,
responseError: responseError
};
return factory;
//////////
function requestTodoWhenDone() {
var token = store.get('token');
return $http({
method: 'POST',
url: ApiEndpoint.url,
params: {
grant_type: 'refresh',
id_token: $cookies.get('access_token')
}
})
.success(function(response) {
// Set the refreshed token.
$cookies.put('access_token', response.data.access_token);
})
.then(function(){
// Attempt to retry the request if request config is passed.
if( !angular.isUndefined(requestTodoWhenDone) && requestTodoWhenDone.length > 0 ) {
// Set the new token for the authorization header.
requestTodoWhenDone.headers = {
'Authorization': 'Bearer ' + $cookies.get('access_token')
};
// Run the request again.
return $http(requestTodoWhenDone);
}
});
}
//////////
// Add authorization token to headers
function request(config) {
config.headers = config.headers || {};
if ($cookies.get('access_token')) {
config.headers.Authorization = 'Bearer ' + $cookies.get('access_token');
}
return config;
}
// Intercept 401s and redirect you to login
function responseError(response, requestTodoWhenDone) {
if (response.status === 401 && $cookies.get('access_token')) {
return checkAuthorization(response);
}
return $q.reject(response);
/////////
function checkAuthorization(res) {
return $q(function(resolve, reject) {
var replay = {
success: function(){
$injector.get('$http')(res.config).then(resolve, reject);
},
cancel: function(){
reject(res);
}
};
replays.push(replay);
console.log(replays);
if (!refreshTokenPromise) {
refreshTokenPromise = $injector.get('requestTodoWhenDone') // REFRESH TOKEN HERE
.refreshToken()
.then(clearRefreshTokenPromise)
.then(replayRequests)
.catch(cancelRequestsAndRedirect);
}
});
////////////
function clearRefreshTokenPromise(auth) {
refreshTokenPromise = null;
return auth;
}
function replayRequests(auth) {
replays.forEach(function(replay) {
replay.success();
});
replays.length = 0;
return auth;
}
function cancelRequestsAndRedirect() {
refreshTokenPromise = null;
replays.forEach(function(replay) {
replay.cancel();
});
replays.length = 0;
$cookies.remove('token');
var $state = $injector.get('$state');
// SET YOUR LOGIN PAGE
$location.path('/login');
}
}
}
}
根据上面的代码,当令牌过期(401 响应)时,我在控制台中收到以下错误。
控制台错误
Error: "[$injector:unpr] Unknown provider: requestTodoWhenDoneProvider <- requestTodoWhenDone
如有任何帮助,我们将不胜感激。
谢谢
好的,我最终用不同的方式解决了这个问题。但是当我的令牌非活动时间也到期时,我仍然无法将用户重定向到登录页面(这发生在 jwt 到期后)。
这是代码。
authInterceptor.service.js
angular.module('someApp').factory('AuthorizationTokenService', AuthorizationTokenService);
AuthorizationTokenService.$inject = ['$q', '$injector', '$cookies'];
function AuthorizationTokenService($q, $injector, $cookies) {
// Local storage for token
var tokenVM = {
accessToken: null
};
// Subscribed listeners which will get notified when new Access Token is available
var subscribers = [];
// Promise for getting new Access Token from backend
var deferedRefreshAccessToken = null;
var service = {
getLocalAccessToken: getLocalAccessToken,
refreshAccessToken: refreshAccessToken,
isAccessTokenExpired: isAccessTokenExpired,
subscribe: subscribe
};
return service;
////////////////////////////////////
// Get the new Access Token from backend
function refreshAccessToken() {
// If already waiting for the Promise, return it.
if( deferedRefreshAccessToken ) {
return deferedRefreshAccessToken.promise
} else {
deferedRefreshAccessToken = $q.defer();
// Get $http service with $injector to avoid circular dependency
var http = $injector.get('$http');
http({
method: 'POST',
url: 'api_url',
params: {
grant_type: 'refresh',
id_token: $cookies.get('access_token')
}
})
.then(function mySucces(response) {
var data = response.data;
if( data ){
// Save new Access Token
$cookies.put('access_token', data.access_token);
if( $cookies.get('access_token') ) {
// Resolve Promise
deferedRefreshAccessToken.resolve(data.access_token);
// Notify all subscribers
notifySubscribersNewAccessToken(data.access_token);
deferedRefreshAccessToken = null;
}
}
}, function myError(error) {
deferedRefreshAccessToken.reject(error);
deferedRefreshAccessToken = null;
});
return deferedRefreshAccessToken.promise;
}
}
function getLocalAccessToken() {
// get accesstoken from storage - $cookies
if ( $cookies.get('access_token') ) {
var access_token = $cookies.get('access_token')
return access_token;
}
}
function isAccessTokenExpired() {
// Check if expiresAt is older then current Date
}
function saveToken(accessToken) {
// get accesstoken from storage - $cookies
var access_token = $cookies.put('access_token');
console.log('access_token ' + access_token);
return access_token;
}
// This function will call all listeners (callbacks) and notify them that new access token is available
// This is used to notify the web socket that new access token is available
function notifySubscribersNewAccessToken(accessToken) {
angular.forEach(subscribers, function(subscriber) {
subscriber(accessToken);
});
}
// Subscribe to this service. Be notifyed when access token is renewed
function subscribe(callback) {
subscribers.push(callback);
}
}
比配置 (app.js) 我遵循的代码拦截了适当的 header(s) 并在 401 响应中刷新(请求)api。
这里是配置代码
config.$inject = ['$stateProvider', '$urlRouterProvider', '$httpProvider'];
function config($stateProvider, $urlRouterProvider, $httpProvider) {
// Push httpRequestInterceptor
// $httpProvider.interceptors.push('httpRequestInterceptor');
//Intercept all http requests
$httpProvider.interceptors.push(['$injector', '$q', "AuthorizationTokenService", "$cookies", function ($injector, $q, AuthorizationTokenService, $cookies) {
var cachedRequest = null;
return {
request: function (config) {
//If request if for API attach Authorization header with Access Token
if (config.url.indexOf("api") != -1) {
// var accessToken = AuthorizationTokenService.getLocalAccessToken();
console.log('cookie ' + $cookies.get('access_token'));
config.headers.Authorization = 'Bearer ' + $cookies.get('access_token');
}
return config;
},
responseError: function (response) {
switch (response.status) {
// Detect if reponse error is 401 (Unauthorized)
case 401:
// Cache this request
var deferred = $q.defer();
if(!cachedRequest) {
// Cache request for renewing Access Token and wait for Promise
cachedRequest = AuthorizationTokenService.refreshAccessToken();
}
// When Promise is resolved, new Access Token is returend
cachedRequest.then(function(accessToken) {
cachedRequest = null;
if (accessToken) {
// Resend this request when Access Token is renewed
$injector.get("$http")(response.config).then(function(resp) {
// Resolve this request (successfully this time)
deferred.resolve(resp);
},function(resp) {
deferred.reject();
console.log('success: refresh token has expired');
});
} else {
// If any error occurs reject the Promise
console.log('error: refresh token has expired');
deferred.reject();
}
}, function(response) {
// If any error occurs reject the Promise
cachedRequest = null;
deferred.reject();
return;
});
return deferred.promise;
}
// If any error occurs reject the Promise
return $q.reject(response);
}
};
}]);
}
代码在 JWT 过期时发生的 401(响应)情况下工作正常。但它没有将我重定向到登录页面(在这种情况下,我在配置中的承诺请求中添加了控制台而不是重定向代码)
请帮忙解决这个问题,谢谢...
我在根据 401(未授权)header 响应刷新过期的 JWT 令牌时遇到问题。我想要的是当用户获得 401 (header) 响应时,应该通过调用特定服务 (api) 生成新的(刷新)JWT。
我在 header 响应中发送了 XSRF-TOKEN 和 access_token (JWT),它们工作正常。我什至还可以通过手动调用 api 来获取刷新(过期)令牌。但无法使用 401 (header) 响应。
我有一个工厂负责处理这个承诺并拦截 header 请求。我的(工厂)代码如下所示。
angular.module('myApp').factory('httpRequestInterceptor', httpRequestInterceptor);
function httpRequestInterceptor($cookies, $rootScope, $q, $location, $injector) {
var replays = [];
var refreshTokenPromise;
var factory = {
request: request,
responseError: responseError
};
return factory;
//////////
function requestTodoWhenDone() {
var token = store.get('token');
return $http({
method: 'POST',
url: ApiEndpoint.url,
params: {
grant_type: 'refresh',
id_token: $cookies.get('access_token')
}
})
.success(function(response) {
// Set the refreshed token.
$cookies.put('access_token', response.data.access_token);
})
.then(function(){
// Attempt to retry the request if request config is passed.
if( !angular.isUndefined(requestTodoWhenDone) && requestTodoWhenDone.length > 0 ) {
// Set the new token for the authorization header.
requestTodoWhenDone.headers = {
'Authorization': 'Bearer ' + $cookies.get('access_token')
};
// Run the request again.
return $http(requestTodoWhenDone);
}
});
}
//////////
// Add authorization token to headers
function request(config) {
config.headers = config.headers || {};
if ($cookies.get('access_token')) {
config.headers.Authorization = 'Bearer ' + $cookies.get('access_token');
}
return config;
}
// Intercept 401s and redirect you to login
function responseError(response, requestTodoWhenDone) {
if (response.status === 401 && $cookies.get('access_token')) {
return checkAuthorization(response);
}
return $q.reject(response);
/////////
function checkAuthorization(res) {
return $q(function(resolve, reject) {
var replay = {
success: function(){
$injector.get('$http')(res.config).then(resolve, reject);
},
cancel: function(){
reject(res);
}
};
replays.push(replay);
console.log(replays);
if (!refreshTokenPromise) {
refreshTokenPromise = $injector.get('requestTodoWhenDone') // REFRESH TOKEN HERE
.refreshToken()
.then(clearRefreshTokenPromise)
.then(replayRequests)
.catch(cancelRequestsAndRedirect);
}
});
////////////
function clearRefreshTokenPromise(auth) {
refreshTokenPromise = null;
return auth;
}
function replayRequests(auth) {
replays.forEach(function(replay) {
replay.success();
});
replays.length = 0;
return auth;
}
function cancelRequestsAndRedirect() {
refreshTokenPromise = null;
replays.forEach(function(replay) {
replay.cancel();
});
replays.length = 0;
$cookies.remove('token');
var $state = $injector.get('$state');
// SET YOUR LOGIN PAGE
$location.path('/login');
}
}
}
}
根据上面的代码,当令牌过期(401 响应)时,我在控制台中收到以下错误。
控制台错误
Error: "[$injector:unpr] Unknown provider: requestTodoWhenDoneProvider <- requestTodoWhenDone
如有任何帮助,我们将不胜感激。 谢谢
好的,我最终用不同的方式解决了这个问题。但是当我的令牌非活动时间也到期时,我仍然无法将用户重定向到登录页面(这发生在 jwt 到期后)。
这是代码。
authInterceptor.service.js
angular.module('someApp').factory('AuthorizationTokenService', AuthorizationTokenService);
AuthorizationTokenService.$inject = ['$q', '$injector', '$cookies'];
function AuthorizationTokenService($q, $injector, $cookies) {
// Local storage for token
var tokenVM = {
accessToken: null
};
// Subscribed listeners which will get notified when new Access Token is available
var subscribers = [];
// Promise for getting new Access Token from backend
var deferedRefreshAccessToken = null;
var service = {
getLocalAccessToken: getLocalAccessToken,
refreshAccessToken: refreshAccessToken,
isAccessTokenExpired: isAccessTokenExpired,
subscribe: subscribe
};
return service;
////////////////////////////////////
// Get the new Access Token from backend
function refreshAccessToken() {
// If already waiting for the Promise, return it.
if( deferedRefreshAccessToken ) {
return deferedRefreshAccessToken.promise
} else {
deferedRefreshAccessToken = $q.defer();
// Get $http service with $injector to avoid circular dependency
var http = $injector.get('$http');
http({
method: 'POST',
url: 'api_url',
params: {
grant_type: 'refresh',
id_token: $cookies.get('access_token')
}
})
.then(function mySucces(response) {
var data = response.data;
if( data ){
// Save new Access Token
$cookies.put('access_token', data.access_token);
if( $cookies.get('access_token') ) {
// Resolve Promise
deferedRefreshAccessToken.resolve(data.access_token);
// Notify all subscribers
notifySubscribersNewAccessToken(data.access_token);
deferedRefreshAccessToken = null;
}
}
}, function myError(error) {
deferedRefreshAccessToken.reject(error);
deferedRefreshAccessToken = null;
});
return deferedRefreshAccessToken.promise;
}
}
function getLocalAccessToken() {
// get accesstoken from storage - $cookies
if ( $cookies.get('access_token') ) {
var access_token = $cookies.get('access_token')
return access_token;
}
}
function isAccessTokenExpired() {
// Check if expiresAt is older then current Date
}
function saveToken(accessToken) {
// get accesstoken from storage - $cookies
var access_token = $cookies.put('access_token');
console.log('access_token ' + access_token);
return access_token;
}
// This function will call all listeners (callbacks) and notify them that new access token is available
// This is used to notify the web socket that new access token is available
function notifySubscribersNewAccessToken(accessToken) {
angular.forEach(subscribers, function(subscriber) {
subscriber(accessToken);
});
}
// Subscribe to this service. Be notifyed when access token is renewed
function subscribe(callback) {
subscribers.push(callback);
}
}
比配置 (app.js) 我遵循的代码拦截了适当的 header(s) 并在 401 响应中刷新(请求)api。
这里是配置代码
config.$inject = ['$stateProvider', '$urlRouterProvider', '$httpProvider'];
function config($stateProvider, $urlRouterProvider, $httpProvider) {
// Push httpRequestInterceptor
// $httpProvider.interceptors.push('httpRequestInterceptor');
//Intercept all http requests
$httpProvider.interceptors.push(['$injector', '$q', "AuthorizationTokenService", "$cookies", function ($injector, $q, AuthorizationTokenService, $cookies) {
var cachedRequest = null;
return {
request: function (config) {
//If request if for API attach Authorization header with Access Token
if (config.url.indexOf("api") != -1) {
// var accessToken = AuthorizationTokenService.getLocalAccessToken();
console.log('cookie ' + $cookies.get('access_token'));
config.headers.Authorization = 'Bearer ' + $cookies.get('access_token');
}
return config;
},
responseError: function (response) {
switch (response.status) {
// Detect if reponse error is 401 (Unauthorized)
case 401:
// Cache this request
var deferred = $q.defer();
if(!cachedRequest) {
// Cache request for renewing Access Token and wait for Promise
cachedRequest = AuthorizationTokenService.refreshAccessToken();
}
// When Promise is resolved, new Access Token is returend
cachedRequest.then(function(accessToken) {
cachedRequest = null;
if (accessToken) {
// Resend this request when Access Token is renewed
$injector.get("$http")(response.config).then(function(resp) {
// Resolve this request (successfully this time)
deferred.resolve(resp);
},function(resp) {
deferred.reject();
console.log('success: refresh token has expired');
});
} else {
// If any error occurs reject the Promise
console.log('error: refresh token has expired');
deferred.reject();
}
}, function(response) {
// If any error occurs reject the Promise
cachedRequest = null;
deferred.reject();
return;
});
return deferred.promise;
}
// If any error occurs reject the Promise
return $q.reject(response);
}
};
}]);
}
代码在 JWT 过期时发生的 401(响应)情况下工作正常。但它没有将我重定向到登录页面(在这种情况下,我在配置中的承诺请求中添加了控制台而不是重定向代码)
请帮忙解决这个问题,谢谢...