2つのcanActivate
ガード(AuthGuard
およびRoleGuard
)があるルートがあります。最初の(AuthGuard
)は、ユーザーがログインしているかどうかを確認し、ログインしていない場合はログインページにリダイレクトします。 2番目は、ユーザーがページを表示できるロールが定義されているかどうかを確認し、定義されていない場合は、許可されていないページにリダイレクトします。
canActivate: [ AuthGuard, RoleGuard ]
...
export class AuthGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
...
this.router.navigate(['/login']);
resolve(false);
}
export class RoleGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
...
this.router.navigate(['/unauthorized']);
resolve(false);
}
問題は、ルートにアクセスしてログインしていないときにAuthGuard
を押すと、失敗してルーターに/login
。ただし、AuthGuard
が失敗した場合でも、RoleGuard
は実行され、/unauthorized
。
私の意見では、最初のガードが失敗した場合、次のガードを実行しても意味がありません。この動作を強制する方法はありますか?
これは、単にboolean
ではなくPromise<boolean>
を返すためです。ブール値を返すだけの場合、RoleGuard
はチェックされません。これはangular2
のバグか、非同期リクエストの予想される結果のどちらかだと思います。
ただし、特定のRoleGuard
が必要なURLにRole
を使用するだけで、この例でこれを解決できます。ロールを取得するにはログインする必要があると思います。その場合、RoleGuard
を次のように変更できます。
@Injectable()
export class RoleGuard implements CanActivate {
constructor(private _authGuard: AuthGuard) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
return this._authGuard.canActivate(route, state).then((auth: boolean) => {
if(!auth) {
return Promise.resolve(false);
}
//... your role guard check code goes here
});
}
@PierreDucで述べたように、data
クラスのRoute
プロパティとマスターガードを使用して、この問題を解決できます。
まず、angularは、タンデムでガードを呼び出す機能をサポートしていません。したがって、最初のガードが非同期でajax呼び出しを行おうとすると、残りのガードはすべて前に起動されますガード1でのajaxリクエストの完了。
私は同様の問題に直面し、これが私がそれを解決した方法です-
マスターガードを作成し、マスターガードに他のガードの実行を処理させるという考え方です。
ルーティング構成この場合、唯一のガードとしてのマスターガードが含まれます。
特定のルートでトリガーされるガードについてマスターガードに知らせるには、data
にRoute
プロパティを追加します。
data
プロパティは、ルートにデータを添付できるキーと値のペアです。
ガード内のActivatedRouteSnapshot
メソッドのcanActivate
パラメーターを使用して、ガード内のデータにアクセスできます。
ソリューションは複雑に見えますが、アプリケーションに統合されると、ガードの適切な動作が保証されます。
次の例でこのアプローチを説明します-
1。すべてのアプリケーションガードをマップする定数オブジェクト-
export const GUARDS = {
GUARD1: "GUARD1",
GUARD2: "GUARD2",
GUARD3: "GUARD3",
GUARD4: "GUARD4",
}
2。Application Guard-
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つは、既存のルーティングモデルのリファクタリングです。ただし、変更は中断されないため、部分的に行うことができます。
これがお役に立てば幸いです。
インターネット上でより良い解決策を見つけることができませんでしたが、ガイドとしてベストアンサーを使用して、Rxjs mergeMapを使用して連結された両方の要求を含む1つのガードのみを使用することにしました。ここでの例では、必要に応じてconsole.logを避け、最初にトリガーされたものを確認するためにそれを使用していました。
1ユーザーを認証するためにgetCASUsernameが呼び出されます(表示されないconsole.log(1)があります)
2 userNameがあります
3ここでは、最初のリクエストが応答を使用した後にトリガーされる2番目のリクエストを実行しています(true)
4返されたuserNameを使用して、そのユーザーのロールを取得します
これにより、コールシーケンスと重複コールを回避するためのソリューションが得られます。たぶんそれはあなたのために働く可能性があります。
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private AuthService : AuthService,
private AepApiService: AepApiService) {}
canActivate(): Observable<boolean> {
return this.AepApiService.getCASUsername(this.AuthService.token)
.map(res => {
console.log(2, 'userName');
if (res.name) {
this.AuthService.authenticateUser(res.name);
return true
}
})
.mergeMap( (res) => {
console.log(3, 'authenticated: ' + res);
if (res) {
return this.AepApiService.getAuthorityRoles(this.AuthService.$userName)
.map( res => {
console.log(4, 'roles');
const roles = res.roles;
this.AuthService.$userRoles = roles;
if (!roles.length) this.AuthService.goToAccessDenied();
return true;
})
.catch(() => {
return Observable.of(false);
});
} else {
return Observable.of(false);
}
})
.catch(():Observable<boolean> => {
this.AuthService.goToCASLoginPage();
return Observable.of(false);
});
}
}
現在、複数の非同期ガード(PromiseまたはObservableを返す)が同時に実行されます。私はこの問題を開きました: https://github.com/angular/angular/issues/21702
上記のソリューションの別の回避策は、ネストされたルートを使用することです。
{
path: '',
canActivate: [
AuthGuard,
],
children: [
{
path: '',
canActivate: [
RoleGuard,
],
component: YourComponent
// or redirectTo
// or children
// or loadChildren
}
]
}