Angular 4 多个守卫 - 执行顺序
Angular 4 Multiple Guards - Execution Sequence
我在应用程序中有 2 个守卫,AuthGuard 和 AccessGuard。
AuthGuard顾名思义保护所有页面,将会话对象存储在GlobalService中,而AccessGuard依赖于AuthGuard存储在GlobalService中的会话对象中的一些访问数据。
当 AuthGuard returns 一个 Observable 然后同时执行 AccessGuard 以检查尚未到达的会话对象并且代码中断时,问题就出现了。 是否有任何其他方法可以限制 AccessGuard 的执行直到会话对象到达或任何其他解决方法来打破这种竞争条件?
#Note 我没有将 AccessGuard 逻辑合并到 AuthGuard 中,因为只有一些路由需要检查访问权限,而所有其他路由都需要身份验证。例如,帐户页面和数据库页面可供所有人访问,但用户管理和仪表板需要来自会话对象的外部访问参数
export const routes: Routes = [
{
path: 'login',
loadChildren: 'app/login/login.module#LoginModule',
},
{
path: 'logout',
loadChildren: 'app/logout/logout.module#LogoutModule',
},
{
path: 'forget',
loadChildren: 'app/forget/forget.module#ForgetModule',
},{
path: 'reset',
loadChildren: 'app/reset/reset.module#ResetModule',
},
path: 'pages',
component: Pages,
children: [
{ path: '', redirectTo: 'db', pathMatch: 'full' },
{ path: 'db', loadChildren: 'app/pages/db/db.module#DbModule' },
{ path: 'bi', loadChildren: 'app/pages/dashboard/dashboard.module#DashboardModule', canActivate:[AccessableGuard] },
{ path: 'account', loadChildren: 'app/pages/account/account.module#AccountModule' },
{ path: 'um', loadChildren: 'app/pages/um/um.module#UserManagementModule', canActivate:[AccessableGuard] },
],
canActivate: [AuthGuard]
}
];
export const routing: ModuleWithProviders = RouterModule.forChild(routes);
#EDIT:添加保护代码
AuthGuard:
canActivate(route:ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean{
return new Observable<boolean>( observer => {
this._dataService.callRestful('POST', params.SERVER.AUTH_URL + urls.AUTH.GET_SESSION).subscribe(
(accessData) => {
if (accessData['successful']) {
observer.next(true);
observer.complete();
console.log("done");
}
else {
observer.next(false);
observer.complete();
}
});
});
}
AccessableGuard:
canActivate(route:ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean{
if(this._dataService.getModulePermission(route.routeConfig.path.toUpperCase()) < 2){
return false;
}
return true;
}
#NOTE: _dataService 是存储来自 AuthGuard 的访问权限的 GlobalService。
看看这个 Angular 指南 (link)。
“如果您使用的是真实世界 API,在从服务器返回要显示的数据之前可能会有一些延迟。您不希望在等待数据时显示空白组件。
最好从服务器预取数据,以便在路由激活时准备就绪。这也允许您在路由到组件之前处理错误...
总而言之,您想延迟渲染路由组件,直到获取所有必要的数据。
你需要一个解析器。"
使用 Master Guard 来解雇应用程序守卫可以做到这一点。
EDIT : Adding the code snippet for better understanding.
我遇到了类似的问题,我就是这样解决的 -
解决方案
想法是创建一个守卫大师,让守卫大师处理其他守卫的执行。
在这种情况下,路由配置将包含 master 守卫作为唯一的守卫。
为了让master guard知道特定路由要触发的守卫,在Route
中添加一个data
属性。
data
属性 是一个键值对,允许我们将数据附加到路由。
然后可以使用守卫中 canActivate
方法的 ActivatedRouteSnapshot
参数在守卫中访问数据。
该解决方案看起来很复杂,但一旦集成到应用程序中,它将确保守卫正常工作。
以下示例解释了这种方法 -
例子
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);
}
}
挑战
这种方法的挑战之一是重构现有路由模型。但是,它可以部分完成,因为更改是非破坏性的。
希望对您有所帮助。
我选择了一条不同的道路——嵌套我的守卫并使它们相互依赖。
我有一个 RequireAuthenticationGuard
和一个 RequirePermissionGuard
。对于大多数路线,他们都需要 运行 但我需要一个特定的顺序。
RequireAuthenticationGuard
依赖于我的 authN 服务来检查当前会话是否已通过身份验证。
RequirePermissionGuard
依赖于我的 authZ 服务来检查当前会话是否已获得路由授权。
我将 RequireAuthenticationGuard
添加为 RequirePermissionGuard
的构造函数依赖项,并且仅在确定身份验证后才开始检查权限。
require-authentication.guard.ts
constructor(
private userSessionSerivce: UserSessionService) {}
canActivate(
_route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): Observable<boolean> {
return this.validateAuthentication(state.url);
}
require-permission.guard.ts
constructor(
private permissionService: PermissionService,
/**
* We use the RequireAuthenticationGuard internally
* since Angular does not provide ordered deterministic guard execution in route definitions
*
* We only check permissions once authentication state has been determined
*/
private requireAuthenticationGuard: RequireAuthenticatedGuard,
) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): Observable<boolean> {
const requiredPermissions: Permission[] = next.data.permissions || [];
return this.requireAuthenticationGuard
.canActivate(next, state)
.pipe(
mapTo(this.validateAuthorization(state.url, requiredPermissions)),
);
}
只需创建一个主守卫,其中一个注入子守卫,这里是一个例子:
app.guard.ts
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { GuardA } from '...';
import { GuardB } from '...';
@Injectable({
providedIn: 'root',
})
export class AppGuard implements CanActivate {
constructor(
// inject your sub guards
private guardA: GuardA,
private guardB: GuardB,
) {
}
public async canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
for (const guard of this.getOrderedGuards()) {
if (await guard.canActivate(next, state) === false) {
return false;
}
}
return true;
}
// -> Return here the sub guards in the right order
private getOrderedGuards(): CanActivate[] {
return [
this.guardA,
this.guardB,
];
}
}
然后在您的应用中-routing.module.ts
const routes: Routes = [
{
path: 'page',
loadChildren: './pages.module#PageModule',
canActivate: [AppGuard],
}
];
当然,您必须管理您的模块,以便为您的 AppGuard 提供(理解为可注入)守卫。
我在应用程序中有 2 个守卫,AuthGuard 和 AccessGuard。 AuthGuard顾名思义保护所有页面,将会话对象存储在GlobalService中,而AccessGuard依赖于AuthGuard存储在GlobalService中的会话对象中的一些访问数据。
当 AuthGuard returns 一个 Observable 然后同时执行 AccessGuard 以检查尚未到达的会话对象并且代码中断时,问题就出现了。 是否有任何其他方法可以限制 AccessGuard 的执行直到会话对象到达或任何其他解决方法来打破这种竞争条件?
#Note 我没有将 AccessGuard 逻辑合并到 AuthGuard 中,因为只有一些路由需要检查访问权限,而所有其他路由都需要身份验证。例如,帐户页面和数据库页面可供所有人访问,但用户管理和仪表板需要来自会话对象的外部访问参数
export const routes: Routes = [
{
path: 'login',
loadChildren: 'app/login/login.module#LoginModule',
},
{
path: 'logout',
loadChildren: 'app/logout/logout.module#LogoutModule',
},
{
path: 'forget',
loadChildren: 'app/forget/forget.module#ForgetModule',
},{
path: 'reset',
loadChildren: 'app/reset/reset.module#ResetModule',
},
path: 'pages',
component: Pages,
children: [
{ path: '', redirectTo: 'db', pathMatch: 'full' },
{ path: 'db', loadChildren: 'app/pages/db/db.module#DbModule' },
{ path: 'bi', loadChildren: 'app/pages/dashboard/dashboard.module#DashboardModule', canActivate:[AccessableGuard] },
{ path: 'account', loadChildren: 'app/pages/account/account.module#AccountModule' },
{ path: 'um', loadChildren: 'app/pages/um/um.module#UserManagementModule', canActivate:[AccessableGuard] },
],
canActivate: [AuthGuard]
}
];
export const routing: ModuleWithProviders = RouterModule.forChild(routes);
#EDIT:添加保护代码
AuthGuard:
canActivate(route:ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean{
return new Observable<boolean>( observer => {
this._dataService.callRestful('POST', params.SERVER.AUTH_URL + urls.AUTH.GET_SESSION).subscribe(
(accessData) => {
if (accessData['successful']) {
observer.next(true);
observer.complete();
console.log("done");
}
else {
observer.next(false);
observer.complete();
}
});
});
}
AccessableGuard:
canActivate(route:ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean{
if(this._dataService.getModulePermission(route.routeConfig.path.toUpperCase()) < 2){
return false;
}
return true;
}
#NOTE: _dataService 是存储来自 AuthGuard 的访问权限的 GlobalService。
看看这个 Angular 指南 (link)。 “如果您使用的是真实世界 API,在从服务器返回要显示的数据之前可能会有一些延迟。您不希望在等待数据时显示空白组件。
最好从服务器预取数据,以便在路由激活时准备就绪。这也允许您在路由到组件之前处理错误...
总而言之,您想延迟渲染路由组件,直到获取所有必要的数据。
你需要一个解析器。"
使用 Master Guard 来解雇应用程序守卫可以做到这一点。
EDIT : Adding the code snippet for better understanding.
我遇到了类似的问题,我就是这样解决的 -
解决方案
想法是创建一个守卫大师,让守卫大师处理其他守卫的执行。
在这种情况下,路由配置将包含 master 守卫作为唯一的守卫。
为了让master guard知道特定路由要触发的守卫,在Route
中添加一个data
属性。
data
属性 是一个键值对,允许我们将数据附加到路由。
然后可以使用守卫中 canActivate
方法的 ActivatedRouteSnapshot
参数在守卫中访问数据。
该解决方案看起来很复杂,但一旦集成到应用程序中,它将确保守卫正常工作。
以下示例解释了这种方法 -
例子
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);
}
}
挑战
这种方法的挑战之一是重构现有路由模型。但是,它可以部分完成,因为更改是非破坏性的。
希望对您有所帮助。
我选择了一条不同的道路——嵌套我的守卫并使它们相互依赖。
我有一个 RequireAuthenticationGuard
和一个 RequirePermissionGuard
。对于大多数路线,他们都需要 运行 但我需要一个特定的顺序。
RequireAuthenticationGuard
依赖于我的 authN 服务来检查当前会话是否已通过身份验证。
RequirePermissionGuard
依赖于我的 authZ 服务来检查当前会话是否已获得路由授权。
我将 RequireAuthenticationGuard
添加为 RequirePermissionGuard
的构造函数依赖项,并且仅在确定身份验证后才开始检查权限。
require-authentication.guard.ts
constructor(
private userSessionSerivce: UserSessionService) {}
canActivate(
_route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): Observable<boolean> {
return this.validateAuthentication(state.url);
}
require-permission.guard.ts
constructor(
private permissionService: PermissionService,
/**
* We use the RequireAuthenticationGuard internally
* since Angular does not provide ordered deterministic guard execution in route definitions
*
* We only check permissions once authentication state has been determined
*/
private requireAuthenticationGuard: RequireAuthenticatedGuard,
) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): Observable<boolean> {
const requiredPermissions: Permission[] = next.data.permissions || [];
return this.requireAuthenticationGuard
.canActivate(next, state)
.pipe(
mapTo(this.validateAuthorization(state.url, requiredPermissions)),
);
}
只需创建一个主守卫,其中一个注入子守卫,这里是一个例子:
app.guard.ts
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { GuardA } from '...';
import { GuardB } from '...';
@Injectable({
providedIn: 'root',
})
export class AppGuard implements CanActivate {
constructor(
// inject your sub guards
private guardA: GuardA,
private guardB: GuardB,
) {
}
public async canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
for (const guard of this.getOrderedGuards()) {
if (await guard.canActivate(next, state) === false) {
return false;
}
}
return true;
}
// -> Return here the sub guards in the right order
private getOrderedGuards(): CanActivate[] {
return [
this.guardA,
this.guardB,
];
}
}
然后在您的应用中-routing.module.ts
const routes: Routes = [
{
path: 'page',
loadChildren: './pages.module#PageModule',
canActivate: [AppGuard],
}
];
当然,您必须管理您的模块,以便为您的 AppGuard 提供(理解为可注入)守卫。