Angular 4.3 - HTTP 拦截器 - 刷新 JWT 令牌

Posted

技术标签:

【中文标题】Angular 4.3 - HTTP 拦截器 - 刷新 JWT 令牌【英文标题】:Angular 4.3 - HTTP Interceptor - refresh JWT token 【发布时间】:2017-12-31 09:34:40 【问题描述】:

我需要(在拦截器类中)对 403 Forbidden HTTP status(获取/刷新)JWT 令牌做出反应,并使用新令牌重试请求。

在下面的代码中,当服务器返回错误响应时,它会进入成功回调(而不是像我期望的那样进入错误回调),并且事件是 typeof 对象(对错误响应的反应是无用的)。事件对象如下所示: 类型:0。

问题:

-如何正确处理httpErrorResponse(403 Forbidden)中的 当我需要刷新accessToken并重试http请求时使用HttpInterceptor?

 import 
  HttpInterceptor,
  HttpRequest,
  HttpResponse,
  HttpHandler,
  HttpEvent
 from '@angular/common/http';
import 'rxjs/add/operator/map';

@Injectable()
class JWTInterceptor implements HttpInterceptor 

  constructor(private tokenService: TokenService) 
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
  let myHeaders = req.headers;
  if (this.tokenService.accessToken) 
        myHeaders = myHeaders.append('Authorization',`$this.tokenService.accessToken.token_type $this.tokenService.accessToken.access_token`)
   

  const authReq = req.clone(headers: myHeaders);

    return next.handle(authReq).map((event: HttpEvent<any>) => 
      if (event instanceof HttpResponse) 
        // success callback
      
    , (err: any) => 
      if (err instanceof HttpErrorResponse 
        if (err.status === 403) 
          // error callback
          this.tokenService.obtainAccessToken()
        
      
    )
      .retry(1);
  

【问题讨论】:

【参考方案1】:

您需要从 RxJS 添加 catch 运算符。这就是错误所在,您可以相应地处理它。

当您收到状态 0 的错误时,这很可能意味着远程服务器已关闭并且无法建立连接。

看看我的示例逻辑:

this.http.request(url, options)
        .map((res: Response) => res.json())
        .catch((error: any) => 
            const err = error.json();

            // Refresh JWT
            if (err.status === 403) 
                // Add your token refresh logic here.
            

            return Observable.throw(err);
        );

为了让你的刷新逻辑通过拦截器,你需要返回调用,函数也应该返回一个Observable。比如修改上面原来的逻辑:

this.http.request(url, options)
        .map((res: Response) => res.json())
        .catch((error: any) => 
            const err = error.json();

            // Refresh JWT
            if (err.status === 403) 
                // refreshToken makes another HTTP call and returns an Observable.
                return this.refreshToken(...);
            

            return Observable.throw(err);
        );

如果您希望能够重试原始请求,您可以做的是传递原始请求数据,以便在成功刷新令牌后再次进行调用。例如,这就是您的 refreshToken 函数的样子:

refreshToken(url: stirng, options: RequestOptionsArgs, body: any, tokenData: any): Observable<any>
    return this.post(`$this.url/token/refresh`, tokenData)
        .flatMap((res: any) => 
            // This is where I retry the original request
            return this.request(url, options, body);
        );

【讨论】:

它解决了我的部分问题——在 catch 中,我现在可以访问 HttpErrorResponse 并调用我的刷新/获取令牌逻辑——谢谢。但是当我调用 .retry(1) 时,新的 http 请求不会再次通过拦截器(因此 JWT 标头不会添加到新调用中) 您需要在刷新函数中返回一个Observable。然后在该 catch 块中返回该刷新函数的调用,它将添加到拦截器中。例如更新的答案。【参考方案2】:

我对这个问题的最终解决方案:

@Injectable()
export class WebApiInterceptor implements HttpInterceptor 
  constructor(private tokenService: TokenService) 
  

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
    console.log('*An intercepted httpRequest*', req, this.tokenService.accessToken);
    const authReq = this.authenticateRequest(req);
    console.log('*Updated httpRequest*', authReq);
    return next.handle(authReq)
      .map((event: HttpEvent<any>) => 
        if (event instanceof HttpResponse) 
          console.log('*An intercepted httpResponse*', event);
          return event;
        
      )
      .catch((error: any) => 
        if (error instanceof HttpErrorResponse) 
          if (error.status === 403 && error.url !== environment.authEndpoint) 
            return this.tokenService
              .obtainAccessToken()
              .flatMap((token) => 
                const authReqRepeat = this.authenticateRequest(req);
                console.log('*Repeating httpRequest*', authReqRepeat);
                return next.handle(authReqRepeat);
              );
          
         else 
          return Observable.throw(error);
        
      )
  

功能

authenticateRequest(req)

只需将授权标头添加到原始请求的副本中

功能

obtainAccessToken()

获取新的令牌形式授权服务器并存储它

【讨论】:

【参考方案3】:

只是想分享对我有用的东西:

@Injectable()
export class AutoReLoginInterceptor implements HttpInterceptor 

    constructor() 
    

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
        // as we want to intercept the possible errors, instead of directly returning the request execution, we return an Observable to control EVERYTHING
        return new Observable<HttpEvent<any>>(subscriber => 

            // first try for the request
            next.handle(req)
                .subscribe((event: HttpEvent<any>) => 
                        if (event instanceof HttpResponse) 
                            // the request went well and we have valid response
                            // give response to user and complete the subscription
                            subscriber.next(event);
                            subscriber.complete();
                        
                    ,
                    error => 
                        if (error instanceof HttpErrorResponse && error.status === 401) 
                            console.log('401 error, trying to re-login');

                            // try to re-log the user
                            this.reLogin().subscribe(authToken => 
                                // re-login successful -> create new headers with the new auth token
                                let newRequest = req.clone(
                                    headers: req.headers.set('Authorization', authToken)
                                );

                                // retry the request with the new token
                                next.handle(newRequest)
                                    .subscribe(newEvent => 
                                        if (newEvent instanceof HttpResponse) 
                                            // the second try went well and we have valid response
                                            // give response to user and complete the subscription
                                            subscriber.next(newEvent);
                                            subscriber.complete();
                                        
                                    , error => 
                                        // second try went wrong -> throw error to subscriber
                                        subscriber.error(error);
                                    );
                            );
                         else 
                            // the error was not related to auth token -> throw error to subscriber
                            subscriber.error(error);
                        
                    );
        );

    

    /**
     * Try to re-login the user.
     */
    private reLogin(): Observable<string> 
        // obtain new authorization token and return it
    

【讨论】:

完美。谢谢。授权标头应包含 Bearer 前缀。【参考方案4】:
import  Injectable  from '@angular/core';
import  HttpRequest, HttpHandler, HttpEvent, HttpInterceptor  from '@angular/common/http';
import  Observable  from 'rxjs';

@Injectable()
export class JwtInterceptor implements HttpInterceptor 
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
        // add authorization header with jwt token if available
        const currentUser = JSON.parse(sessionStorage.getItem('currentUser'));
        console.log('Interceter called');
        console.log(currentUser);
        //  const re = 'https://newsapi.org';
        const re = '/user';
        if (request.url.search(re) === -1) 

            if (currentUser && currentUser.token) 
                console.log('Token is being added....!!!!!');
                // console.log(currentUser.token);
                request = request.clone(
                    setHeaders: 
                        Authorisation: `Token $currentUser.token`,
                    
                );
            
            console.log('Request Sent :');
            console.log(request);
        
        return next.handle(request);
    

【讨论】:

以上是关于Angular 4.3 - HTTP 拦截器 - 刷新 JWT 令牌的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Angular cli 升级 Angular 的次要版本

向 Angular 应用程序添加多个 HTTP 拦截器

Angular - http 拦截器 - http 速率限制器 - 滑动窗口

单元测试 Angular 12 HTTP 拦截器 expectOne 失败

Angular 5:从拦截器中的 http 响应标头获取授权

在 http 请求之前执行的 Angular 拦截器