如果刷新(JWT)令牌未经授权(401响应),AngularJS重定向到登录

Posted

技术标签:

【中文标题】如果刷新(JWT)令牌未经授权(401响应),AngularJS重定向到登录【英文标题】:AngularJS redirect to login if Refresh (JWT) token gets unauthorized (401 response) 【发布时间】:2019-07-12 05:35:52 【问题描述】:

如果刷新 (jwt) 令牌未经授权(在第一个令牌过期后),我将面临将用户重定向到登录页面的问题。代币未授权有两种情况;

第一个:当 jwt 令牌基于 401 响应过期时,将调用新的刷新服务以通过 $http-interceptors (config) 生成新令牌。

第 2 次:当刷新令牌也获得未经授权的 (401) 响应时,此时用户应重定向到登录页面。

我能够在第一种情况下发送刷新令牌,并且它按预期工作,但如果刷新令牌也得到未经授权的 (401) 响应,我无法将用户重定向到登录页面。

这是我的代码;

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);
  

并在 Config (app.js) 中

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);
      
    ;
  ]);

serviceconfig 中,我都尝试实现基于双 401 重定向用户(意味着刷新令牌也会过期并返回 401)。

我也尝试了多个后代 401 条件,但效果不佳。 (下例)

responseError: function (response) 
  // Detect if reponse error is 401 (Unauthorized)
  if (response.status === 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 (response.status === 401) 
        console.log('refresh token also expired');
        $location.path('/login');
       else 
        // 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');
        );
      
    , function(response) 
      // If any error occurs reject the Promise
      cachedRequest = null;
      deferred.reject();
      return;
    );

    return deferred.promise;
  

根据上面的代码,请指导我做错了什么,或者登录/实现可能有问题。无论哪种情况,请帮助我。谢谢

【问题讨论】:

大家可以在这里添加任何可以帮助我解决问题的内容!那真的很有帮助 【参考方案1】:

我设法通过以下代码行简单地解决了这个问题;

配置 (app.js)

// Cache this request
var deferred = $q.defer();
if(!cachedRequest) 
    // Cache request for renewing Access Token and wait for Promise
    cachedRequest = AuthorizationTokenService.refreshAccessToken();
 else 
    // this is where it checks for request token expiry
    do_logout();

【讨论】:

以上是关于如果刷新(JWT)令牌未经授权(401响应),AngularJS重定向到登录的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET JSON Web 令牌“401 未经授权”

护照-jwt 总是返回“未经授权” - 401

未经授权使用 jwt 令牌

转到Spring Boot 1.5.1和OAuth2 + JWT令牌 - 错误401未经授权

未经授权使用jwt令牌

Angular JWT Auth - 总是 401 未经授权