Мне нужны соответствующие предложения для удовлетворения моей потребности, а именно:
Я реализовал систему аутентификации на основе jwt в своем угловом приложении. Когда я получаю свой новый токен и токен обновления, я создаю setTimeout, чтобы токен можно было обновить до истечения срока его действия. Но я также намеренно обновляю свой токен при загрузке приложения (в моем AppComponent), и моя проблема связана с этим случаем.
Я получаю информацию о вошедшем в систему пользователе из jwt, и проблема в том, что когда приложение загружается и выполняется обновление токена, токены сохраняются после отображения компонента, что приводит к тому, что приложение получает информацию о пользователях. предыдущий токен, а не вновь созданный.
Мы могли бы подумать об использовании преобразователей, но проблема в том, что при использовании преобразователя у меня будут запускаться два запроса токена обновления: один в моем 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, но это не то, что я хочу. Мне нужно сохранить мойrefreToken внутри AppComponent и не вызывать его дважды. Вот преобразователь:
export const tokenRefreshedResolver: ResolveFn<boolean> = (route, state) => {
return inject(AuthService).refreshToken().pipe(
map(() => true),
catchError(() => of(false))
);
};
Каким будет подходящее решение?





Я настоятельно рекомендую вам использовать поставщика APP_INITIALIZER для решения этой проблемы. Если вы с ним не знакомы, вот документация и вот мое краткое объяснение.
APP_INITIALIZER можно использовать для выполнения желаемых функций до загрузки приложения. Это означает, что вы сможете получить токен до того, как что-либо начнет отображаться, что позволит избежать вашей проблемы. Это намного лучше, чем обрабатывать его в AppComponent.
Вам необходимо добавить поставщика APP_INITIALIZER в AppModule, а затем реализовать его так, как вы хотите. Распространенным решением является использование для этого фабричной функции.
// 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));
Реализовать фабричную функцию. Поместите его в отдельный файл или как функцию над объявлением 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> {
// 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.
Простите, извините, я по ошибке удалил одну строчку. Я отредактировал свой ответ, исправил проблему и предоставил код для автономного приложения.
Что ж, теперь, используя только authService.refreshToken() внутри возврата, он работает как шарм и решает мою проблему! Спасибо
Похоже, это может быть решением моей проблемы, но есть проблема с реализацией, прежде всего, когда я сохраняю подпись appInit
: () => Observable<boolean>, я получаю эту ошибкуType Observable<boolean> is not assignable to type () => Observable<boolean>, а когда я ее удаляю, я получаю эту ошибку в консолиTypeError: appInits is not a function. И, кстати, я использую автономное приложение, но у меня оно соответственно