ルートガードにパラメーターを渡す


101

私は、それらの役割に基づいてアプリの一部へのナビゲーションをブロックするためにガードを使用する必要がある多くの役割を持つアプリに取り組んでいます。役割ごとに個別のガードクラスを作成できることはわかっていますが、パラメーターを渡すことができるクラスを1つ持つほうがよいと思います。つまり、次のようなことができるようになりたいと思います。

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

ただし、渡すのはガードのタイプ名だけなので、その方法を考えることはできません。弾丸を噛んで役割ごとに個別のガードクラスを記述し、代わりに単一のパラメーター化された型を持つことによる優雅さの錯覚を打ち砕く必要がありますか?

回答:


216

を使用する代わりに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>;
    ...
}

素晴らしいオプションもありがとう。私はアルアンのファクトリーメソッドアプローチがほんの少し良くなっているのが好きですが、可能性について頭を広げてくれてありがとう!
Brian Noyes 2017年

7
このデータの安全性は重要ではないと思います。サーバー側で認証と承認を使用する必要があります。ガードの要点は、アプリケーションを完全に保護することではないと思います。誰かがそれを「ハッキング」して管理ページに移動した場合、彼/彼女はサーバーから安全なデータを取得できません。私の意見では大丈夫な管理コンポーネントを見るだけです。これは受け入れられたものよりもはるかに良い解決策だと思います。受け入れられたソリューションは、依存性注入を壊します。
bucicimaci

1
これは優れたソリューションであり、私の一般的なAuthGuardでうまく機能します。
SAV

3
このソリューションはうまく機能します。私の問題は、それが間接層に依存していることです。このコードを見ている人が、rolesオブジェクトとルートガードがリンクされていることを、コードがどのように事前に機能するかを知らずにリンクされていることに気付く方法はありません。Angularがこれをより宣言的な方法で行う方法をサポートしていないのは残念です。(明確にするために、これはAngularを完全に合理的な解決策にするのではなく、私を嘆き
悲しんでい

1
@KhalilRavannaありがとう、はい。でも、このソリューションは何年も前に使用していたため、別のソリューションに移動しました。私の新しい解決策は、1つのRoleGaurdとMap <URL、AccessRoles>定数を含む "access.ts"という名前の1つのファイルであり、それをRoleGaurdで使用します。アプリでアクセスを制御したい場合は、この新しい方法の方が、1つのプロジェクトに複数のアプリがある場合に特に優れていると思います。
Hasan Beheshti

11

これが私の見解であり、不足しているプロバイダーの問題に対する可能な解決策です。

私の場合、許可または許可のリストをパラメーターとして受け取るガードがありますが、これは役割を持つのと同じことです。

許可の有無にかかわらず、認証ガードを処理するためのクラスがあります。

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

の興味深い部分forPermissionAuthGuardService.guards.push-これは基本的にforPermissions、カスタムガードクラスを取得するために呼び出されたときに、この配列にも保存されることを確認します。これはメインクラスでも静的です。

public static guards = [ ]; 

次に、この配列を使用してすべてのガードを登録できます。これは、アプリモジュールがこれらのプロバイダーを登録するまでにルートが定義され、すべてのガードクラスが作成されていることを確認している限り問題ありません(たとえば、インポート順序を確認し、これらのプロバイダーをリストでできるだけ低く保つ-ルーティングモジュールがあると便利です):

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

お役に立てれば。


1
この解決策は私に静的エラーを与えます:シンボル値を静的に解決しているときに発生したエラーでのエラー。
Arninja 2017年

このソリューションは私には開発にERROR in Error during template compile of 'RoutingModule' Function calls are not supported in decorators but 'PermGuardService' was called.
役立ち

これは、独自のルーティングモジュールを持つ遅延ロードされたモジュールで機能しますか?
ときめき

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

ロールの組み合わせごとに新しいクラスが生成されるため、ロールの組み合わせごとにプロバイダーとして登録する必要があることに注意してください。つまり、あなたが持っている場合:

canActivate: [roleGuard('foo')]そしてcanActivate: [roleGuard('foo', 'bar')]あなたは両方を登録する必要があります:providers[roleGuard('foo'), roleGuard('foo', 'bar')]

より良い解決策は、内のグローバルプロバイダーコレクションにプロバイダーを自動的に登録することですroleGuardが、前述したように、それを実装するスキルが不足しています。


私はこの機能的アプローチが本当に好きですが、DI(クラス)とのミックスインクロージャーはオーバーヘッドのように見えます。
ビル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.