У меня есть маршрут с двумя охранниками canActivate
(AuthGuard
и RoleGuard
). Первая (AuthGuard
) проверяет, вошел ли пользователь в систему, а если нет, перенаправляется на страницу входа. Второй проверяет, имеет ли пользователь определенную роль, которая разрешена для просмотра страницы, а если нет, перенаправляет на неавторизованную страницу.
canActivate: [ AuthGuard, RoleGuard ]
...
export class AuthGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
...
this.router.navigate(['/login']);
resolve(false);
}
export class RoleGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
...
this.router.navigate(['/unauthorized']);
resolve(false);
}
Проблема в том, что когда я получаю доступ к маршруту, и я не вошел в систему, я нажимаю AuthGuard
, который терпит неудачу и говорит маршрутизатору перейти к /login
. Однако, несмотря на то, что AuthGuard
не удалось, RoleGuard
работает в любом случае, а затем переходит на /unauthorized
.
По-моему, бессмысленно запускать следующего охранника, если первое не удается. Есть ли способ обеспечить соблюдение этого поведения?
Ответ 1
Это связано с тем, что вы возвращаете Promise<boolean>
вместо просто boolean
. Если вы просто вернете логическое значение, он не проверит RoleGuard
. Я думаю, это либо ошибка в angular2
, либо ожидаемый результат асинхронных запросов.
Однако вы можете решить эту проблему с помощью своего примера, используя только RoleGuard
для URL-адресов, для которых требуется определенный Role
, потому что я полагаю, что вы должны войти в систему, чтобы иметь роль. В этом случае вы можете изменить свой RoleGuard
на это:
@Injectable()
export class RoleGuard implements CanActivate {
constructor(private _authGuard: AuthGuard) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
return this._authGuard.canActivate(route, state).then((auth: boolean) => {
if(!auth) {
return false;
}
//... your role guard check code goes here
});
}
}
Ответ 2
Я был в той же ситуации несколько недель назад, поэтому решил написать несколько "хакерских" решений для достижения этого, используя атрибут data
объекта определения маршрута.
Идея состоит в том, чтобы определить вашу собственную охрану (назовем ее MainGuard
), которая будет читать ваш атрибут данных (например, guardsList
) с массивом охранников внутри функции MainGuard.canActivate
и выполнять их (вызывая guardsList[i].canActive
) один за другим в цикле. Вам нужно будет использовать приложение Injector
, чтобы иметь возможность называть canActivate
для данного защитника.
Он немного усложняется, если вы хотите поддерживать Observable
, Promise
, а plain boolean
защищает все под одним массивом guardsList
, так как вам нужно подписаться и дождаться завершения и т.д.
Я реализовал этот "хак" как Angular Library, который позволяет вам сделать что-то вроде этого:
const appRoutes: Routes = [
{
...
canActivate: [SequentialGuards],
data: { GUARDS_SEQ: [Guard1, Guard2, Guard3] }
...
}]
В случае неудачи Guard1
Guard2
и Guard3
не будут вызываться.
Ответ 3
Как упоминалось в свойстве @PierreDuc data
в классе Route
, наряду с Master Guard, можно использовать для решения этой проблемы.
Проблема
Прежде всего, angular не поддерживает функцию вызова охранников в тандеме. Поэтому, если первая защита асинхронна и пытается выполнить вызовы ajax, все остальные охранники будут запущены даже до завершения запроса ajax в защите 1.
Я столкнулся с подобной проблемой, и именно так я ее решил -
Решение
Идея состоит в том, чтобы создать главный защитник и позволить главному охраннику обрабатывать выполнение других охранников.
Конфигурация маршрутизации в этом случае будет содержать master guard как единственный защитник.
Чтобы мастер-охранник узнал о срабатывании охранников для определенных маршрутов, добавьте свойство data
в Route
.
Свойство data
- это пара ключевых значений, которая позволяет нам связывать данные с маршрутами.
Затем данные могут быть доступны в охранниках с использованием параметра ActivatedRouteSnapshot
метода canActivate
в защите.
Решение выглядит сложным, но оно обеспечит надлежащую работу охранников после его интеграции в приложение.
В следующем примере объясняется этот подход -
Пример
1. Константы Объект, чтобы отобразить все защитные устройства -
export const GUARDS = {
GUARD1: "GUARD1",
GUARD2: "GUARD2",
GUARD3: "GUARD3",
GUARD4: "GUARD4",
}
2. Защита приложения -
import { Injectable } from "@angular/core";
import { Guard4DependencyService } from "./guard4dependency";
@Injectable()
export class Guard4 implements CanActivate {
//A guard with dependency
constructor(private _Guard4DependencyService: Guard4DependencyService) {}
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
return new Promise((resolve: Function, reject: Function) => {
//logic of guard 4 here
if (this._Guard4DependencyService.valid()) {
resolve(true);
} else {
reject(false);
}
});
}
}
3. Конфигурация маршрутизации -
import { Route } from "@angular/router";
import { View1Component } from "./view1";
import { View2Component } from "./view2";
import { MasterGuard, GUARDS } from "./master-guard";
export const routes: Route[] = [
{
path: "view1",
component: View1Component,
//attach master guard here
canActivate: [MasterGuard],
//this is the data object which will be used by
//masteer guard to execute guard1 and guard 2
data: {
guards: [
GUARDS.GUARD1,
GUARDS.GUARD2
]
}
},
{
path: "view2",
component: View2Component,
//attach master guard here
canActivate: [MasterGuard],
//this is the data object which will be used by
//masteer guard to execute guard1, guard 2, guard 3 & guard 4
data: {
guards: [
GUARDS.GUARD1,
GUARDS.GUARD2,
GUARDS.GUARD3,
GUARDS.GUARD4
]
}
}
];
4. Мастер Гвардии -
import { Injectable } from "@angular/core";
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from "@angular/router";
//import all the guards in the application
import { Guard1 } from "./guard1";
import { Guard2 } from "./guard2";
import { Guard3 } from "./guard3";
import { Guard4 } from "./guard4";
import { Guard4DependencyService } from "./guard4dependency";
@Injectable()
export class MasterGuard implements CanActivate {
//you may need to include dependencies of individual guards if specified in guard constructor
constructor(private _Guard4DependencyService: Guard4DependencyService) {}
private route: ActivatedRouteSnapshot;
private state: RouterStateSnapshot;
//This method gets triggered when the route is hit
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
this.route = route;
this.state = state;
if (!route.data) {
Promise.resolve(true);
return;
}
//this.route.data.guards is an array of strings set in routing configuration
if (!this.route.data.guards || !this.route.data.guards.length) {
Promise.resolve(true);
return;
}
return this.executeGuards();
}
//Execute the guards sent in the route data
private executeGuards(guardIndex: number = 0): Promise<boolean> {
return this.activateGuard(this.route.data.guards[guardIndex])
.then(() => {
if (guardIndex < this.route.data.guards.length - 1) {
return this.executeGuards(guardIndex + 1);
} else {
return Promise.resolve(true);
}
})
.catch(() => {
return Promise.reject(false);
});
}
//Create an instance of the guard and fire canActivate method returning a promise
private activateGuard(guardKey: string): Promise<boolean> {
let guard: Guard1 | Guard2 | Guard3 | Guard4;
switch (guardKey) {
case GUARDS.GUARD1:
guard = new Guard1();
break;
case GUARDS.GUARD2:
guard = new Guard2();
break;
case GUARDS.GUARD3:
guard = new Guard3();
break;
case GUARDS.GUARD4:
guard = new Guard4(this._Guard4DependencyService);
break;
default:
break;
}
return guard.canActivate(this.route, this.state);
}
}
Вызовы
Одной из проблем в этом подходе является реорганизация существующей модели маршрутизации. Однако это может быть сделано частями, поскольку изменения не нарушаются.
Надеюсь, это поможет.
Ответ 4
Я не нашел лучшего решения в Интернете, но, используя в качестве путеводителя лучший ответ, я решил использовать только один защитник, включая оба конкатенированных запроса с использованием Rxjs mergeMap, чтобы избежать дублирования вызовов на одну и ту же конечную точку.
В этом примере, избегайте console.log, если вы хотите, я использовал его, чтобы быть уверенным в том, что было вызвано первым.
1 getCASUsername вызывается для аутентификации пользователя (heres console.log(1), который вы не видите)
2 У нас есть имя пользователя
3 Здесь я делаю второй запрос, который будет запускаться после первого, используя ответ (true)
4 Используя возвращаемое имя пользователя, я получаю роли для этого пользователя
У меня есть решение для последовательности вызовов и для избежания дублированных вызовов. Возможно, это может сработать для вас.
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private AuthService : AuthService,
private AepApiService: AepApiService) {}
canActivate(): Observable<boolean> {
return this.AepApiService.getCASUsername(this.AuthService.token)
.map(res => {
console.log(2, 'userName');
if (res.name) {
this.AuthService.authenticateUser(res.name);
return true
}
})
.mergeMap( (res) => {
console.log(3, 'authenticated: ' + res);
if (res) {
return this.AepApiService.getAuthorityRoles(this.AuthService.$userName)
.map( res => {
console.log(4, 'roles');
const roles = res.roles;
this.AuthService.$userRoles = roles;
if (!roles.length) this.AuthService.goToAccessDenied();
return true;
})
.catch(() => {
return Observable.of(false);
});
} else {
return Observable.of(false);
}
})
.catch(():Observable<boolean> => {
this.AuthService.goToCASLoginPage();
return Observable.of(false);
});
}
}
Ответ 5
В настоящее время существует несколько асинхронных охранников (возвращающих Promise или Observable) одновременно. Я открыл для этого проблему: https://github.com/angular/angular/issues/21702
Другим обходным решением описанного выше решения является использование вложенных маршрутов:
{
path: '',
canActivate: [
AuthGuard,
],
children: [
{
path: '',
canActivate: [
RoleGuard,
],
component: YourComponent
// or redirectTo
// or children
// or loadChildren
}
]
}