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