在角度加载组件之前无法获取最新令牌

问题描述 投票:0回答:1

我需要相关建议来实现我的需求,这就是:

我在我的 Angular 应用程序中实现了基于 jwt 的身份验证系统。当我获取新令牌和刷新令牌时,我创建一个 setTimeout ,以便可以在令牌过期之前刷新令牌。但当我引导应用程序(在我的 AppComponent 中)时,我也特意刷新了我的令牌,我的问题与此案例相关。

我从jwt获取登录的用户信息,问题是当应用程序启动并执行令牌刷新时,令牌在组件显示后存储,这导致应用程序获取用户信息之前的令牌而不是新创建的令牌。

我们可以考虑使用解析器,但问题是,使用解析器时,我将触发 2 个刷新令牌查询,一个在我的 AppComponent 中,一个在解析器内,这不是我需要的行为。

所以基本上,这是我的 AppComponent 的一部分:

     ngOnInit() {
        this.refreshToken();
        ...
      }
    
    private refreshToken() {
        this.authSvc.refreshToken().pipe(
          untilDestroyed(this),
          catchError(error => {
            if (error.status === 401) {
              this.router.navigate(['/login']);
            }
            return of();
          })
        ).subscribe();
      }

这是AuthService的一部分:

    get user(): User | null {
      const token = localStorage.getItem('token');
      return token ? this.jwtHelper.decodeToken(token) : null;
    }

    refreshToken(): Observable<LoginResponse> {
        return this.http.post<LoginResponse>(
          `${environment.apiUrl}/token/refresh`,
          {refresh_token: localStorage.getItem('refresh_token')}, this.CONTEXT
        ).pipe(
          tap((res: LoginResponse) => this.storeTokens(res as LoginSuccess))
        );
      }
    
    storeTokens(data: LoginSuccess): void {
        localStorage.setItem('token', data.token);
        localStorage.setItem('refresh_token', data.refresh_token);
        this.scheduleTokenRefresh(data.token);
      }

这是我需要用户数据的组件(它不是唯一需要此数据的组件):

export class HomeComponent implements OnInit {
  user!: User | null;
  constructor(private authSvc: AuthService) {
  }

  ngOnInit() {
    this.user = this.authSvc.user;
  }

我面临的问题是 Home 组件在 storeTokens 被调用之前显示,因此如果自最后一个令牌以来用户数据已在后端更新,我的 HomeComponent 将不知道它,因为它将使用前一个令牌...

我尝试使用解析器错误,它迫使我再次调用refreshToken,但这不是我想要的。我需要将刷新令牌保留在 AppComponent 中,而不是调用它两次..这是解析器:

export const tokenRefreshedResolver: ResolveFn<boolean> = (route, state) => {
  return inject(AuthService).refreshToken().pipe(
    map(() => true),
    catchError(() => of(false))
  );
};

什么是合适的解决方案?

angular typescript authentication jwt refresh-token
1个回答
0
投票

我强烈建议您使用 APP_INITIALIZER 提供程序来处理此问题。如果您不熟悉它,这里是文档,这是我的简短解释。

APP_INITIALIZER 可用于在应用程序引导之前执行所需的功能。这意味着您应该能够在任何内容开始渲染之前获取令牌,从而避免出现问题。这比在 AppComponent 中处理要好得多。

您需要在 AppModule 中添加 APP_INITIALIZER 提供程序,然后按照您想要的方式实现它。常见的解决方案是使用工厂函数。

// AppModule import { APP_INITIALIZER, NgModule } from '@angular/core'; import { AuthService } from 'somewhere/in/you/project'; @NgModule({ declarations: [ AppComponent, AboutUsComponent,HomeComponent,ContactUsComponent ], imports: [ HttpClientModule, BrowserModule, AppRoutingModule, ], // Add a custom provider providers: [ { provide: APP_INITIALIZER, useFactory: appInit, // function returning a Promise or Observable deps: [AuthService] // dependencies needed for the factory func } ], bootstrap: [AppComponent] }) export class AppModule { }
实现工厂功能。将其放入单独的文件中或作为 NgModule 声明上方的函数。主要取决于它能达到多大。

// Factory method file import { AuthService } from 'somewhere/in/you/project'; // Deps defined in AppModule reflect into the parameters of your factory function. // You can add more if needed. export function appInit( authService: AuthService, ): () => Observable<boolean> { return authService.refreshToken().pipe( map(response => !!response) // Map it in some way that it returns boolean. // Check if response exists, or contains some property, etc. ) }
大多数 AUTH 库都推荐这种方法。我已经将它与 KeyCloak 和 Azure 一起使用,用户体验非常流畅。但您可能会遇到的情况是,加载时间有点长。但直接在 

index.html 中实现一些加载屏幕非常简单

© www.soinside.com 2019 - 2024. All rights reserved.