2016-04-15 48 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; 
});