当前位置: 首页 > 知识库问答 >
问题:

令牌刷新后Angular 4侦听器重试请求

公羊嘉
2023-03-14

您好,我正试图通过刷新令牌并重试请求,找出如何实现新的角度拦截器和处理401未经授权的错误。这是我一直遵循的指南:https://ryanchenkie.com/angular-authentication-using-the-http-client-and-http-interceptors

我成功缓存失败的请求,并可以刷新令牌,但我不知道如何重新发送以前失败的请求。我还想让它与我目前使用的解析器一起工作。

token.interceptor.ts

return next.handle( request ).do(( event: HttpEvent<any> ) => {
        if ( event instanceof HttpResponse ) {
            // do stuff with response if you want
        }
    }, ( err: any ) => {
        if ( err instanceof HttpErrorResponse ) {
            if ( err.status === 401 ) {
                console.log( err );
                this.auth.collectFailedRequest( request );
                this.auth.refreshToken().subscribe( resp => {
                    if ( !resp ) {
                        console.log( "Invalid" );
                    } else {
                        this.auth.retryFailedRequests();
                    }
                } );

            }
        }
    } );

认证。服务ts

cachedRequests: Array<HttpRequest<any>> = [];

public collectFailedRequest ( request ): void {
    this.cachedRequests.push( request );
}

public retryFailedRequests (): void {
    // retry the requests. this method can
    // be called after the token is refreshed
    this.cachedRequests.forEach( request => {
        request = request.clone( {
            setHeaders: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
                Authorization: `Bearer ${ this.getToken() }`
            }
        } );
        //??What to do here
    } );
}

上面的retryFailed请求()文件是我不明白的。如何重新发送请求,并使它们在重试后通过解析器的路由可用?

如果有帮助的话,这是所有相关的代码:https://gist.github.com/joshharms/00d8159900897dc5bed45757e30405f9

共有3个答案

长孙景天
2023-03-14

我必须解决以下要求:

  • ✅ 对于多个请求,仅刷新令牌一次

因此,我收集了不同的选项,以便在Angular中刷新令牌:

  • 蛮力解决方案与令牌刷新$行为主体作为信号量
  • 在RxJS操作符中使用catchError参数重试请求失败的请求
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    let retries = 0;
    return this.authService.token$.pipe(
      map(token => req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })),
      concatMap(authReq => next.handle(authReq)),
      // Catch the 401 and handle it by refreshing the token and restarting the chain
      // (where a new subscription to this.auth.token will get the latest token).
      catchError((err, restart) => {
        // If the request is unauthorized, try refreshing the token before restarting.
        if (err.status === 401 && retries === 0) {
          retries++;
    
          return concat(this.authService.refreshToken$, restart);
        }
    
        if (retries > 0) {
          this.authService.logout();
        }
    
        return throwError(err);
      })
    );
}
  • 使用retry当RxJS运算符
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return this.authService.token$.pipe(
      map(token => req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })),
      concatMap(authReq => next.handle(authReq)),
      retryWhen((errors: Observable<any>) => errors.pipe(
        mergeMap((error, index) => {
          // any other error than 401 with {error: 'invalid_grant'} should be ignored by this retryWhen
          if (error.status !== 401) {
            return throwError(error);
          }
    
          if (index === 0) {
            // first time execute refresh token logic...
            return this.authService.refreshToken$;
          }
    
          this.authService.logout();
          return throwError(error);
        }),
        take(2)
        // first request should refresh token and retry,
        // if there's still an error the second time is the last time and should navigate to login
      )),
    );
}

所有这些选项都经过全面测试,可以在angular refresh token github repo中找到

另见:

  • catchError-RxJS参考
秦伯寅
2023-03-14

使用最新版本的Angular(7.0.0)和rxjs(6.3.3),这就是我如何创建一个功能齐全的自动会话恢复拦截器,以确保如果401并发请求失败,那么它也应该只命中令牌刷新API一次,并将失败的请求管道到使用SwitchMap和主题的响应。下面是我的拦截代码的样子。我省略了我的身份验证服务和存储服务的代码,因为它们是相当标准的服务类。

import {
  HttpErrorResponse,
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest
} from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable, Subject, throwError } from "rxjs";
import { catchError, switchMap } from "rxjs/operators";

import { AuthService } from "../auth/auth.service";
import { STATUS_CODE } from "../error-code";
import { UserSessionStoreService as StoreService } from "../store/user-session-store.service";

@Injectable()
export class SessionRecoveryInterceptor implements HttpInterceptor {
  constructor(
    private readonly store: StoreService,
    private readonly sessionService: AuthService
  ) {}

  private _refreshSubject: Subject<any> = new Subject<any>();

  private _ifTokenExpired() {
    this._refreshSubject.subscribe({
      complete: () => {
        this._refreshSubject = new Subject<any>();
      }
    });
    if (this._refreshSubject.observers.length === 1) {
      this.sessionService.refreshToken().subscribe(this._refreshSubject);
    }
    return this._refreshSubject;
  }

  private _checkTokenExpiryErr(error: HttpErrorResponse): boolean {
    return (
      error.status &&
      error.status === STATUS_CODE.UNAUTHORIZED &&
      error.error.message === "TokenExpired"
    );
  }

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    if (req.url.endsWith("/logout") || req.url.endsWith("/token-refresh")) {
      return next.handle(req);
    } else {
      return next.handle(req).pipe(
        catchError((error, caught) => {
          if (error instanceof HttpErrorResponse) {
            if (this._checkTokenExpiryErr(error)) {
              return this._ifTokenExpired().pipe(
                switchMap(() => {
                  return next.handle(this.updateHeader(req));
                })
              );
            } else {
              return throwError(error);
            }
          }
          return caught;
        })
      );
    }
  }

  updateHeader(req) {
    const authToken = this.store.getAccessToken();
    req = req.clone({
      headers: req.headers.set("Authorization", `Bearer ${authToken}`)
    });
    return req;
  }
}

根据@安东-toshik的评论,我认为在一篇文章中解释这段代码的功能是个好主意。你可以在这里阅读我的文章,了解这段代码的解释和理解(它是如何工作的以及为什么工作的?)。希望有帮助。

浦德义
2023-03-14

我的最终解决方案。适用于并行请求。

更新:代码更新为Angular 9/RxJS 6,错误处理和refreshToken失败时的修复循环

import { HttpRequest, HttpHandler, HttpInterceptor, HTTP_INTERCEPTORS } from "@angular/common/http";
import { Injector } from "@angular/core";
import { Router } from "@angular/router";
import { Subject, Observable, throwError } from "rxjs";
import { catchError, switchMap, tap} from "rxjs/operators";
import { AuthService } from "./auth.service";

export class AuthInterceptor implements HttpInterceptor {

    authService;
    refreshTokenInProgress = false;

    tokenRefreshedSource = new Subject();
    tokenRefreshed$ = this.tokenRefreshedSource.asObservable();

    constructor(private injector: Injector, private router: Router) {}

    addAuthHeader(request) {
        const authHeader = this.authService.getAuthorizationHeader();
        if (authHeader) {
            return request.clone({
                setHeaders: {
                    "Authorization": authHeader
                }
            });
        }
        return request;
    }

    refreshToken(): Observable<any> {
        if (this.refreshTokenInProgress) {
            return new Observable(observer => {
                this.tokenRefreshed$.subscribe(() => {
                    observer.next();
                    observer.complete();
                });
            });
        } else {
            this.refreshTokenInProgress = true;

            return this.authService.refreshToken().pipe(
                tap(() => {
                    this.refreshTokenInProgress = false;
                    this.tokenRefreshedSource.next();
                }),
                catchError(() => {
                    this.refreshTokenInProgress = false;
                    this.logout();
                }));
        }
    }

    logout() {
        this.authService.logout();
        this.router.navigate(["login"]);
    }

    handleResponseError(error, request?, next?) {
        // Business error
        if (error.status === 400) {
            // Show message
        }

        // Invalid token error
        else if (error.status === 401) {
            return this.refreshToken().pipe(
                switchMap(() => {
                    request = this.addAuthHeader(request);
                    return next.handle(request);
                }),
                catchError(e => {
                    if (e.status !== 401) {
                        return this.handleResponseError(e);
                    } else {
                        this.logout();
                    }
                }));
        }

        // Access denied error
        else if (error.status === 403) {
            // Show message
            // Logout
            this.logout();
        }

        // Server error
        else if (error.status === 500) {
            // Show message
        }

        // Maintenance error
        else if (error.status === 503) {
            // Show message
            // Redirect to the maintenance page
        }

        return throwError(error);
    }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
        this.authService = this.injector.get(AuthService);

        // Handle request
        request = this.addAuthHeader(request);

        // Handle response
        return next.handle(request).pipe(catchError(error => {
            return this.handleResponseError(error, request, next);
        }));
    }
}

export const AuthInterceptorProvider = {
    provide: HTTP_INTERCEPTORS,
    useClass: AuthInterceptor,
    multi: true
};
 类似资料:
  • 我正在尝试实现401响应的捕获,并尝试在令牌刷新后基于Angular 4 Intericetor重试请求获取刷新令牌。我试图实现同样的事情,但我始终无法重试该请求,我真的不确定这是否是应用刷新令牌策略的最佳方法。这是我的代码: 问题是在中从未到达切换映射... 还有do运算符… 这让我想到了我的authService refreshToken方法... 它返回一个映射的可观察值,我知道如果我替换了

  • 我有 401 拦截器,当access_token过期时,有一个请求的成功案例。拦截器重新加载令牌并返回 next.handle(customReq)。但是当同时发出 2 个或更多请求并且两个请求的令牌都已过期时,我遇到了问题,因为第二个请求尝试再次刷新,但现在刷新令牌无效。所以。。。。我尝试设置一个标志只执行一次,并使用自定义可观察量返回。问题是组件现在永远不会成功,我无法删除加载器。 HTTP

  • 我在自己的Web API上使用Oauth2,在Web应用程序上使用ASP.NET C#来使用该API。在我的web应用程序上,我正在进行HttpWebRequests。当我的访问令牌过期时,我调用一个方法“refreshToken”,该方法发出请求以获取新的访问令牌。这工作很好,没有问题...除了我得到的响应包含一个新的刷新令牌???我在等新的访问令牌。我甚至认为在没有再次传递凭据的情况下这是不可

  • 问题内容: 我有一个有角度的应用程序,有时每个状态会执行多个$ http.get请求。该应用将JWT用于带有刷新令牌的用户身份验证。API服务器会发送由于身份验证错误而失败的每个请求。我做了一个请求,该请求在401错误时请求带有刷新令牌的新令牌,然后重新发送原始请求。 问题是,如果一个状态发出例如2个$ http.get请求,并且都获得401响应,那么我将访问令牌更新两次。显然,我只想刷新一次令牌

  • 我正在尝试从应用程序服务中获取Google的刷新令牌,但我不能。 日志说 2016-11-04T00:04:25 PID[500]收到的详细请求:获取https://noteappsvr.azurewebsites.net/.auth/login/google?access _ type = offline 2016-11-04t 00:04:25 PID[500]从https://account

  • 我在刷新访问令牌时得到这个错误:访问令牌无法刷新。请重新验证 这一错误此前曾在2017年4月报告过。OneLogin文档声明刷新令牌可以使用45天左右。我的刷新令牌已经有20个小时了。是文档正确还是刷新令牌的寿命更短?我可以做获取访问令牌和撤销令牌罚款。 public RootObject RefreshToken(HttpRequesterDM rDM){restsharp.deserializ