【问题标题】:Pass parameter into route guard将参数传递给路由保护
【发布时间】:2017-07-31 20:39:37
【问题描述】:

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

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

但是由于你传递的只是你的后卫的类型名称,所以想不出办法来做到这一点。我是否应该硬着头皮为每个角色编写单独的警卫类,并打破我对拥有单一参数化类型的优雅幻想?

【问题讨论】:

    标签: angular typescript angular2-routing


    【解决方案1】:

    您可以这样做,而不是使用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>;
        ...
    }
    

    【讨论】:

    • 也是不错的选择,谢谢。虽然我更喜欢 Aluan 的工厂方法方法,但要稍微好一点,但感谢您扩展了我的脑洞!
    • 我认为这些数据的安全性无关紧要。您必须在服务器端使用身份验证和授权。我认为保护的重点不是完全保护您的应用程序。如果有人“破解”它并导航到管理页面,他/她将不会从服务器获取安全数据,只会看到您的管理组件,这在我看来是可以的。我认为这是公认的更好的解决方案。接受的解决方案打破了依赖注入。
    • 这是一个很好的解决方案,它在我的通用 AuthGuard 中效果很好。
    • 这个解决方案效果很好。我的问题是它依赖于间接层。在不知道代码如何工作的情况下,查看此代码的人不可能意识到 roles 对象和路由保护是链接的。 Angular 不支持以更具声明性的方式执行此操作,这很糟糕。 (要明确的是,这是我在哀叹 Angular 而不是这个完全合理的解决方案。)
    • @KhalilRavanna 谢谢你,是的,但我以前多次使用过这个解决方案,然后我转向了另一个解决方案。我的新解决方案是一个 RoleGaurd 和一个名称为“access.ts”的文件,其中包含 Map 常量,然后我在 RoleGaurd 中使用它。如果您想控制您的应用程序中的访问,我认为这种新方法要好得多,尤其是当您在一个项目中拥有多个应用程序时。
    【解决方案2】:

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

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

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

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

    【讨论】:

    • 我真的很喜欢这种函数式方法,但是带有 DI(类)的 mixin 闭包看起来像是开销。
    【解决方案3】:

    这是我对此的看法以及缺少提供程序问题的可能解决方案。

    在我的例子中,我们有一个将权限或权限列表作为参数的守卫,但它具有相同的角色。

    我们有一个类可以在有或没有许可的情况下处理身份验证守卫:

    @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 = [ ]; 
    

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

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

    希望这会有所帮助。

    【讨论】:

    • 这个解决方案给了我一个静态错误:ERROR in Error遇到静态解析符号值。
    • 这个解决方案对我来说适用于开发,但是当我构建生产应用程序时抛出错误ERROR in Error during template compile of 'RoutingModule' Function calls are not supported in decorators but 'PermGuardService' was called.
    • 这是否适用于具有自己的路由模块的延迟加载模块?
    【解决方案4】:

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

    【讨论】:

    • 此处使用Spread Syntax (...) 的限制意味着您的路由中定义的任何data 属性都将被...canActivateWithRoles([...]) 覆盖。例如,这将不起作用{/* ... */, data: { title: 'MyTitle' }, ...canActivateWithRoles([Role.Admin]) }
    【解决方案5】:

    另一种解决方案是返回 InjectionToken 并使用工厂方法:

    export class AccessGuard {
      static canActivateWithRoles(roles: string[]) {
        return new InjectionToken<CanActivate>('AccessGuardWithRoles', {
          providedIn: 'root',
          factory: () => {
            const authorizationService = inject(AuthorizationService);
    
            return {
              canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): <boolean | UrlTree > | Promise<boolean | UrlTree> | boolean | UrlTree {
                  return authorizationService.hasRole(roles);
              }
            };
          },
        });
      }
    }
    

    并像这样使用它:

    canActivate: [AccessGuard.canActivateWithRoles(['ADMIN'])]
    

    【讨论】:

      猜你喜欢
      • 2023-03-20
      • 1970-01-01
      • 2019-03-03
      • 2018-08-14
      • 2020-02-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-24
      相关资源
      最近更新 更多