12

我在应用程序中有 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:添加警卫代码

身份验证:

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

#注意: _dataService是存储来自 AuthGuard 的访问权限的 GlobalService。

4

4 回答 4

23

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

我有一个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)),
        );
}
于 2018-09-07T22:30:57.170 回答
5

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

编辑:添加代码片段以便更好地理解。

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


解决方案

这个想法是创建一个主守卫并让主守卫处理其他守卫的执行。

在这种情况下,路由配置将包含master guard 作为唯一的 guard

要让主守卫知道要为特定路线触发的守卫,请dataRoute.

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

挑战

这种方法的挑战之一是重构现有的路由模型。但是,它可以部分完成,因为更改是非破坏性的。

我希望这有帮助。

于 2017-12-07T15:17:12.317 回答
3

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

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

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

你需要一个解析器。”

于 2017-05-23T13:02:21.940 回答
2

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

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 中提供(理解可注入的)防护。

于 2019-11-13T14:20:39.417 回答