Angular 将参数传递给route guard

Angular 将参数传递给route guard,angular,typescript,angular2-routing,Angular,Typescript,Angular2 Routing,我正在开发一个应用程序,它有很多角色,我需要使用防护来阻止基于这些角色的应用程序部分导航。我意识到我可以为每个角色创建单独的守卫类,但我更希望有一个可以通过某种方式传递参数的类。换句话说,我希望能够做类似的事情: { path: 'super-user-stuff', component: SuperUserStuffComponent, canActivate: [RoleGuard.forRole('superUser')] } 但是,由于你传递的只是你的守卫的类型名称,所

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

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

但是,由于你传递的只是你的守卫的类型名称,所以我想不出一种方法来做到这一点。我是否应该咬紧牙关,为每个角色编写单独的保护类,打破我的优雅幻想,改用一个参数化类型?

而不是使用
forRole()
,您可以这样做:

{ 
   path: 'super-user-stuff', 
   component: SuperUserStuffComponent,
   canActivate: RoleGuard,
   data: {roles: ['SuperAdmin', ...]}
}
在你的角色守卫中使用这个

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

    let roles = route.data.roles as Array<string>;
    ...
}
canActivate(路由:ActivatedRouteSnapshot,状态:RouterStateSnashot)
:可观察的|承诺|布尔值{
让roles=route.data.roles作为数组;
...
}

@AluanHaddad的解决方案给出了“无提供者”错误。这里有一个解决办法(感觉很脏,但我缺乏做一个更好的技能)

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

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

canActivate: [roleGuard('foo')]
你应该:

providers: [roleGuard('foo')]
然而,@AluanHaddad的解决方案将为每次调用
roleGuard
生成新类,即使
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;
        }
    }
});
export var roleGuard=..memoize(角色函数(…角色:字符串[]):类型{
返回类AuthGuard实现了CanActivate{
canActivate(路由:ActivatedRouteSnapshot,状态:RouterStateSnashot):
可观察
|允诺
|布尔值{
log(`checking access for${roles.join(',')}.`);
返回true;
}
}
});
注意,每个角色组合都会生成一个新类,因此您需要将每个角色组合注册为提供者。即,如果您有:

canActivate:[roleGuard('foo')]
canActivate:[roleGuard('foo','bar')]
您必须同时注册两个:
提供者[roleGuard('foo')、roleGuard('foo','bar')]


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

以下是我对这一点的看法,以及缺少提供者问题的可能解决方案

在我的例子中,我们有一个以权限或权限列表为参数的守卫,但它也有一个角色

我们有一个处理授权守卫的类,无论是否经过许可:

@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;
  }
这允许我们使用该方法根据路由模块中的权限参数注册一些自定义防护:

....
{ path: 'something', 
  component: SomeComponent, 
  canActivate: [ AuthGuardService.forPermissions('permission1', 'permission2') ] },
forPermission
的有趣部分是
AuthGuardService.guards.push
——这基本上可以确保在调用
forPermissions
以获取自定义保护类时,它也会将其存储在此数组中。这在主类上也是静态的:

public static guards = [ ]; 
然后,我们可以使用此数组注册所有防护-只要我们确保在应用程序模块注册这些提供程序时,路由已经定义,并且所有防护类都已经创建(例如,检查导入顺序并在列表中尽可能降低这些提供程序-使用路由模块有助于):


希望这有帮助。

另一种方法与
数据
和工厂功能的组合:

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

导出功能可为角色激活(角色:角色[]){
返回{data:{roles},可以激活:[RoleGuard]}
}
导出类RoleGuard实现CanActivate{
canActivate(路由:ActivatedRouteSnapshot,状态:RouterStateSnashot)
:可观察的|承诺|布尔值{
const roles=route.data.roles作为角色[];
...
}
}
...
{path:'admin',component:AdminComponent,…可以通过角色([Role.admin])激活,

也是不错的选择,谢谢。虽然我更喜欢Aluan的工厂法,但还是要感谢你拓展了我的思路,让我了解了各种可能性!我认为这些数据的安全性无关紧要。您必须在服务器端使用身份验证和授权。我认为保护的重点不是完全保护您的应用程序。如果有人“黑客”它并导航到管理页面,他/她将不会从服务器上获得安全数据,只会看到你的管理组件,这在我看来是好的。我认为这是比公认的更好的解决方案。接受的解决方案打破了依赖注入。这是一个很好的解决方案,在我的通用AuthGuard中非常有效。这个解决方案非常有效。我的问题是它依赖于一层间接性。查看此代码的人不会意识到,
角色
对象和路由卫士是链接在一起的,而事先不知道代码是如何工作的。Angular不支持以更具声明性的方式实现这一点,这太糟糕了。(说得清楚一点,这是我在哀叹这不是一个完全合理的解决方案。)@KhalilRavanna谢谢你,是的,但我很久以前就用过这个解决方案,我换了另一个解决方案。我的新解决方案是一个RoleGard和一个名为“access.ts”的文件,其中包含Map常量,然后我在RoleGard中使用它。如果您想控制应用程序中的访问,我认为这种新方法会更好,尤其是当您在一个项目中有多个应用程序时。此解决方案会给我一个静态错误:静态解析符号值时遇到错误。此解决方案对我的开发非常有效,但是,当我在为生产构建应用程序时,在de中不支持“RoutingModule”函数调用的模板编译过程中抛出error
error-in-error
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]) },