148 votes

Transmettre un paramètre dans le garde de route

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

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

Mais étant donné que tout ce que vous passez est le nom de type de votre garde, je ne trouve pas de moyen de le faire. Devrais-je simplement prendre mon courage à deux mains et écrire des classes de garde individuelles par rôle et briser mon illusion d'élégance en ayant un seul type paramétré au lieu?

295voto

Hasan Beheshti Points 1345

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 | Promise | boolean  {

    let roles = route.data.roles as Array;
    ...
}

1 votes

Super option aussi, merci. J'aime un tout petit peu mieux l'approche de la méthode d'usine d'Aluan cependant, mais merci d'avoir élargi mes horizons sur les possibilités!

0 votes

Est-ce sécurisé? Quelqu'un peut-il simplement afficher ces données?

10 votes

Je pense que la sécurité de ces données est sans importance. Vous devez utiliser l'authentification et l'autorisation côté serveur. Je pense que le rôle du garde n'est pas de protéger complètement votre application. Si quelqu'un « pirate » et accède à la page d'administration, il/elle ne récupérera pas les données sécurisées du serveur, mais verra seulement les composants de votre administration, ce qui est à mon avis correct. Je pense que c'est une bien meilleure solution que celle acceptée. La solution acceptée casse l'injection de dépendance.

14voto

Ovidiu Dolha Points 3496

Voici mon point de vue sur ce sujet et une solution possible pour le problème du fournisseur manquant.

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

Nous avons une classe pour gérer 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 de permission personnalisé, qui dépend en réalité de la classe AuthGuardService elle-même.

static forPermissions(permissions: string | string[]) {
    @Injectable()
    class AuthGuardServiceWithPermissions {
      constructor(private authGuardService: AuthGuardService) { } // utilise en fait l'instance de la classe parent, mais pourrait en théorie prendre tout autre dépendance

      canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
        // vérifie l'activation typique (auth) + les permissions personnalisées
        return this.authGuardService.canActivate(route, state) && this.checkPermissions();
      }

      checkPermissions() {
        const user = ... // obtenir l'utilisateur actuel
        // vérifie les permissions données avec l'utilisateur actuel
        return user.hasPermissions(permissions);
      }
    }

    AuthGuardService.guards.push(AuthGuardServiceWithPermissions);
    return AuthGuardServiceWithPermissions;
  }

Cela nous permet d'utiliser la méthode pour enregistrer certains gardes personnalisés basés sur le paramètre de permissions dans notre module de routage :

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

La partie intéressante de forPermission est AuthGuardService.guards.push - cela garantit essentiellement qu'à chaque fois que forPermissions est appelé pour obtenir une classe de garde personnalisée, elle sera également stockée dans ce tableau. Cela est également statique sur la classe principale :

public static guards = [ ]; 

Ensuite, nous pouvons utiliser ce tableau pour enregistrer tous les gardes - c'est bien tant que nous nous assurons que lorsque le module de l'application enregistre ces fournisseurs, les routes ont été définies et que 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,
]

En espérant que cela vous aidera.

1 votes

Cette solution me donne une erreur statique : ERREUR dans une erreur rencontrée lors de la résolution des valeurs de symbole de manière statique.

0 votes

Cette solution a fonctionné pour moi pour le développement, mais lorsque je construis l'application pour la production, elle affiche l'erreur ERROR in Erreur lors de la compilation du modèle de 'RoutingModule' Les appels de fonction ne sont pas pris en charge dans les décorateurs mais 'PermGuardService' a été appelé.

0 votes

Est-ce que cela fonctionne avec des modules chargés paresseusement qui ont leurs propres modules de routage ?

4voto

Une autre combinaison d'approche avec data et une fonction factory:

export function canActivateForRoles(roles: Role[]) {
  return {data: {roles}, canActivate: [RoleGuard]}
}

export class RoleGuard implements CanActivate {
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot)
      : Observable | Promise | boolean  {

      const roles = route.data.roles as Role[];
    ...
  }
}

...

{ path: 'admin', component: AdminComponent, ...canActivateWithRoles([Role.Admin]) },

2voto

THX-1138 Points 7129

La solution de @AluanHaddad donne une erreur "pas de fournisseur". Voici un correctif pour cela (cela semble sale, mais je manque de 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')]

vous devriez avoir :

providers: [roleGuard('foo')]

Cependant, la solution telle qu'elle est présentée par @AluanHaddad générera une nouvelle classe à chaque appel de roleGuard, même si le paramètre roles est le même. En utilisant lodash.memoize cela ressemblerait à ceci :

export var roleGuard = _.memoize(function forRole(...roles: string[]): Type {
    return class AuthGuard implements CanActivate {
        canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
            Observable
            | Promise
            | boolean {
            console.log(`verification de l'accès pour ${roles.join(', ')}.`);
            return true;
        }
    }
});

Remarquez, chaque combinaison de rôles génère une nouvelle classe, donc vous devez enregistrer en tant que fournisseur chaque combinaison de rôles. Par exemple, si vous avez :

canActivate: [roleGuard('foo')] et canActivate: [roleGuard('foo', 'bar')] vous devrez enregistrer les deux : providers[roleGuard('foo'), roleGuard('foo', 'bar')]

Une meilleure solution serait d'enregistrer les fournisseurs automatiquement dans une collection globale de fournisseurs à l'intérieur de roleGuard, mais comme je l'ai dit, je manque des compétences pour implémenter cela.

0 votes

Je suis vraiment fan de cette approche fonctionnelle mais mélanger les fermetures de mixins avec l'injection de dépendances (classes) semble superflu.

1voto

maechler Points 670

Une autre solution pourrait être de retourner un InjectionToken et d'utiliser une méthode de fabrique :

export class AccessGuard {
  static canActivateWithRoles(roles: string[]) {
    return new InjectionToken('AccessGuardWithRoles', {
      providedIn: 'root',
      factory: () => {
        const authorizationService = inject(AuthorizationService);

        return {
          canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):  | Promise | boolean | UrlTree {
              return authorizationService.hasRole(roles);
          }
        };
      },
    });
  }
}

Et l'utiliser comme ceci :

canActivate: [AccessGuard.canActivateWithRoles(['ADMIN'])]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X