2016-04-15 80 views
7

我有一個基於令牌的認證機制的API。成功登錄後,我在瀏覽器的本地存儲中存儲兩個令牌 - 訪問和刷新令牌。 訪問令牌包含在服務器端授權用戶所需的所有必要信息,並且它具有到期日期。 訪問令牌過期時,客戶端可以使用刷新令牌請求新的訪問令牌,並在響應中獲得一對新的令牌。Angular2 http重試邏輯

在angular 1.x中,實現非常簡單直接。例如,我們可以使用攔截器:

httpInterceptor.$inject = ['$httpProvider']; 
function httpInterceptor($httpProvider) { 
    $httpProvider.interceptors.push(handleStaleAccessToken); 

    handleStaleAccessToken.$inject = ['$q', '$injector', 'session']; 
    function handleStaleAccessToken($q, $injector, session) { 

    function logoutAndRedirect() { 
     var authenticationRedirect = $injector.get('authenticationRedirect'); 
     session.destroy(); 
     authenticationRedirect.toLoginPage(); 
    } 

    return { 
     responseError: function(rejection) { 
     // Do nothing for non 403 errors 
     if (rejection.status !== 403) { 
      return $q.reject(rejection); 
     } 

     var errorCode = rejection.data.error && rejection.data.error.code; 
     if (errorCode === 'access_token_expired') { 
      var $http = $injector.get('$http'); 

      // Refresh token 
      var params = { refreshToken: session.getRefreshToken() }; 
      return $http.post('/api/auth/refresh', params).then(function(response) { 
      session.setTokens(response.data); 
      // Re try failed http request 
      return $http(rejection.config); 
      }).catch(function(error) { 
      logoutAndRedirect(); 
      return $q.reject(error); 
      }); 
     } else { 
      logoutAndRedirect(); 
     } 

     return $q.reject(rejection); 
     } 
    }; 
    } 
} 

但是如何在角度2/rxjs應用程序中實現類似的邏輯?

回答

7

這可以通過擴展Http類並利用可觀察運營商如flatMap在Angular2中透明地完成。

下面是一些示例代碼:

if (hasTokenExpired()) { 
    return this.authService 
      .refreshAuthenticationObservable() 
      .flatMap((authenticationResult:AuthenticationResult) => { 
       if (authenticationResult.IsAuthenticated == true) { 
        this.authService.setAuthorizationHeader(request.headers); 
        return this.http.request(url, request); 
       } 
       return Observable.throw(initialError); 
    }); 
} 

此代碼必須被集成到一個自定義的子類中的Http之一:

一種方法可以是延伸HTTP對象攔截錯誤:

@Injectable() 
export class CustomHttp extends Http { 
    constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) { 
    super(backend, defaultOptions); 
    } 

    request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> { 
    console.log('request...'); 
    return super.request(url, options).catch(res => { 
     // do something 
    });   
    } 

    get(url: string, options?: RequestOptionsArgs): Observable<Response> { 
    console.log('get...'); 
    return super.get(url, options).catch(res => { 
     // do something 
    }); 
    } 
} 

和如下所述註冊它:

bootstrap(AppComponent, [HTTP_PROVIDERS, 
    new Provider(Http, { 
     useFactory: (backend: XHRBackend, defaultOptions: RequestOptions) => new CustomHttp(backend, defaultOptions), 
     deps: [XHRBackend, RequestOptions] 
    }) 
]); 

有關詳細信息看看這些問題:

+1

你的aproach是我的一樣......對我來說,唯一的問題是並行的請求。如果我訂閱3個不同的http請求...則每個請求都有相同的標記。第一個將使標識失效,另外兩個http請求將失敗。有什麼建議麼? – Michalis

1

我不得不這樣做在我最近的項目shafihuzaib/cdp-ng-boilerplate類似的東西,落在這個問題我的回答。我不能去尋求上述建議的解決方案,因爲它感覺複雜而且不可取。所以在我實施一個解決方案後,我回來離開我的解決方案。但是,不同的是,在我的情況下,我有兩個這樣的令牌。

因此,每個需要有令牌有效性檢查的請求都會在這個函數內部調用。

tokenValidatedRequest(func): Observable<any>{ 
    let returnObservable = new Observable(); 

    /** 
    * 1. check for auth token expiry - refresh it, if necessary 
    */ 
    if(parseInt(localStorage.getItem('AUTH_TOKEN_EXPIRY')) < (new Date()).valueOf()){ 
     //auth expired 
     this.refresh().subscribe(res => { 
      //refreshed 
      //this.postAuthSuccess(res); 

      returnObservable = func(); 

     }) 
    } 
    else{ 
     //auth not expired 

     returnObservable = func(); 

    } 

    return returnObservable; 
} 

最重要的這裏的事情是,func()應該返回一個Observable,以便它可以進行相應的消耗。

makeSomeHttpCall(){ 
    this.tokenValidatedRequest(()=>{ 
     return this.http.post(...); 
    }). subscribe(); 
} 

這對新人來說似乎有些複雜,但我相信它更有效一些。

在以下鏈接中,請忽略與此問題無關的詳細信息,並重點介紹建議解決方案的用法。

Actual implementation of tokenValidatedRequest() in my project

tokenValidatedRequest(func , tqlCheck = false): Observable<any>{ 
    /** 
    * Delegate the actual task. However return an Observable, so as to execute 
    * the callback function only when subscribed to.. 
    */ 
    //return Observable.create(obs => obs = (this.__tokenValidatedRequest(func, tqlCheck))); 

    return this.__tokenValidatedRequest(func, tqlCheck); 
} 
private __tokenValidatedRequest(func, tqlCheck = false): Observable<any>{ 
    let returnObservable = new Observable(); 

    /** 
    * 1. check for auth token expiry - refresh it, if necessary 
    * 2. after step 1 - check for TQL token expiry (if tqlCheck is true) - refresh it, if necessary 
    * 3. 
    */ 
    if(parseInt(localStorage.getItem('AUTH_TOKEN_EXPIRY')) < (new Date()).valueOf()){ 
     //auth expired 
     this.refresh().subscribe(res => { 
      //refreshed 
      this.postAuthSuccess(res); 

      if(tqlCheck && localStorage.getItem("TQL_TOKEN_EXPIRY") && 
        parseInt(localStorage.getItem("TQL_TOKEN_EXPIRY")) < (new Date()).valueOf() 
       ){ 

       this.activateUser().subscribe(res => { 
        //TQL token subscribed 
        returnObservable = func(); 
       }) 
      } 
      else{ 
       // Probably not a TQL request 
       returnObservable = func(); 
      } 
     }) 
    } 
    else{ 
     //auth not expired 

     //check if tql token has expired 
     if(tqlCheck && localStorage.getItem("TQL_TOKEN_EXPIRY") && 
        parseInt(localStorage.getItem("TQL_TOKEN_EXPIRY")) < (new Date()).valueOf() 
       ){ 

       this.activateUser().subscribe(res => { 
        //TQL token subscribed 
        returnObservable = func(); 
       }) 
      } 
      else{ 
       // Probably not a TQL request or none of the tokens expired 
       returnObservable = func(); 
      } 
    } 

    return returnObservable; 
} 

How it is used in other services!

getAllParkingSpaces() : Observable<any> { 
    let query = { 
     Query: { 
      .... 
     } 
    }; 

    return this.authService.tokenValidatedRequest(()=>{ 
     return this.api.post(CONFIG.api.engineUrl + 'devices/parking', query); 
    }, true); 
} 

How I finally subscribe to it!

this.realTimeParkingService.getAllParkingSpaces().subscribe(r => { 
    this.parkingSpaces = r; 
});