web-dev-qa-db-ja.com

Angular 4複数のガード-実行シーケンス

アプリケーションにAuthGuardとAccessGuardの2つのガードがあります。 AuthGuardは名前が示すようにすべてのページを保護し、GlobalServiceにセッションオブジェクトを保存します。AccessGuardは、AuthGuardがGlobalServiceに保存するセッションオブジェクトの一部のアクセスデータに依存します。

AuthGuardがObservableを返し、同時にAccessGuardが実行されて、まだ到着していないセッションオブジェクトをチェックしてコードが破損すると、問題が発生します。 セッションオブジェクトが到着するまで、またはこの競合状態を解消するために他の回避策があるまで、AccessGuardの実行を制限する方法はありますか?

#Note AccessGuardロジックをAuthGuardにマージしていません。他のすべてのルートで認証を必要とし、一部のルートのみをチェックする必要があるためです。たとえば、アカウントページとDBページにはすべてのユーザーがアクセスできますが、ユーザー管理とダッシュボードにはセッションオブジェクトからの外部アクセスパラメータが必要です。

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:ガードコードの追加

AuthGuard:

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

#NOTE:_dataServiceは、AuthGuardからのアクセス許可を格納するGlobalServiceです。

9
Akul Narang

これをご覧くださいAngularガイド( リンク )。 "実際のAPIを使用していた場合、表示するデータが返されるまでに遅延が発生する可能性がありますサーバー。データを待機している間、空白のコンポーネントを表示したくない。

ルートがアクティブになった瞬間に準備ができるように、サーバーからデータをプリフェッチすることをお勧めします。これにより、コンポーネントにルーティングする前にエラーを処理することもできます...

要約すると、必要なすべてのデータがフェッチされるまで、ルーティングされたコンポーネントのレンダリングを遅らせる必要があります。

リゾルバーが必要です。」

3

私は別の道を選びました---ガードを入れ子にし、お互いに依存関係を作りました。

RequireAuthenticationGuardRequirePermissionGuardがあります。ほとんどのルートでは両方を実行する必要がありますが、特定の順序が必要です。

RequireAuthenticationGuardは、現在のセッションが認証されているかどうかを確認するために私のauthNサービスに依存しています。

RequirePermissionGuardは、現在のセッションがルートに対して承認されているかどうかを確認するために私のauthZサービスに依存しています。

RequireAuthenticationGuardRequirePermissionGuardのコンストラクター依存関係として追加し、認証が決定された場合にのみ権限のチェックを開始します。

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)),
        );
}
9
seangwright

マスターガードを使用してアプリケーションガードを起動すると、トリックを実行できます。

編集:理解を深めるためにコードスニペットを追加します。

私は同様の問題に直面し、これが私がそれを解決した方法です-


解決

アイデアはマスターガードを作成し、マスターガードに他のガードの実行を処理させることです。

この場合、ルーティング設定には唯一のガードとしてのマスターガードが含まれます。

特定のルートに対してトリガーされるガードについてマスターガードに通知するには、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);
            }
        });
    }
}

。ルーティング構成-

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

挑戦

このアプローチの課題の1つは、既存のルーティングモデルのリファクタリングです。ただし、変更は重大ではないため、部分的に実行できます。

これがお役に立てば幸いです。

6
planet_hunter

サブガードを注入するマスターガードを作成するだけです。例は次のとおりです。

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に提供(注入可能と理解)できるようにモジュールを管理する必要があります。

0
Flavien Volken