【问题标题】:Angular 2: How to handle http oauth authtoken 401 errors on a global basisAngular 2:如何在全球范围内处理 http oauth authtoken 401 错误
【发布时间】:2018-03-29 10:30:48
【问题描述】:

我想使用全局错误处理程序来处理访问令牌过期并使用刷新令牌刷新它们。我几乎似乎可行的解决方案如下:

从你的组件中,订阅一个 observable,如下所示:

   this.myHttpService.getSomeData().subscribe(response => {
               console.log('user logged in', response );   
      },
      error => {
        // optionally do something custom here.
        console.log(error);
        throw error;
        // we could handle the error here, getting the new auth token and calling this again. 
    }

上面的错误处理程序不是必需的,因为我们可以在全局错误处理程序中处理错误,但您可能想在这里处理其他事情。

在我的 http 服务中,我有一个返回 observable 的函数,如下所示:

  getSomeData(): Observable<any> {
    return this.http.get(this.API_URL_BASE + '/api/activities/getsomeData, this.httpHelperMethodsService.defaultOptions).map((response: Response) => {     
      return response.json();
    }).catch(this.handleError);
  }

处理身份验证令牌过期的过程如下: 1.捕捉错误 2. 识别授权令牌已过期 3.调用以使用刷新令牌获取新的身份验证令牌 4. 重新运行源 observable

在服务中,实现处理错误函数,如下所示:

private handleError(error: any, originalObservable: any) {
    let errorHelper = { 'error': error, 'observable': originalObservable};
    return  Observable.throw(errorHelper);
}

然后您可以创建一个全局错误处理程序,如下所示:

@Injectable()
export class ApplicationErrorHandler extends ErrorHandler {

constructor(private injector: Injector) {
    super(false);
  }

  handleError(error: any): void {
     const refreshTokenService = this.injector.get(RefreshTokenService);
      ... 
      else if(error.statusText == "Unauthorized"){
          // check if token is expired. This will involve looking in local storage and comparing expiry time to current time.
          if(isTokenExpired){
              refreshTokenService.refreshToken(refreshToken).subscribe(response => {
                     // set refresh token
                      error.observable().subscribe(response => {
                        // return response.
                        }, error => {
                         // return error. 
                         })
               }
          }
     }
  }

}

使用http-interceptor,配置如下:

import {Injectable} from "@angular/core";
import { ConnectionBackend, RequestOptions, Request, RequestOptionsArgs, Response, Http, Headers} from "@angular/http";
import {Observable} from "rxjs/Rx";


@Injectable()
export class InterceptedHttp extends Http {

    constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
        super(backend, defaultOptions);
    }

    request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
        return super.request(url, options);
    }

    get(url: string, options?: RequestOptionsArgs): Observable<Response> {
        return super.get(url, this.getRequestOptionArgs(options));
    }

    post(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
        return super.post(url, body, this.getRequestOptionArgs(options));
    }

    put(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
        return super.put(url, body, this.getRequestOptionArgs(options));
    }

    delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
        return super.delete(url, this.getRequestOptionArgs(options));
    }

    private getRequestOptionArgs(options?: RequestOptionsArgs) : RequestOptionsArgs {
        return this.getOptionsWithAccessToken();
    }

    getOptionsWithAccessToken() {
        let accessToken = localStorage.getItem('access_token');
        let accessTokenJson = <any>JSON.parse(accessToken);
        let headers = new Headers();
        headers.append('Content-Type', 'application/json');
        headers.append('Accept', 'application/json');
        headers.append('Authorization', accessTokenJson);
        let options = new RequestOptions({ headers: headers });
        console.log('options updated', options);
        return options;
      }
}

然而,当再次订阅原始 observable 时,不会调用 http 拦截器,而是得到另一个 401。根据日志语句console.log('options updated', options);,我可以看到这个拦截器在所有其他 http 调用之前被调用。订阅原始observable时好像没有运行http拦截器,是这样吗?

有没有办法更新这个 observable 上的选项以使用新的身份验证令牌?也许我可以在可观察对象上调用其他方法之一而不是订阅?或者这是不可能的?我能想到的唯一选择是在每个 http 调用中捕获错误,但我有大约一百个,所以宁愿不这样做。

【问题讨论】:

  • 谢谢。是的,这听起来像是解决方案。如果在拦截器中设置了身份验证令牌,那么它将按预期设置。我现在要回家了,明天试试这个。
  • @JBNizet 我已更新问题以包括我尝试使用 http 拦截器,但不幸的是这不起作用。

标签: angular rxjs


【解决方案1】:

我从使用Http 升级到HttpClient。然后我实现了新的 httpInterceptor ,可以传入Injector 以访问其他服务,并检查每个请求以查看令牌是否即将到期,如果是这种情况则刷新它,否则会捕获错误。

@Injectable()
export class MyInterceptor implements HttpInterceptor {
    API_URL_BASE;
    isCurrentlyRefreshing: boolean = false;

    constructor(private injector: Injector) {
        this.API_URL_BASE = myGlobals.API_GLOBAL_VAR;
    }

    intercept(
        req: HttpRequest<any>,
        next: HttpHandler
    ): Observable<HttpEvent<any>> {

        if (!this.isCurrentlyRefreshing) {
            var isTokenRefreshRequired = this.checkIfRefreshRequired();
            if (isTokenRefreshRequired) {
                this.refreshToken().subscribe(response => {
                    this.updateDetailsAfterLogin(response);

                    const httpService = this.injector.get(HttpHelperMethodsService);
                    httpService.loginFromRefreshToken();
                    this.isCurrentlyRefreshing = false;
                    console.log('response', response);
                }, error => {
                    this.isCurrentlyRefreshing = false;
                });
            }
        }


        return next.handle(req).do(evt => {

        }, err => {
            if (err instanceof HttpErrorResponse && err.status == 401) {
               let toastr = this.injector.get(TOASTR_TOKEN);

                this.refreshToken().subscribe(response => {
                this.updateDetailsAfterLogin(response);
                const httpService = this.injector.get(HttpHelperMethodsService);
                httpService.loginFromRefreshToken();
                toastr.info('User session refreshed following timeout. Please retry.');
            }, error => {
                if(error.error){
                    if(error.error.error == "invalid_grant"){
                        toastr.error("session timed out");
                        var userService = this.injector.get(UserService);
                        var router = this.injector.get(Router);
                        userService.logout();
                        router.navigate(['/login']);
                    }
                }
            })
        }
    });
}

虽然我发现可以从这里重新触发 put 或 post 请求,但在刷新令牌后重新发送 req,响应没有链接回初始观察者。我决定在这些罕见的边缘情况下显示一个 toastr 是可以的。

【讨论】:

    【解决方案2】:
    // Worked with  angular 5.2
    import {
      HttpEvent,
      HttpInterceptor,
      HttpHandler,enter code here
      HttpRequest,
      HttpResponse,
      HttpErrorResponse
    } from '@angular/common/http';
    import { Observable } from 'rxjs/Observable';
    import 'rxjs/add/observable/throw';
    import { catchError, tap } from 'rxjs/operators';
    import 'rxjs/add/operator/do';
    import { Router } from '@angular/router';
    import { Injectable } from '@angular/core';
    
    @Injectable()
    export class AuthInterceptor implements HttpInterceptor {
      constructor(public router: Router) {}
      intercept(
        request: HttpRequest<any>,
        next: HttpHandler
      ): Observable<HttpEvent<any>> {
        return next.handle(request).pipe(
          tap(
            event => {},
            error => {
              this.handleError(error);
            }
          )
        );
      }
      private handleError(error: Response) {
        if (error.status === 401) {
          this.router.navigate(['/']);
        }
        return Observable.throw(error);
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2016-04-28
      • 2016-07-03
      • 2017-01-05
      • 2017-12-16
      • 2018-03-25
      • 2014-09-14
      • 1970-01-01
      • 2018-05-05
      • 2021-04-05
      相关资源
      最近更新 更多