Вопрос или проблема
У меня есть маршруты “https://stackoverflow.com/” и ‘/auth’. Логика заключается в том, что пользователи, не вошедшие в систему, могут получить доступ ТОЛЬКО к странице /auth, в то время как вошедшие в систему пользователи НЕ МОГУТ получить к ней доступ.
Итак, я создал два охранника и по какой-то причине получаю бесконечный цикл между ними.
Я настроил маршруты следующим образом:
app.routes.ts:
{
path: 'auth',
canActivate: [OnlyUnauthorizedGuard],
children: [
{ path: '', component: AuthComponent, pathMatch: 'full' },
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent },
]
},
{
path: '',
data: {
allowedRoleIDs: [UserRole.Customer, UserRole.Owner, UserRole.Admin, UserRole.Root]
},
canActivate: [RoleGuard],
component: MainComponent,
},
Мои охранники:
role.guard.ts:
export class RoleGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
console.log('Сработал RoleGuard');
const allowedRoles = route.data['allowedRoleIDs'] as number[];
return this.authService.user$.pipe(
map(user => {
console.log('пользователь', user);
if (user === null) {
console.log('Перенаправление на /auth, так как пользователь равен null');
this.router.navigate(['/auth']);
return false;
}
if (user && !allowedRoles.includes(user.roleID)) {
console.log('Перенаправление на /404');
this.router.navigate(['/404']);
return false;
}
return true;
})
);
}
}`
onlyUnauthorized.guard.ts:
export class OnlyUnauthorizedGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): Observable<boolean> {
console.log('Сработал OnlyUnauthorizedGuard');
return this.authService.user$.pipe(
take(1),
map(user => {
console.log('пользователь ', user);
if (user) {
console.log('Пользователь вошел в систему, перенаправление на /');
this.router.navigate(["https://stackoverflow.com/"]);
return false;
}
console.log('Разрешено');
return true;
})
);
}
}
`
Как я получаю данные пользователя:
auth.service.ts:
export class AuthService {
private _userSubject = new BehaviorSubject<User | null>(null);
user$ = this._userSubject.asObservable();
constructor(
private http: HttpClient
) {
this.loadUser();
}
private loadUser() {
this._loadingSubject.next(true);
this.http.get<User>('/auth/getUser').pipe(
tap(user => {
this._userSubject.next(user)
console.log(user)
}),
catchError(() => {
this._userSubject.next(null);
return of(null);
}),
finalize(() => this._loadingSubject.next(false))
).subscribe();
}
get user() {
return this._userSubject.value;
}
}`
На странице у меня есть странная ошибка:
PM [vite] Внутренняя ошибка сервера: Страница /auth/getUser не отобразилась за 30 секунд.
Я не вижу запроса к /auth/getUser на вкладке сети в браузере.
Есть такие логи в Docker при доступе к странице /auth (вкладка консоли в браузере не показывает логов) :
`
2024-10-28 00:26:06 Сработал RoleGuard
2024-10-28 00:26:06 пользователь null
2024-10-28 00:26:06 Перенаправление на /auth, так как пользователь равен null
2024-10-28 00:26:06 Сработал OnlyUnauthorizedGuard
2024-10-28 00:26:06 пользователь null
2024-10-28 00:26:06 Разрешено
2024-10-28 00:26:06 Сработал RoleGuard
...`
Кажется, что по какой-то причине, даже если OnlyUnauthorizedGuard возвращает true, происходит перенаправление на /.
Если пользователь равен null и вы получаете доступ к корневому пути /, то RoleGuard перенаправляет на /auth, а затем, когда это разрешено, пытается перенаправить обратно на / снова, что вызывает бесконечный цикл.
Настройте охранников, чтобы предотвратить появление цикла. Используйте take(1) в обоих охранниках, чтобы убедиться, что они корректно завершают цепочку обсервации, затем убедитесь, что пользователь загружен, прежде чем срабатывать охранники. Рассмотрите возможность инициализации состояния пользователя как undefined и обработки логики, когда оно разрешено в null или объект пользователя, и убедитесь, что вы не используете навигацию непосредственно внутри охранников. Вместо этого возвращайте дерево URL.
RoleGuard
export class RoleGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> | UrlTree {
console.log('Сработал RoleGuard');
const allowedRoles = route.data['allowedRoleIDs'] as number[];
return this.authService.user$.pipe(
take(1),
map(user => {
console.log('пользователь', user);
if (user === null) {
return this.router.createUrlTree(['/auth']);
}
if (user && !allowedRoles.includes(user.roleID)) {
return this.router.createUrlTree(['/404']);
}
return true;
})
);
}
}
OnlyUnauthorizedGuard
export class OnlyUnauthorizedGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): Observable<boolean> | UrlTree {
console.log('Сработал OnlyUnauthorizedGuard');
return this.authService.user$.pipe(
take(1),
map(user => {
console.log('пользователь ', user);
if (user) {
return this.router.createUrlTree(["https://stackoverflow.com/"]);
}
return true;
})
);
}
}
Попробуйте добавить состояние загрузки, чтобы обеспечить обратную связь с пользовательским интерфейсом, пока данные пользователя загружаются.
Ответ или решение
Вопрос, который вы поставили, касается взаимодействия двух охранников (guards) в Angular, что приводит к бесконечному циклу перенаправления. Позвольте подробно разобрать эту проблему с учетом ваших маршрутов и логики охранников.
Описание проблемы
Ваше приложение имеет два охранника: RoleGuard
, который разрешает доступ только авторизованным пользователям, и OnlyUnauthorizedGuard
, который разрешает доступ только неавторизованным пользователям. Ваша логика определяет, что неавторизованные пользователи должны иметь доступ только к маршруту /auth
. Данная ситуация может привести к бесконечному циклу перенаправления при определенных условиях.
Логика охранников
-
RoleGuard
:- Проверяет, авторизован ли пользователь. Если
user
равенnull
, он перенаправляет на/auth
. Еслиuser
существует, он проверяет разрешенные роли.
- Проверяет, авторизован ли пользователь. Если
-
OnlyUnauthorizedGuard
:- Проверяет, не авторизован ли пользователь. Если
user
существует, он перенаправляет наhttps://stackoverflow.com/
. Еслиuser
равенnull
, то доступ разрешается.
- Проверяет, не авторизован ли пользователь. Если
Анализ
Когда пользователь пытается получить доступ к корневому маршруту (/
), RoleGuard
срабатывает первым и проверяет, является ли пользователь авторизованным. Поскольку user
равен null
, он перенаправляет на /auth
. Теперь, когда пользователь находится на /auth
, срабатывает OnlyUnauthorizedGuard
, который проверяет, не авторизован ли пользователь, и, поскольку user
по-прежнему равен null
, доступ разрешается. Однако, если в этот момент пользователь пытается получить доступ к защищенному маршруту, RoleGuard
опять срабатывает, приводя к перенаправлению на /auth
, и мы попадаем в бесконечный цикл.
Как избежать бесконечного перенаправления
Чтобы решить проблему, можно внести изменения в каждую из охранников, чтобы предотвратить бесконечные перенаправления и корректно обрабатывать состояния пользователя. Рекомендации по изменению:
-
Использование
take(1)
: Это обеспечит завершение цепочки наблюдения (observable) и предотвратит повторные вызовы при каждом изменении состояния. -
Возврат
UrlTree
: Вместо выполнения перенаправления внутри охранников, возвращайтеUrlTree
, что позволяет лучше управлять маршрутами и минимизировать риск создания циклов.
Пример исправленных охранников
RoleGuard
export class RoleGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot): Observable<boolean | UrlTree> {
console.log('Triggered RoleGuard');
const allowedRoles = route.data['allowedRoleIDs'] as number[];
return this.authService.user$.pipe(
take(1),
map(user => {
console.log('user', user);
if (user === null) {
return this.router.createUrlTree(['/auth']);
}
if (user && !allowedRoles.includes(user.roleID)) {
return this.router.createUrlTree(['/404']);
}
return true;
})
);
}
}
OnlyUnauthorizedGuard
export class OnlyUnauthorizedGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): Observable<boolean | UrlTree> {
console.log('Triggered OnlyUnauthorizedGuard');
return this.authService.user$.pipe(
take(1),
map(user => {
console.log('user', user);
if (user) {
return this.router.createUrlTree(["https://stackoverflow.com/"]);
}
return true;
})
);
}
}
Заключение
Внедрив указанные изменения, вы сможете избежать бесконечного цикла перенаправления и улучшить взаимодействие охранников. Также следует следить за состоянием загрузки для пользовательского интерфейса, чтобы пользователи знали, что данные загружаются. Успехов в разработке приложения!