将参数传递给路线保护员

时间:2017-03-10 13:22:43

标签: angular typescript angular2-routing

我正在开发一个具有很多角色的应用,我需要使用警卫根据这些角色阻止导航到部分应用。我意识到我可以为每个角色创建单独的防护类,但宁愿有一个我可以以某种方式传递参数的类。换句话说,我希望能够做类似的事情:

{ 
  path: 'super-user-stuff', 
  component: SuperUserStuffComponent,
  canActivate: [RoleGuard.forRole('superUser')]
}

但是,既然你传递的只是你后卫的类型名称,就不能想办法。我是否应该咬紧牙关并为每个角色编写单独的防护等级,而不是使用单一的参数化类型来打破我的优雅幻想?

4 个答案:

答案 0 :(得分:123)

你必须这样做。

而是使用forRole(),您应该使用:

{ 
   path: 'super-user-stuff', 
   component: SuperUserStuffComponent,
   canActivate: RoleGuard,
   data: {roles: ['SuperAdmin', ...]}
}

并在您的RoleGuard中使用它

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot)
    : Observable<boolean> | Promise<boolean> | boolean  {

    let roles = route.data.roles as Array<string>;
    ...
}

答案 1 :(得分:5)

以下是我对此的看法,以及针对缺少的提供商问题的可能解决方案。

在我的情况下,我们有一个守护程序,它将权限或权限列表作为参数,但是它具有相同的功能。

我们有一个课程,无论是否经过许可,都可以处理授权警卫:

@Injectable()
export class AuthGuardService implements CanActivate {

    checkUserLoggedIn() { ... }

这涉及检查用户活动会话等。

它还包含一个用于获取自定义权限保护的方法,实际上取决于AuthGuardService本身

static forPermissions(permissions: string | string[]) {
    @Injectable()
    class AuthGuardServiceWithPermissions {
      constructor(private authGuardService: AuthGuardService) { } // uses the parent class instance actually, but could in theory take any other deps

      canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
        // checks typical activation (auth) + custom permissions
        return this.authGuardService.canActivate(route, state) && this.checkPermissions();
      }

      checkPermissions() {
        const user = ... // get the current user
        // checks the given permissions with the current user 
        return user.hasPermissions(permissions);
      }
    }

    AuthGuardService.guards.push(AuthGuardServiceWithPermissions);
    return AuthGuardServiceWithPermissions;
  }

这允许我们使用该方法在路由模块中基于permissions参数注册一些自定义防护:

....
{ path: 'something', 
  component: SomeComponent, 
  canActivate: [ AuthGuardService.forPermissions('permission1', 'permission2') ] },

forPermission的有趣部分是AuthGuardService.guards.push - 这基本上确保调用任何时间forPermissions来获取自定义防护类,它也会将它存储在此数组中。这在主类上也是静态的:

public static guards = [ ]; 

然后我们可以使用这个数组来注册所有守卫 - 这是好的,只要我们确保在app模块注册这些提供者时,已经定义了路由并且已经创建了所有防护类(例如,检查导入顺序并在列表中保持这些提供程序尽可能低 - 有路由模块帮助):

providers: [
    // ...
    AuthGuardService,
    ...AuthGuardService.guards,
]

希望这有帮助。

答案 2 :(得分:1)

另一种将方法与 data 和工厂函数结合起来的选项:

export function canActivateForRoles(roles: Role[]) {
  return {data: {roles}, canActivate: [RoleGuard]}
}

export class RoleGuard implements CanActivate {
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot)
      : Observable<boolean> | Promise<boolean> | boolean  {
  
      const roles = route.data.roles as Role[];
    ...
  }
}

...

{ path: 'admin', component: AdminComponent, ...canActivateWithRoles([Role.Admin]) },

答案 3 :(得分:0)

@ AluanHaddad的解决方案是给予&#34;没有提供商&#34;错误。这是一个解决方案(它感觉很脏,但我缺乏制作更好的技能)。

从概念上讲,我作为提供者注册由roleGuard创建的每个动态生成的类。

因此,对于所检查的每个角色:

canActivate: [roleGuard('foo')]

你应该:

providers: [roleGuard('foo')]

但是,即使roleGuard参数相同,@ AluanHaddad的解决方案as-is也会为每次roles调用生成新类。使用lodash.memoize看起来像这样:

export var roleGuard = _.memoize(function forRole(...roles: string[]): Type<CanActivate> {
    return class AuthGuard implements CanActivate {
        canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
            Observable<boolean>
            | Promise<boolean>
            | boolean {
            console.log(`checking access for ${roles.join(', ')}.`);
            return true;
        }
    }
});

注意,每个角色组合都会生成一个新类,因此您需要注册为提供者每个角色组合。即如果你有:

canActivate: [roleGuard('foo')]canActivate: [roleGuard('foo', 'bar')]您必须同时注册:providers[roleGuard('foo'), roleGuard('foo', 'bar')]

更好的解决方案是在roleGuard内的全局提供商集合中自动注册提供商,但正如我所说,我缺乏实现这一点的技能。

相关问题