Parameter an Route Guard übergeben

101

Ich arbeite an einer App mit vielen Rollen, die ich mithilfe von Wachen verwenden muss, um die Navigation für Teile der App basierend auf diesen Rollen zu blockieren. Mir ist klar, dass ich für jede Rolle individuelle Schutzklassen erstellen kann, aber lieber eine Klasse hätte, an die ich irgendwie einen Parameter übergeben könnte. Mit anderen Worten, ich möchte in der Lage sein, etwas Ähnliches zu tun:

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

Aber da alles, was Sie passieren, der Typname Ihrer Wache ist, können Sie sich keinen Weg vorstellen, dies zu tun. Sollte ich einfach in die Kugel beißen und die einzelnen Schutzklassen pro Rolle schreiben und meine Illusion von Eleganz zerstören, stattdessen einen einzigen parametrisierten Typ zu haben?

Brian Noyes
quelle

Antworten:

216

Anstatt zu verwenden forRole(), können Sie Folgendes tun:

{ 
   path: 'super-user-stuff', 
   component: SuperUserStuffComponent,
   canActivate: RoleGuard,
   data: {roles: ['SuperAdmin', ...]}
}

und verwenden Sie dies in Ihrem RoleGuard

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot)
    : Observable<boolean> | Promise<boolean> | boolean  {

    let roles = route.data.roles as Array<string>;
    ...
}
Hasan Beheshti
quelle
Tolle Option, danke. Ich mag Aluans Fabrikmethodenansatz zwar nur ein bisschen besser, aber danke, dass Sie mein Gehirn auf die Möglichkeiten erweitert haben!
Brian Noyes
7
Ich denke, die Sicherheit dieser Daten ist irrelevant. Sie müssen die Authentifizierung und Autorisierung auf der Serverseite verwenden. Ich denke, der Sinn des Schutzes besteht darin, Ihre Anwendung nicht vollständig zu schützen. Wenn jemand es "hackt" und zur Administrationsseite navigiert, erhält er / sie nicht die sicheren Daten vom Server, sondern sieht nur Ihre Administratorkomponenten, was meiner Meinung nach in Ordnung ist. Ich denke, dies ist eine viel bessere Lösung als die akzeptierte. Die akzeptierte Lösung unterbricht die Abhängigkeitsinjektion.
Bucicimaci
1
Dies ist eine gute Lösung und funktioniert hervorragend in meinem generischen AuthGuard.
SAV
3
Diese Lösung funktioniert hervorragend. Mein Problem ist, dass es auf einer Indirektionsebene beruht. Es gibt keine Möglichkeit, dass jemand, der sich diesen Code ansieht, erkennt, dass das rolesObjekt und der Routenwächter miteinander verbunden sind, ohne vorher zu wissen, wie der Code funktioniert. Es ist schade, dass Angular keine Möglichkeit unterstützt, dies deklarativer zu tun. (Um klar zu sein, beklage ich Angular nicht diese völlig vernünftige Lösung.)
KhalilRavanna
1
@ KhalilRavanna Danke, ja, aber ich habe diese Lösung vor vielen Zeiten verwendet und bin zu einer anderen Lösung übergegangen. Meine neue Lösung ist eine RoleGaurd und eine Datei mit dem Namen "access.ts" mit der Konstante Map <URL, AccessRoles>, die ich dann in RoleGaurd verwende. Wenn Sie Ihre Zugriffe in Ihrer App steuern möchten, ist diese neue Methode meiner Meinung nach viel besser, insbesondere wenn Sie mehr als eine App in einem Projekt haben.
Hasan Beheshti
11

Hier ist meine Meinung dazu und eine mögliche Lösung für das fehlende Anbieterproblem.

In meinem Fall haben wir einen Guard, der eine Berechtigung oder eine Liste von Berechtigungen als Parameter verwendet, aber es ist dasselbe, das eine Rolle spielt.

Wir haben eine Klasse für den Umgang mit Auth Guards mit oder ohne Erlaubnis:

@Injectable()
export class AuthGuardService implements CanActivate {

    checkUserLoggedIn() { ... }

Dies befasst sich mit der Überprüfung der aktiven Sitzung des Benutzers usw.

Es enthält auch eine Methode zum Abrufen eines benutzerdefinierten Berechtigungsschutzes, die tatsächlich von sich AuthGuardServiceselbst abhängt

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

Auf diese Weise können wir die Methode verwenden, um einige benutzerdefinierte Schutzvorrichtungen basierend auf Berechtigungsparametern in unserem Routingmodul zu registrieren:

....
{ path: 'something', 
  component: SomeComponent, 
  canActivate: [ AuthGuardService.forPermissions('permission1', 'permission2') ] },

Der interessante Teil von forPermissionist AuthGuardService.guards.push- dies stellt im Grunde sicher, dass jedes Mal, wenn forPermissionseine benutzerdefinierte Schutzklasse aufgerufen wird, diese auch in diesem Array gespeichert wird. Dies gilt auch für die Hauptklasse:

public static guards = [ ]; 

Dann können wir dieses Array verwenden, um alle Wachen zu registrieren - dies ist in Ordnung, solange wir sicherstellen, dass zum Zeitpunkt der Registrierung dieser Anbieter durch das App-Modul die Routen definiert und alle Wächterklassen erstellt wurden (z. B. Importreihenfolge überprüfen und Halten Sie diese Anbieter in der Liste so niedrig wie möglich - ein Routing-Modul hilft):

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

Hoffe das hilft.

Ovidiu Dolha
quelle
1
Diese Lösung gibt mir einen statischen Fehler: ERROR in Error hat das statische Auflösen von Symbolwerten festgestellt.
Arninja
Diese Lösung funktionierte für mich für die Entwicklung, aber als ich die Anwendung für die Produktion in Würfen Fehler bauteERROR in Error during template compile of 'RoutingModule' Function calls are not supported in decorators but 'PermGuardService' was called.
kpacn
Funktioniert dies mit faul geladenen Modulen, die ihre eigenen Routingmodule haben?
Crush
2

Die Lösung von @ AluanHaddad gibt den Fehler "kein Anbieter" aus. Hier ist eine Lösung dafür (es fühlt sich schmutzig an, aber mir fehlen die Fähigkeiten, um eine bessere zu machen).

Konzeptionell registriere ich als Anbieter jede dynamisch generierte Klasse, die von erstellt wurde roleGuard.

Also für jede überprüfte Rolle:

canActivate: [roleGuard('foo')]

du solltest haben:

providers: [roleGuard('foo')]

Die aktuelle Lösung von @ AluanHaddad generiert jedoch für jeden Aufruf von eine neue Klasse roleGuard, auch wenn der rolesParameter derselbe ist. Die Verwendung lodash.memoizesieht folgendermaßen aus:

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

Beachten Sie, dass jede Rollenkombination eine neue Klasse generiert. Sie müssen daher jede Rollenkombination als Anbieter registrieren . Dh wenn Sie haben:

canActivate: [roleGuard('foo')]und canActivate: [roleGuard('foo', 'bar')]Sie müssen beide registrieren:providers[roleGuard('foo'), roleGuard('foo', 'bar')]

Eine bessere Lösung wäre, Anbieter automatisch in einer globalen Anbietersammlung zu registrieren roleGuard, aber wie gesagt, mir fehlen die Fähigkeiten, dies umzusetzen.

THX-1138
quelle
Ich mag diesen funktionalen Ansatz sehr, aber Mixin-Verschlüsse mit DI (Klassen) sehen nach Overhead aus.
Rechnung