example ejemplo canactivatechild canactivate angular routing angular2-routing

ejemplo - canactivate angular 6 example



Se ejecutan mĂșltiples guardias canActivate cuando falla la primera vez (4)

Tengo una ruta con dos guardias AuthGuard ( AuthGuard y RoleGuard ). El primero ( AuthGuard ) verifica si el usuario ha iniciado sesión y, en caso contrario, redirige a la página de inicio de sesión. La segunda verifica si el usuario tiene un rol definido que tenga permiso para ver la página y, en caso contrario, redirige a la página no autorizada.

canActivate: [ AuthGuard, RoleGuard ] ... export class AuthGuard implements CanActivate { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> { ... this.router.navigate([''/login'']); resolve(false); } export class RoleGuard implements CanActivate { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> { ... this.router.navigate([''/unauthorized'']); resolve(false); }

El problema es que cuando AuthGuard la ruta y no estoy conectado, AuthGuard , que falla y le dice al enrutador que navegue a /login . Sin embargo, a pesar de que AuthGuard falló, RoleGuard ejecuta de todos modos y luego navega a /unauthorized .

En mi opinión, no tiene sentido ejecutar la siguiente guardia si la primera falla. ¿Hay alguna manera de hacer cumplir este comportamiento?


Actualmente, se ejecutarán varios guardias asíncronos (Promesa de retorno u Observable) al mismo tiempo. Abrí un problema para esto: https://github.com/angular/angular/issues/21702

Otra solución a la solución descrita anteriormente es usar rutas anidadas:

{ path: '''', canActivate: [ AuthGuard, ], children: [ { path: '''', canActivate: [ RoleGuard, ], component: YourComponent // or redirectTo // or children // or loadChildren } ] }


Como lo mencionó data propiedad de data @PierreDuc en Route Class junto con un Master Guard, se puede usar para resolver este problema.

Problema

En primer lugar, angular no admite la función para llamar a los guardias en tándem. Entonces, si la primera guardia es asíncrona y está tratando de hacer llamadas ajax, todas las guardias restantes serán despedidas incluso antes de completar la solicitud de ajax en guardia 1.

Me enfrenté al problema similar y así es como lo resolví.

Solución

La idea es crear una protección principal y dejar que la protección principal se encargue de la ejecución de otras protecciones.

La configuración de enrutamiento en este caso, contendrá la protección principal como la única protección .

Para que Master Guard sepa acerca de los guardias que se activarán para rutas específicas, agregue una propiedad de data en Route .

La propiedad de data es un par de valores clave que nos permite adjuntar datos con las rutas.

Luego se puede acceder a los datos en los guardias utilizando el parámetro ActivatedRouteSnapshot del método canActivate en el guard.

La solución parece complicada, pero asegurará el correcto funcionamiento de las guardas una vez que esté integrada en la aplicación.

El siguiente ejemplo explica este enfoque:

Ejemplo

1. Constantes Objeto para mapear todos los guardias de aplicación -

export const GUARDS = { GUARD1: "GUARD1", GUARD2: "GUARD2", GUARD3: "GUARD3", GUARD4: "GUARD4", }

2. Aplicación Guardia -

import { Injectable } from "@angular/core"; import { Guard4DependencyService } from "./guard4dependency"; @Injectable() export class Guard4 implements CanActivate { //A guard with dependency constructor(private _Guard4DependencyService: Guard4DependencyService) {} canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> { return new Promise((resolve: Function, reject: Function) => { //logic of guard 4 here if (this._Guard4DependencyService.valid()) { resolve(true); } else { reject(false); } }); } }

3. Configuración de enrutamiento -

import { Route } from "@angular/router"; import { View1Component } from "./view1"; import { View2Component } from "./view2"; import { MasterGuard, GUARDS } from "./master-guard"; export const routes: Route[] = [ { path: "view1", component: View1Component, //attach master guard here canActivate: [MasterGuard], //this is the data object which will be used by //masteer guard to execute guard1 and guard 2 data: { guards: [ GUARDS.GUARD1, GUARDS.GUARD2 ] } }, { path: "view2", component: View2Component, //attach master guard here canActivate: [MasterGuard], //this is the data object which will be used by //masteer guard to execute guard1, guard 2, guard 3 & guard 4 data: { guards: [ GUARDS.GUARD1, GUARDS.GUARD2, GUARDS.GUARD3, GUARDS.GUARD4 ] } } ];

4. Guardia Maestro

import { Injectable } from "@angular/core"; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from "@angular/router"; //import all the guards in the application import { Guard1 } from "./guard1"; import { Guard2 } from "./guard2"; import { Guard3 } from "./guard3"; import { Guard4 } from "./guard4"; import { Guard4DependencyService } from "./guard4dependency"; @Injectable() export class MasterGuard implements CanActivate { //you may need to include dependencies of individual guards if specified in guard constructor constructor(private _Guard4DependencyService: Guard4DependencyService) {} private route: ActivatedRouteSnapshot; private state: RouterStateSnapshot; //This method gets triggered when the route is hit public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> { this.route = route; this.state = state; if (!route.data) { Promise.resolve(true); return; } //this.route.data.guards is an array of strings set in routing configuration if (!this.route.data.guards || !this.route.data.guards.length) { Promise.resolve(true); return; } return this.executeGuards(); } //Execute the guards sent in the route data private executeGuards(guardIndex: number = 0): Promise<boolean> { return this.activateGuard(this.route.data.guards[guardIndex]) .then(() => { if (guardIndex < this.route.data.guards.length - 1) { return this.executeGuards(guardIndex + 1); } else { return Promise.resolve(true); } }) .catch(() => { return Promise.reject(false); }); } //Create an instance of the guard and fire canActivate method returning a promise private activateGuard(guardKey: string): Promise<boolean> { let guard: Guard1 | Guard2 | Guard3 | Guard4; switch (guardKey) { case GUARDS.GUARD1: guard = new Guard1(); break; case GUARDS.GUARD2: guard = new Guard2(); break; case GUARDS.GUARD3: guard = new Guard3(); break; case GUARDS.GUARD4: guard = new Guard4(this._Guard4DependencyService); break; default: break; } return guard.canActivate(this.route, this.state); } }

Desafíos

Uno de los desafíos en este enfoque es la refactorización del modelo de enrutamiento existente. Sin embargo, se puede hacer en partes ya que los cambios no se rompen.

Espero que esto ayude.


Esto se debe al hecho de que está devolviendo una Promise<boolean> lugar de solo un boolean . Si tuviera que devolver un booleano, no se verificará el RoleGuard . Supongo que esto es un error en angular2 o un resultado esperado de solicitudes asíncronas.

Sin embargo, puede resolver esto con su ejemplo utilizando solo RoleGuard para las URL en las que se requiere un determinado Role , porque supongo que debe iniciar sesión para tener un rol. En ese caso puedes cambiar tu RoleGuard a esto:

@Injectable() export class RoleGuard implements CanActivate { constructor(private _authGuard: AuthGuard) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> { return this._authGuard.canActivate(route, state).then((auth: boolean) => { if(!auth) { return Promise.resolve(false); } //... your role guard check code goes here }); }


No encontré una mejor solución en Internet, pero, usando como guía la mejor respuesta, decido usar solo un protector que incluya ambas solicitudes concatenadas usando Rxjs mergeMap, esto para evitar llamadas duplicadas al mismo punto final. Aquí mi ejemplo, evite el archivo console.log; si lo desea, lo estaba usando para estar seguro de qué se activó primero.

Se llama 1 getCASUsuario para autenticar al usuario (aquí hay una console.log (1) que no puede ver)
2 Tenemos el nombre de usuario
3 Aquí estoy haciendo una segunda solicitud que se activará después de la primera utilizando la respuesta (verdadero)
4 Usando el userName devuelto obtengo los roles para ese usuario

Con esto tengo la solución para la secuencia de llamadas y para evitar llamadas duplicadas. Tal vez podría funcionar para usted.

@Injectable() export class AuthGuard implements CanActivate { constructor(private AuthService : AuthService, private AepApiService: AepApiService) {} canActivate(): Observable<boolean> { return this.AepApiService.getCASUsername(this.AuthService.token) .map(res => { console.log(2, ''userName''); if (res.name) { this.AuthService.authenticateUser(res.name); return true } }) .mergeMap( (res) => { console.log(3, ''authenticated: '' + res); if (res) { return this.AepApiService.getAuthorityRoles(this.AuthService.$userName) .map( res => { console.log(4, ''roles''); const roles = res.roles; this.AuthService.$userRoles = roles; if (!roles.length) this.AuthService.goToAccessDenied(); return true; }) .catch(() => { return Observable.of(false); }); } else { return Observable.of(false); } }) .catch(():Observable<boolean> => { this.AuthService.goToCASLoginPage(); return Observable.of(false); }); } }