web-dev-qa-db-de.com

Mehrere canActivate-Wachen werden beim ersten Fehlschlagen ausgeführt

Ich habe eine Route mit zwei canActivate Wachen (AuthGuard und RoleGuard). Der erste (AuthGuard) überprüft, ob der Benutzer angemeldet ist, und leitet, falls nicht, zur Anmeldeseite weiter. Die zweite prüft, ob für den Benutzer eine Rolle definiert ist, die das Anzeigen der Seite zulässt, und leitet, falls nicht, zur nicht autorisierten Seite weiter.

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

Das Problem ist, dass ich, wenn ich auf die Route zugreife und nicht angemeldet bin, den Befehl AuthGuard drücke, der fehlschlägt und den Router auffordert, zu /login Zu navigieren. Obwohl das AuthGuard fehlgeschlagen ist, wird das RoleGuard trotzdem ausgeführt und navigiert dann zu /unauthorized.

Meiner Meinung nach ist es sinnlos, die nächste Wache zu führen, wenn die erste ausfällt. Gibt es eine Möglichkeit, dieses Verhalten durchzusetzen?

27
revoxover

Dies ist darauf zurückzuführen, dass Sie ein Promise<boolean> statt nur ein boolean. Wenn Sie nur einen Booleschen Wert zurückgeben würden, würde das RoleGuard nicht überprüft. Ich würde vermuten, das ist entweder ein Fehler in angular2 oder ein erwartetes Ergebnis von asynchronen Anforderungen.

Sie können dies jedoch mit Ihrem Beispiel beheben, indem Sie RoleGuard nur für URLs verwenden, für die ein bestimmtes Role erforderlich ist, da Sie vermutlich angemeldet sein müssen, um eine Rolle zu spielen. In diesem Fall können Sie Ihr RoleGuard folgendermaßen ändern:

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

Wie von der Eigenschaft @PierreDuc data in der Klasse Route erwähnt, kann dieses Problem mit einem Master Guard gelöst werden.

Problem

Zuallererst unterstützt angular nicht die Funktion, die Wachen gleichzeitig anzurufen. Wenn also die erste Wache asynchron ist und versucht, Ajax-Anrufe zu tätigen, werden alle verbleibenden Wachen bereits zuvor abgefeuert Abschluss der Ajax-Anfrage in Wache 1.

Ich sah mich einem ähnlichen Problem gegenüber und habe es auf diese Weise gelöst -


Lösung

Die Idee ist, einen Master Guard zu erstellen und den Master Guard mit der Ausführung anderer Guards zu beauftragen.

Die Routing-Konfiguration enthält in diesem Fall Master Guard als einziger Guard.

Fügen Sie eine data -Eigenschaft in Route hinzu, um den Hauptwächter über die für bestimmte Routen auszulösenden Wachen zu informieren.

Die Eigenschaft data ist ein Schlüsselwertpaar, mit dem wir Daten an die Routen anhängen können.

Auf die Daten kann dann in den Wachen mit dem Parameter ActivatedRouteSnapshot der Methode canActivate in der Wache zugegriffen werden.

Die Lösung sieht kompliziert aus, stellt jedoch sicher, dass die Wachen ordnungsgemäß funktionieren, sobald sie in die Anwendung integriert sind.

Das folgende Beispiel erläutert diesen Ansatz:


Beispiel

1. Konstanten Objekt zum Zuordnen aller Anwendungs-Guards -

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

. Routing-Konfiguration -

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. Master Guard -

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

Herausforderungen

Eine der Herausforderungen bei diesem Ansatz ist das Refactoring des vorhandenen Routing-Modells. Es kann jedoch in Teilen durchgeführt werden, da die Änderungen nicht brechen.

Ich hoffe das hilft.

3
planet_hunter

Ich habe im Internet keine bessere Lösung gefunden, aber als Leitfaden für die beste Antwort entscheide ich mich, nur einen Wächter zu verwenden, der beide mit Rxjs mergeMap verknüpften Anforderungen enthält, um doppelte Aufrufe an denselben Endpunkt zu vermeiden. Vermeiden Sie in meinem Beispiel die Datei console.log, wenn Sie möchten. Ich habe sie verwendet, um sicherzugehen, was zuerst ausgelöst wurde.

1 getCASUsername wird aufgerufen, um den Benutzer zu authentifizieren (hier ist ein console.log (1), das Sie nicht sehen können)
2 Wir haben den Benutzernamen
3 Hier mache ich eine zweite Anfrage, die nach der ersten mit der Antwort ausgelöst wird (true)
4 Mit dem zurückgegebenen Benutzernamen erhalte ich die Rollen für diesen Benutzer

Damit habe ich die Lösung zur Anruffolge und zur Vermeidung doppelter Anrufe. Vielleicht könnte es für Sie arbeiten.

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

Gegenwärtig werden mehrere asynchrone Wachen (Returning Promise oder Observable) gleichzeitig ausgeführt. Ich habe dazu ein Problem aufgeschlagen: https://github.com/angular/angular/issues/21702

Eine andere Problemumgehung für die oben beschriebene Lösung besteht darin, verschachtelte Routen zu verwenden:

{
  path: '',
  canActivate: [
    AuthGuard,
  ],
  children: [
    {
      path: '',
      canActivate: [
        RoleGuard,
      ],
      component: YourComponent
      // or redirectTo
      // or children
      // or loadChildren
    }
  ]
}
1
Mick