Passar parâmetro para guarda de rota


102

Estou trabalhando em um aplicativo que tem várias funções e preciso usar guardas para bloquear a navegação em partes do aplicativo com base nessas funções. Sei que posso criar classes de guarda individuais para cada função, mas preferiria ter uma classe para a qual pudesse de alguma forma passar um parâmetro. Em outras palavras, gostaria de ser capaz de fazer algo semelhante a isto:

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

Mas, como tudo que você passa é o nome do tipo do seu guarda, não consigo pensar em uma maneira de fazer isso. Devo apenas controlar a bala e escrever as classes de guarda individuais por função e destruir minha ilusão de elegância em ter um único tipo parametrizado?

Respostas:


218

Em vez de usar forRole(), você pode fazer o seguinte:

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

e use isso em seu RoleGuard

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

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

Ótima opção também, obrigado. Eu gosto da abordagem do método de fábrica de Aluan apenas um pouco melhor, mas obrigado por expandir meu cérebro nas possibilidades!
Brian Noyes,

7
Acho que a segurança desses dados é irrelevante. Você tem que usar autenticação e autorização no lado do servidor. Eu acho que o objetivo do guarda não é proteger totalmente seu aplicativo. Se alguém o "hackear" e navegar para a página de administração, ele / ela não obterá os dados seguros do servidor, apenas verá seus componentes de administração, o que está ok em minha opinião. Acho que essa solução é muito mais melhor do que a aceita. A solução aceita quebra a injeção de dependência.
bucicimaci de

1
Esta é uma boa solução e funciona muito bem no meu AuthGuard genérico.
SAV de

3
Esta solução funciona muito bem. Meu problema é que ele depende de uma camada indireta. Não há como alguém olhando para este código perceber que o rolesobjeto e o protetor de rota estão vinculados sem saber com antecedência como o código funciona. É uma pena que o Angular não ofereça suporte a uma maneira de fazer isso de forma mais declarativa. (Para ficar claro, sou eu lamentando o Angular, não essa solução perfeitamente razoável.)
KhalilRavanna

1
@KhalilRavanna obrigado, sim mas usei esta solução há muito tempo e mudei para outra solução. Minha nova solução é um RoleGaurd e um arquivo com o nome "access.ts" com a constante Map <URL, AccessRoles> nele, então eu o uso em RoleGaurd. Se você deseja controlar seus acessos em seu aplicativo, acho que esta nova forma é muito melhor, principalmente quando você tem mais de um aplicativo em um projeto.
Hasan Beheshti

11

Aqui está minha opinião sobre isso e uma possível solução para o problema do provedor ausente.

No meu caso, temos um guarda que leva uma permissão ou lista de permissões como parâmetro, mas é a mesma coisa ter uma função.

Temos uma aula para lidar com os guardas de autenticação com ou sem permissão:

@Injectable()
export class AuthGuardService implements CanActivate {

    checkUserLoggedIn() { ... }

Trata-se de verificar a sessão ativa do usuário, etc.

Ele também contém um método usado para obter uma proteção de permissão personalizada, que na verdade depende do AuthGuardServicepróprio

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

Isso nos permite usar o método para registrar alguns guardas personalizados com base no parâmetro de permissões em nosso módulo de roteamento:

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

A parte interessante disso forPermissioné AuthGuardService.guards.push- basicamente, isso garante que a qualquer momento que forPermissionsfor chamado para obter uma classe de guarda personalizada, ela também a armazenará neste array. Isso também é estático na classe principal:

public static guards = [ ]; 

Então, podemos usar essa matriz para registrar todos os guardas - isso está ok, contanto que tenhamos certeza de que, no momento em que o módulo de app registra esses provedores, as rotas foram definidas e todas as classes de guarda foram criadas (por exemplo, verificar a ordem de importação e mantenha esses provedores o mais baixo possível na lista - ter um módulo de roteamento ajuda):

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

Espero que isto ajude.


1
Esta solução me dá um erro estático: ERROR in Error found resolver estaticamente valores de símbolo.
Arninja

Esta solução funcionou para mim para o desenvolvimento, mas quando eu construí o aplicativo para produção, lança um erroERROR in Error during template compile of 'RoutingModule' Function calls are not supported in decorators but 'PermGuardService' was called.
kpacn

Isso funciona com módulos carregados lentamente que têm seus próprios módulos de roteamento?
esmagamento de

2

A solução de @AluanHaddad está apresentando o erro "nenhum provedor". Aqui está uma solução para isso (parece sujo, mas não tenho as habilidades para fazer um melhor).

Conceitualmente, eu registro, como um provedor, cada classe gerada dinamicamente criada por roleGuard .

Portanto, para cada função verificada:

canActivate: [roleGuard('foo')]

Você devia ter:

providers: [roleGuard('foo')]

No entanto, a solução no estado em que se encontra de @AluanHaddad gerará uma nova classe para cada chamada a roleGuard, mesmo se o rolesparâmetro for o mesmo. Usando lodash.memoizea aparência:

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

Observe que cada combinação de funções gera uma nova classe, então você precisa se registrar como um provedor cada combinação de funções. Ou seja, se você tem:

canActivate: [roleGuard('foo')] e canActivate: [roleGuard('foo', 'bar')] você terá que registrar ambos:providers[roleGuard('foo'), roleGuard('foo', 'bar')]

Uma solução melhor seria registrar provedores automaticamente em uma coleção global de provedores internos roleGuard, mas como eu disse, não tenho as habilidades para implementar isso.


Eu realmente gosto dessa abordagem funcional, mas mixin closures com DI (classes) parecem sobrecarga.
BILL de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.