在发出克隆请求之前更新令牌

Renew token before making cloned request

因此,如果令牌已过期,我想使用刷新令牌更新访问令牌,这就是我的令牌拦截器的样子:

intercept(req: HttpRequest<any>, next: HttpHandler) {
    let authservice = this.injector.get(AuthService)
      let tokenreq = req.clone({
        setHeaders: {
          Authorization: `Bearer ${authservice.setToken()}`
        }
      })
      return next.handle(tokenreq).pipe(
        catchError((error) => {
          if (error.error.message == 'The incoming token has expired') {
            authservice.renewToken()
            let tokenreq = req.clone({
              setHeaders: {
                Authorization: `Bearer ${authservice.setToken()}`
              }
            })
            return next.handle(tokenreq)
          }
          else return throwError(error)
        })
      );
    }
  }

问题是在我收到过期令牌消息后 authservice.renewToken()authservice.setToken() 被同时调用,所以过期的令牌被再次设置。

另一个问题是,如果用户使用 cookie 中的过期令牌再次打开应用程序,所有 GET 方法将抛出错误并多次请求新令牌。如何处理过期令牌错误?

您可以通过使用 retryWhen 运算符将 setToken 连接到 returned 可观察对象来修复此错误行为。 这样 renewTokensetToken 将不会并行执行,更重要的是 setToken 将在每个请求中被拦截器链考虑。

intercept(req: HttpRequest<any>, next: HttpHandler) {
    const authservice = this.injector.get(AuthService);
      
    return of(req).pipe(
          switchMap((req) => {
            return authservice.setToken()               // Gets the token. *should rename this method to getToken()
                .pipe(
                    map(token => {                      // Set the token.
                        const tokenreq = req.clone({
                            setHeaders: {
                                Authorization: `Bearer ${authservice.setToken()}`
                            }
                            });

                        return tokenreq;
                    })
                ),
            switchMap(tokenreq => next.handle(tokenreq)), // Execute next interceptor and eventually send the request.
            retryWhen(errors => errors.pipe(
                mergeMap((err: HttpErrorResponse, i: number) => {
                    authservice.invalidateToken()        // Invalidate token. Erase token or expires it.
                    if (error.error.message == 'The incoming token has expired') {
                        return of(err);                   // will start the current pipe all over again - and get the token once again.
                    }

                    return throwError(error);
                })
            )
        )
  }

解释:

在问题中,setToken 未连接到由拦截器方法 return 编辑的可观察链。 之前的代码执行流程:

1) interceptor method execution -> setToken() -> return observable
2) request asked by http.get(..) -> chain of observables return by interceptors -> request sent -> chain of observables

在这个回答中:

1) interceptor method execution -> setToken() -> return observable
2) request asked by http.get(..) -> chain of observables return by interceptors and setToken() inside one! -> request sent -> chain of observables

注:

setToken 方法应该 return 一个带有令牌的可观察对象并且 invalidateToken 应该能够删除令牌。

这可以通过以下方式轻松实现:

private token$: AsyncSubject<string>;

getToken(): Observable {
    if (!token$) this.token$ = new AsyncSubject();

    getTokenOperation.subscribe(t => {
        this.token$.next(t);
        this.token$.complete();
    })

    return this.token$.asObservable();
}

invalidateToken() {
    this.token$ = null;
}

您需要传递新令牌。

return next.handle(request).pipe(
        map((event: HttpEvent<any>) => {
            return this.eventCallSendBack(event);
        }),
        catchError((error: HttpErrorResponse) => {
            if (error instanceof HttpErrorResponse) {
                switch (error.status) {
                    case 401:
                        return this.handle401Error(request, next);
                    case 403:
                        //DO Want you want here.
                        throw error;
                    default:
                        throw error;
                }
            }
            throw error;
        })
    );

这是回调 fn。

private eventCallSendBack(event) {
    if (event instanceof HttpResponse) {
        return event.clone({ body: event.body });
    }
    return event;
}

您想拥有一个添加令牌 fn:

private addToken(event: HttpRequest<any>, token: string) {
    return event.clone({
        setHeaders: {
            Authorization: `Bearer ${token}`
        }
    });
}

现在假设您的令牌已过期,您需要创建 fn 以从某些 Auth 获取新令牌。

private handle401Error(
    request: HttpRequest<any>,
    next: HttpHandler
): Observable<any> {
        return authServices
            .refreshToken(
                token //This is the old token to get the new.
            )
            .pipe(
                map(jwt => {
                    return jwt;
                }),
                switchMap((jwt: any) => {
                    return next
                        .handle(this.addToken(request, jwt.token)) // Add token in responce 
                        .pipe(
                            map((event: HttpEvent<any>) => {
                                return this.eventCallSendBack(event);
                            })
                        );
                }),
                catchError(err => {
                    return throwError(err);
                })
            );
    
}