Passer le paramètre dans la garde d'itinéraire

103

Je travaille sur une application qui a beaucoup de rôles dont j'ai besoin pour utiliser des gardes pour bloquer la navigation vers des parties de l'application en fonction de ces rôles. Je me rends compte que je peux créer des classes de garde individuelles pour chaque rôle, mais que je préfère avoir une classe dans laquelle je pourrais d'une manière ou d'une autre passer un paramètre. En d'autres termes, j'aimerais pouvoir faire quelque chose de similaire à ceci:

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

Mais comme tout ce que vous passez est le nom de type de votre garde, vous ne pouvez pas penser à un moyen de le faire. Dois-je simplement mordre la balle et écrire les classes de garde individuelles par rôle et briser mon illusion d'élégance en ayant un seul type paramétré à la place?

Brian Noyes
la source

Réponses:

220

Au lieu d'utiliser forRole(), vous pouvez faire ceci:

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

et utilisez ceci dans votre RoleGuard

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

    let roles = route.data.roles as Array<string>;
    ...
}
Hasan Beheshti
la source
Excellente option aussi, merci. J'aime un peu mieux l'approche de la méthode d'usine d'Aluan, mais merci d'avoir élargi mon cerveau sur les possibilités!
Brian Noyes
7
Je pense que la sécurité de ces données n’est pas pertinente. Vous devez utiliser l'authentification et l'autorisation côté serveur. Je pense que le but de la garde n'est pas de protéger complètement votre application. Si quelqu'un le «pirate» et accède à la page d'administration, il / elle n'obtiendra pas les données sécurisées du serveur, il ne verra que vos composants d'administration, ce qui est correct à mon avis. Je pense que c'est une solution bien meilleure que celle acceptée. La solution acceptée rompt l'injection de dépendance.
bucicimaci
1
C'est une bonne solution et cela fonctionne très bien dans mon AuthGuard générique.
SAV
3
Cette solution fonctionne très bien. Mon problème est qu'il repose sur une couche d'indirection. Il est impossible que quelqu'un qui regarde ce code se rende compte que l' rolesobjet et le garde-route sont liés sans savoir comment le code fonctionne à l'avance. Il est nul qu'Angular ne prenne pas en charge un moyen de le faire de manière plus déclarative. (Pour être clair, c'est moi qui déplore Angular pas cette solution parfaitement raisonnable.)
KhalilRavanna
1
@KhalilRavanna merci, oui mais j'ai utilisé cette solution il y a de nombreuses fois et je suis passé à une autre solution. Ma nouvelle solution est un RoleGaurd et un fichier avec le nom "access.ts" avec la constante Map <URL, AccessRoles>, puis je l'utilise dans RoleGaurd. Si vous souhaitez contrôler vos accès dans votre application, je pense que cette nouvelle méthode est bien meilleure, surtout lorsque vous avez plus d'une application dans un projet.
Hasan Beheshti
11

Voici mon avis à ce sujet et une solution possible au problème du fournisseur manquant.

Dans mon cas, nous avons un garde qui prend une permission ou une liste d'autorisations comme paramètre, mais c'est la même chose d'avoir un rôle.

Nous avons une classe pour traiter avec les gardes d'authentification avec ou sans permission:

@Injectable()
export class AuthGuardService implements CanActivate {

    checkUserLoggedIn() { ... }

Cela concerne la vérification de la session active de l'utilisateur, etc.

Il contient également une méthode utilisée pour obtenir un garde d'autorisation personnalisé, qui dépend en fait de AuthGuardServicelui - même

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

Cela nous permet d'utiliser la méthode pour enregistrer des gardes personnalisés en fonction du paramètre d'autorisations dans notre module de routage:

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

La partie intéressante de forPermissionest AuthGuardService.guards.push- cela garantit essentiellement qu'à tout moment forPermissionsest appelé pour obtenir une classe de garde personnalisée, il la stockera également dans ce tableau. Ceci est également statique sur la classe principale:

public static guards = [ ]; 

Ensuite, nous pouvons utiliser ce tableau pour enregistrer tous les gardes - ce n'est pas grave tant que nous nous assurons qu'au moment où le module d'application enregistre ces fournisseurs, les routes ont été définies et toutes les classes de garde ont été créées (par exemple, vérifiez l'ordre d'importation et gardez ces fournisseurs aussi bas que possible dans la liste - avoir un module de routage aide):

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

J'espère que cela t'aides.

Ovidiu Dolha
la source
1
Cette solution me donne une erreur statique: ERREUR dans Erreur rencontrée lors de la résolution statique des valeurs de symbole.
Arninja
Cette solution a fonctionné pour moi pour le développement, mais lorsque je construis l'application pour la production en jette une erreurERROR in Error during template compile of 'RoutingModule' Function calls are not supported in decorators but 'PermGuardService' was called.
kpacn
Cela fonctionne-t-il avec les modules chargés différés qui ont leurs propres modules de routage?
écraser le
2

La solution de @ AluanHaddad donne l'erreur "pas de fournisseur". Voici un correctif pour cela (cela semble sale, mais je n'ai pas les compétences pour en faire un meilleur).

Conceptuellement, j'enregistre, en tant que fournisseur, chaque classe générée dynamiquement créée par roleGuard.

Donc, pour chaque rôle vérifié:

canActivate: [roleGuard('foo')]

tu aurais dû:

providers: [roleGuard('foo')]

Cependant, la solution de @ AluanHaddad telle quelle générera une nouvelle classe pour chaque appel à roleGuard, même si le rolesparamètre est le même. Son utilisation lodash.memoizeressemble à ceci:

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

Notez que chaque combinaison de rôles génère une nouvelle classe, vous devez donc vous inscrire en tant que fournisseur pour chaque combinaison de rôles. Ie si vous avez:

canActivate: [roleGuard('foo')]et canActivate: [roleGuard('foo', 'bar')]vous devrez vous inscrire à la fois:providers[roleGuard('foo'), roleGuard('foo', 'bar')]

Une meilleure solution serait d'enregistrer automatiquement les fournisseurs dans une collection de fournisseurs mondiaux à l'intérieur roleGuard, mais comme je l'ai dit, je n'ai pas les compétences pour mettre en œuvre cela.

THX-1138
la source
J'aime vraiment cette approche fonctionnelle mais les fermetures mixin avec DI (classes) ressemblent à une surcharge.
PROJET DE LOI