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(响应)情况下工作正常。但它没有将我重定向到登录页面(在这种情况下,我在配置中的承诺请求中添加了控制台而不是重定向代码)

请帮忙解决这个问题,谢谢...