【问题标题】:HttpInterceptor not adding new token to subsequent requests UNTIL after page refresh页面刷新后,HttpInterceptor 未向后续请求添加新令牌
【发布时间】:2021-03-16 14:25:25
【问题描述】:

我在我的应用程序中使用拦截器,当令牌过期(返回 401)时,我想刷新令牌,将新令牌保存到 localstorage,然后使用新令牌继续请求。

捕获 401 错误有效,我能够获取新令牌,但请求仍然失败并出现 401 并且不起作用直到我刷新页面。

这是当前使用的拦截方法的代码:

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  console.log('Interceptor called===============>');
  const token = localStorage.getItem(environment.TOKEN_NAME);

  if (token) {
    const headers = { Authorization: `Bearer ${token}` };
    Object.keys(AppHttpInterceptor.headers).forEach((header) => {
      // @ts-ignore
      if (!AppHttpInterceptor.headers[header]) {
        return;
      }
      // @ts-ignore
      headers[header] = AppHttpInterceptor.headers[header];
    });
    request = request.clone({ setHeaders: headers });
  }
  const handled: Observable<HttpEvent<any>> = next.handle(request);
  const subject: AsyncSubject<HttpEvent<any>> = new AsyncSubject();
  handled.subscribe(subject);
  subject.subscribe((event: HttpEvent<any>) => {
    if (event instanceof HttpErrorResponse) {
      if (event.status === 401) {
        return;
      }

      this.httpError.emit(event);
    }
  }, (err: HttpEvent<any>) => {
    if (err instanceof HttpErrorResponse) {
      if (err.status === 401) {
        this.tokenRefreshService.refreshToken().subscribe(response => {
          const headers = { Authorization: `Bearer ${localStorage.getItem(environment.TOKEN_NAME)}` };
          console.log('HEADERS ======> ' + headers);
          // @ts-ignore
          Object.keys(AppHttpInterceptor.headers).forEach((header) => {
            // @ts-ignore
            if (!AppHttpInterceptor.headers[header]) {
              return;
            }
            // @ts-ignore
            headers[header] = AppHttpInterceptor.headers[header];
          });
          request = request.clone({ setHeaders: headers });
        });

        return;
      }
      if (err.status === 404) {
        return;
      }
      this.httpError.emit(err);
    }
  });
  return Observable.create((obs: Observer<HttpEvent<any>>) => {
    subject.subscribe(obs);
  });
}

这是正确的方法吗?

【问题讨论】:

  • 为什么不使用令牌静默刷新?假设您已经获得 401 这不是刷新令牌的最佳方法。此外,订阅 observables 也是不好的做法,如果您愿意,您应该操作流
  • 只是为了让您可以一瞥这里:dev-academy.com/angular-jwt我不知道您到底想做什么,但是您很可能想要实现一个逻辑@ 987654324@
  • @LukaszBalazy 感谢您的回复。我查看了静默刷新选项,不幸的是,我的身份验证服务器运行在 Spring Security 之后,我无法将其配置为与 OIDC 一起使用
  • 你正在以一种奇怪的方式做这件事。您只需要一些catchError/retry* 运算符,获取一个新令牌并重做请求。在您的拦截器中,您似乎正在执行一些请求分配,但我没有看到它正在执行,然后重新执行原始请求
  • @Sergey 你能提供一个示例代码吗?我发布的代码实际上是跟踪和错误,因此是赏金。

标签: angular jwt access-token angular-http-interceptors


【解决方案1】:

我可以在您的代码中看到 2 个非常小的问题,这可能是它不添加新令牌的原因,除非您刷新。

  1. 当状态为 404 时,您不会返回任何您拥有的 null 返回

  2. 你在第一次克隆时更新了它自己的初始请求,我 建议您保持初始请求参数不变,并且 还可以处理刷新令牌失败的情况。

        intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
          console.log('Interceptor called===============>');
          const token = localStorage.getItem(environment.TOKEN_NAME);
          if (token) {
            const headers = {Authorization: `Bearer ${token}`};
            Object.keys(AppHttpInterceptor.headers).forEach((header) => {
              // @ts-ignore
              if (!AppHttpInterceptor.headers[header]) {
                return;
              }
              // @ts-ignore
              headers[header] = AppHttpInterceptor.headers[header];
            });
           const  request = req.clone({setHeaders: headers}); // created  a new one intial req is not updated
          }
    
    
    
          return next.handle(request).pipe(catchError(error =>{
            if(error instanceof HttpErrorResponse){
              console.log((<HttpErrorResponse>error));
    
              if((<HttpErrorResponse>error).status === 401 && (<HttpErrorResponse>error).error.console.error_description.indexOf("Invalid refresh token" >= 0)){
                return observableThrowError(error); // if refresh token is exptired
              }
              if((<HttpErrorResponse>error).status === 401 && (<HttpErrorResponse>error).error.console.error_description.indexOf("Invalid_token" >= 0)){
                return this.handle401Error(req, next); //passing initial request
              }
    
    
              return observableThrowError(error);
            }
          }));
    
          handle401Error(req: HttpRequest<any>, next: HttpHandler){
    
            return  this.tokenRefreshService.refreshToken().pipe(
              switchMap(() =>{
                const headers = {Authorization: `Bearer ${localStorage.getItem(environment.TOKEN_NAME)}`};
                const newRequest =  req.clone({setHeaders: headers });
                return next.handle(newRequest); // return a new handle it will create a new request with updated header data
              }),
              catchError(error =>{
                retun observableThrowError(error);
              }),
              finalize(() =>{
                 // some extra lines
              }) // import the token service
            )
          }
    
        }
    

如果这能解决您的问题,请告诉我。 谢谢!

【讨论】:

  • 如果您在使用此解决方案时遇到任何问题,请告诉我 :)
  • 我还没试过。我会尽快恢复的
【解决方案2】:

捕获 401 错误有效,我能够获得一个新令牌,但 请求仍然失败并出现 401 并且在我刷新之前不起作用 页面。

获得新令牌后,您再次发送旧(过期)令牌或拦截器不知道要发送新令牌。我看不到处理(替换)新令牌的正确方法。调用刷新令牌API后如何替换旧令牌?

我们来看看拦截器的完整实现的详细图:

如您所见,拦截器是客户端请求和服务器之间的中间件,具有循环实现。您的拦截器是以一种奇怪的方式制作的,具有循环行为。任何更改都应立即并在其中故意进行,否则会干扰应用程序的行为。至少,你应该让拦截器删除/替换过期令牌本身处理401状态码,虽然我们可以设置 外部标记(在服务甚至组件内部)!

这是拦截器实现的简单干净的逻辑

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

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

    if (token) {
      // addToken to request
    }

    return next.handle(request).pipe(catchError(error => {
      if (error instanceof HttpErrorResponse && error.status === 401) {
        // handle401Error
      } else {
        // throwError
      }
    }));
  }
}

有两种更新令牌的方法,您可以根据您的应用程序工作模型选择:

更新:仔细查看以调试您的方法

您使用的是AsyncSubject 而不是BehaviorSubject,那么有什么区别?实际上,AsyncSubject 的工作方式有点不同,其中只有 Observable 执行的最后一个值被发送给它的订阅者,并且只有在执行完成时。因此,使用AsyncSubject 来实现拦截器并不普遍。

例如,在以下步骤中,使用 AsyncSubject 没有任何反应:

  1. 我们创建AsyncSubject
  2. 我们通过订阅者 A 订阅主题
  3. Subject 发出 3 个值,仍然 没有任何反应
  4. 我们通过订阅者 B 订阅主题
  5. Subject 发出一个新值,仍然没有任何反应
  6. 主题完成。现在这些值被发送到 都记录值的订阅者。

让我们回到您的代码。您订阅AsyncSubject 的唯一线路是subject.subscribe(obs);。以这种方式使用AsyncSubject只会增加拦截器的延迟。我不确定,但您可以通过更多工作来解决您的方法,也许是通过以下方式,然后是我上面提到的逻辑:

next.handle(request).do(event => {
    if (event instanceof HttpResponse) {
        subject.next(event);
        subject.complete();
    }
}).subscribe(); // must subscribe to actually kick off request!
return subject;

【讨论】:

  • @OjonugwaJudeOchalifu 我添加了一个有趣的更新来回答你的主要问题。
  • 看到了。抱歉,我的工作站 ATM 不在,我将在几个小时内更新并恢复。
  • 我选择了您的答案,因为它有助于解决问题并澄清了一些困惑。
【解决方案3】:

您需要重新考虑您对可观察对象的方法,并将其视为您链接的流,类似这样

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
            return from(this.authenticationService.getAuthenticationToken()
                .then(token => {
                    return req.clone({
                        setHeaders: {
                            Authorization: `Bearer ${token}`
                        }
                    });
                })
                .catch(err => {
                    this.authenticationService.ErrorHandler(err);
                    return req;
                }))
                .switchMap(req => {
                    return next.handle(req);
                });
        }

【讨论】:

    猜你喜欢
    • 2018-11-07
    • 1970-01-01
    • 1970-01-01
    • 2019-12-03
    • 2020-11-19
    • 1970-01-01
    • 2013-12-12
    • 2016-01-05
    • 2019-11-08
    相关资源
    最近更新 更多