Passa il parametro alla guardia del percorso


102

Sto lavorando a un'app che ha molti ruoli di cui ho bisogno per utilizzare le guardie per bloccare la navigazione verso parti dell'app in base a quei ruoli. Mi rendo conto di poter creare classi di guardia individuali per ogni ruolo, ma preferisco avere una classe a cui passare in qualche modo un parametro. In altre parole vorrei poter fare qualcosa di simile a questo:

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

Ma poiché tutto ciò che passi è il nome del tipo della tua guardia, non riesco a pensare a un modo per farlo. Dovrei solo mordere il proiettile e scrivere le singole classi di guardia per ruolo e frantumare la mia illusione di eleganza nell'avere invece un unico tipo parametrizzato?

Risposte:


218

Invece di usare forRole(), puoi fare questo:

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

e usalo nel tuo RoleGuard

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

    let roles = route.data.roles as Array<string>;
    ...
}

Ottima opzione, grazie. Mi piace l'approccio del metodo di fabbrica di Aluan solo un po 'meglio, ma grazie per aver ampliato il mio cervello sulle possibilità!
Brian Noyes

7
Penso che la sicurezza di questi dati sia irrilevante. Devi utilizzare l'autenticazione e l'autorizzazione sul lato server. Penso che il punto della guardia non sia proteggere completamente la tua applicazione. Se qualcuno lo "hackera" e naviga alla pagina di amministrazione, non otterrà i dati sicuri dal server solo vedrà i componenti di amministrazione che è ok secondo me. Penso che questa sia una soluzione molto migliore di quella accettata. La soluzione accettata interrompe l'inserimento delle dipendenze.
bucicimaci

1
Questa è una buona soluzione e funziona benissimo nel mio AuthGuard generico.
SAV

3
Questa soluzione funziona alla grande. Il mio problema è che si basa su uno strato di indirezione. Non è possibile che qualcuno guardando questo codice si accorga che l' rolesoggetto e la guardia del percorso sono collegati senza sapere come funziona il codice in anticipo. Fa schifo che Angular non supporti un modo per farlo in un modo più dichiarativo. (Per essere chiari,
sto

1
@KhalilRavanna grazie, sì, ma ho usato questa soluzione molte volte fa e sono passato a un'altra soluzione. La mia nuova soluzione è un RoleGaurd e un file con il nome "access.ts" con Map <URL, AccessRoles> costante in esso, quindi lo utilizzo in RoleGaurd. Se vuoi controllare i tuoi accessi nella tua app, penso che questo nuovo modo sia molto meglio, soprattutto quando hai più di un'app in un progetto.
Hasan Beheshti

11

Ecco la mia opinione su questo e una possibile soluzione per il problema del provider mancante.

Nel mio caso, abbiamo una guardia che prende un permesso o un elenco di permessi come parametro, ma è la stessa cosa che ha un ruolo.

Abbiamo una classe per trattare con le guardie di autenticazione con o senza autorizzazione:

@Injectable()
export class AuthGuardService implements CanActivate {

    checkUserLoggedIn() { ... }

Si occupa del controllo della sessione attiva dell'utente, ecc.

Contiene anche un metodo utilizzato per ottenere una protezione di autorizzazione personalizzata, che in realtà dipende da AuthGuardServiceessa stessa

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

Questo ci consente di utilizzare il metodo per registrare alcune guardie personalizzate in base al parametro delle autorizzazioni nel nostro modulo di instradamento:

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

La parte interessante di forPermissionè che AuthGuardService.guards.pushquesto fondamentalmente assicura che ogni volta che forPermissionsviene chiamato per ottenere una classe di guardia personalizzata, la memorizzerà anche in questo array. Anche questo è statico nella classe principale:

public static guards = [ ]; 

Quindi possiamo usare questo array per registrare tutte le guardie - questo va bene fintanto che ci assicuriamo che nel momento in cui il modulo dell'app registra questi provider, le rotte siano state definite e tutte le classi di guardia siano state create (es. Controllare l'ordine di importazione e mantenere questi provider il più basso possibile nell'elenco - avere un modulo di routing aiuta):

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

Spero che questo ti aiuti.


1
Questa soluzione mi dà un errore statico: ERRORE in Errore riscontrato nella risoluzione statica dei valori dei simboli.
Arninja

Questa soluzione ha funzionato per me per lo sviluppo, ma quando ERROR in Error during template compile of 'RoutingModule' Function calls are not supported in decorators but 'PermGuardService' was called.
compilo

Funziona con moduli caricati in modo pigro che hanno i propri moduli di routing?
schiacciare il

2

La soluzione di @ AluanHaddad sta dando l'errore "nessun provider". Ecco una soluzione per questo (sembra sporco, ma mi mancano le capacità per crearne uno migliore).

Concettualmente, registro, come provider, ogni classe generata dinamicamente creata da roleGuard.

Quindi per ogni ruolo controllato:

canActivate: [roleGuard('foo')]

avresti dovuto:

providers: [roleGuard('foo')]

Tuttavia, la soluzione di @ AluanHaddad così com'è genererà una nuova classe per ogni chiamata a roleGuard, anche se il rolesparametro è lo stesso. Usandolo lodash.memoizeassomiglia a questo:

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

Nota, ogni combinazione di ruoli genera una nuova classe, quindi devi registrare come fornitore ogni combinazione di ruoli. Cioè se hai:

canActivate: [roleGuard('foo')]e canActivate: [roleGuard('foo', 'bar')]dovrai registrare entrambi:providers[roleGuard('foo'), roleGuard('foo', 'bar')]

Una soluzione migliore sarebbe quella di registrare automaticamente i fornitori in una raccolta di fornitori globale all'interno roleGuard, ma come ho detto, mi mancano le competenze per implementarlo.


Mi piace molto questo approccio funzionale, ma combinare chiusure con DI (classi) sembra un sovraccarico.
BILL
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.