我需要相关的建议来满足我的需求,即:

我在我的angular应用程序中实现了一个基于jWT的身份验证系统.当我获得新的令牌和刷新令牌时,我创建一个setTimeout,以便令牌可以在到期前刷新.但当我 bootstrap 应用程序(在我的AppComponents中)时,我也故意刷新我的 token ,我的问题与这种情况有关.

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

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

所以基本上,这是我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被调用之前就已显示,因此,如果用户数据自上一个令牌以来已在后台更新,那么我的HomeComponents将不会知道它,因为它将使用之前的令牌..

我try 使用解析器错误,它迫使我再次调用refreshToken,但这不是我想要的.我需要将我的refreshToken保存在AppComponents中,并且不要调用它两次.这是解决方案:

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

合适的解决方案是什么?

推荐答案

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

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

您需要将APP_INITIALIZER提供程序添加到AppMode中,然后以您想要的方式实现它.常见的解决方案是使用工厂功能来实现这一目标.

// 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 { }


// FOR STANDALONE APPLICATION
// to it in bootstrapApplication instead of the AppModule
bootstrapApplication(AppComponent, {
  providers: [
    // ... 
    {
      provide: APP_INITIALIZER,
      useFactory: appInit,
      multi: true,
      deps: [AuthService],
    },
  ],
}).catch(err => console.error(err));

实施工厂职能.将其放入单独的文件中或作为NgMode声明上方的函数.主要取决于它能变得有多大.

// 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> {
  // Since return type is a function, we need to return this way.
  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中实现一些加载屏幕非常简单

Typescript相关问答推荐

如果存在一处房产,请确保存在其他房产

具有映射返回类型的通用设置实用程序

TypScript如何在 struct 上定义基元类型?

TypScript ' NoInfer '类型未按预期工作

如何使用axios在前端显示错误体

React router 6.22.3不只是在生产模式下显示,为什么?

属性的类型,该属性是类的键,其值是所需类型

在配置对象上映射时,类型脚本不正确地推断函数参数

Angular 行为:属性类型从SignalFunction更改为布尔

如何将联合文字类型限制为文字类型之一

如何正确地对类型脚本泛型进行限制

为什么TypeScript假设文档对象始终可用?

如何使用Zod使一个基于其他字段值的字段为必填字段?

对扩展某种类型的属性子集进行操作的函数

Angular 15使用react 式表单在数组内部创建动态表单数组

TypeScrip:来自先前参数的参数中的计算(computed)属性名称

下载量怎么会超过下载量?

如何缩小函数的返回类型?

在tabel管理区域react js上设置特定顺序

在ts中获取级联子K类型?