【问题标题】:Angular 4 Multiple Guards - Execution SequenceAngular 4 Multiple Guards - 执行顺序
【发布时间】:2017-10-09 03:38:29
【问题描述】:

我在应用程序中有 2 个警卫,AuthGuard 和 AccessGuard。 AuthGuard顾名思义就是保护所有页面,把session对象存储在GlobalService中,AccessGuard依赖于AuthGuard在GlobalService中存储的session对象中的一些访问数据。

当 AuthGuard 返回一个 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 angular-routing angular-router-guards


    【解决方案1】:

    看看这个 Angular 指南 (link)。 "如果您使用的是真实世界的 API,在从服务器返回要显示的数据之前可能会有一些延迟。您不想在等待数据时显示空白组件。

    最好从服务器预先获取数据,以便在激活路由时准备好。这也允许您在路由到组件之前处理错误...

    总而言之,您希望延迟渲染路由组件,直到获取所有必要的数据。

    你需要一个解析器。”

    【讨论】:

    • 嘿,谢谢。但 CanActivateChild 对我来说很有魅力。
    • @AkulNarang 你是如何使用 CanActivate 来解决这个问题的?似乎这是 Guards 的一个很常见的问题,没有很好的解决方法。真的不想在需要这种类型的保护的每条路由上都放置一个解析器。
    • 您使用的是哪个版本的 Angular?
    【解决方案2】:

    使用 Master Guard 来触发应用程序守卫可以解决问题。

    编辑:添加代码 sn-p 以便更好地理解。

    我遇到了类似的问题,这就是我解决它的方法 -


    解决方案

    想法是创建一个ma​​ster guard,让master guard来处理其他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);
        }
    }
    

    挑战

    这种方法的挑战之一是重构现有的路由模型。但是,由于更改不会中断,因此可以部分完成。

    我希望这会有所帮助。

    【讨论】:

    • 这很好,但是如果我们需要两个守卫都通过 true 怎么办?我实现了这个解决方案,只要 one 的守卫返回 true,就可以激活路由,这有点否定了拥有多个守卫的想法。
    • 我认为您遗漏了一些东西,这种方法的工作原理类似于AND 条件,因此如果任何警卫失败,路线将不会受到打击。如果你看到executeGuards 方法,它只会在前一个守卫通过truepromise&lt;true&gt; 时被递归调用
    【解决方案3】:

    我选择了一条不同的路径 --- 嵌套我的守卫并使它们相互依赖。

    我有一个RequireAuthenticationGuard 和一个RequirePermissionGuard。对于大多数路线,它们都需要同时运行,但我需要一个特定的顺序。

    RequireAuthenticationGuard 依赖我的身份验证服务来检查当前会话是否经过身份验证。

    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)),
            );
    }
    

    【讨论】:

    • 不得不说这可能是最优雅的方式。
    • 我试过了,但是在 authGuardService 完成之前就调用了 permissionGuard 服务。
    • 似乎如果你像这样链接它们,你不应该把 RequireAuthenticatedGuard 放在 CanActivate 数组中。如果你这样做,它将被调用两次。一次由路由器,一次由权限守卫。
    • @Geo242 这些方法被调用多少次并不重要。重要的是,它们是昂贵的电话吗?如果是这样,则可以缓存结果。此外,如果 AuthN/AuthZ 状态的源是热可观察的,那将是理想的。这样就可以在不触发任何昂贵操作的情况下读取它。
    【解决方案4】:

    只需创建一个注入子守卫的主守卫,这里是一个例子:

    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,
            ];
        }
    }
    

    然后在你的 app-routing.module.ts 中

    const routes: Routes = [
        {
            path: 'page',
            loadChildren: './pages.module#PageModule',
            canActivate: [AppGuard],
        }
    ];
    

    当然,您必须管理您的模块,以便在您的 AppGuard 中提供(理解可注入的)防护。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-19
      • 1970-01-01
      • 1970-01-01
      • 2017-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-18
      相关资源
      最近更新 更多