Angular 2:如何全局处理http oauth authtoken 401错误 [英] Angular 2: How to handle http oauth authtoken 401 errors on a global basis

查看:86
本文介绍了Angular 2:如何全局处理http oauth authtoken 401错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want to use the global error handler to handle access tokens expiring and refreshing them using the refresh token. My solution that almost seems to work is as follows:

从您的组件中,订阅如下的观察对象:

rom your component, subscribe to an observable as follows:

   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. 
    }

上面的错误处理程序不是必需的,因为我们可以在全局错误处理程序中处理错误,但是您可能要在这里处理其他内容.

The error handler above is not necessary, because we can handle the error in the global error handler, but you may want to handle something else here.

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

In my http service, I have a function returning an observable like this:

  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.重新运行可观察的源代码

The process to handle auth tokens expiring will be as follows: 1. catch the error 2. recognise that the auth token has expired 3. call to get a new auth token using the refresh token 4. re-run the source observable

在服务中,实现句柄错误功能,如下所示:

In the service, implement the handle error function, which looks like this:

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

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

You can then create a global error handler as follows:

@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拦截器,其配置如下:

Use an http-interceptor, which is configured as follows:

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.我可以基于log语句看到 console.log('options Updated',options); 该拦截器在所有其他http调用之前被调用.订阅原始可观察者时,http拦截器似乎没有运行,是这种情况吗?

The http interceptor however, is not called when subscribing again to the original observable, instead I just get another 401. I can see based on the log statementconsole.log('options updated', options); this interceptor is being called prior to all other http calls. It seems that the http interceptor does not run when you subscribe to the original observable, is this the case?

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

Is there a way to update the options on this observable to use the new auth token? Perhaps I can call one of the other methods on the observable instead of subscribe? Or is this not possible? The only alternative I can think of is to catch the error on every http call, but I have about a hundred of them, so would rather not do this.

推荐答案

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

I upgraded from using Http to HttpClient. I then implemented the new httpInterceptor where it is possible to pass in an Injector to access other services and checked with each request to see if the token is expiring soon, refreshing it if this was the case, otherwise catching the error.

@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']);
                    }
                }
            })
        }
    });
}

虽然我发现可以通过刷新令牌后重新发送 req 从此处重新发出放置或发布请求,但响应并未链接回初始观察者.我认为在这些罕见的情况下可以烤面包机.

Whilst I found it possible to refire a put or post request from here, by re-sending req after refreshing the token, the response did not link back to the initial observer. I decided that it would be ok to display a toastr in these rare edge cases.

这篇关于Angular 2:如何全局处理http oauth authtoken 401错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆