Angular 4 Multiple Guard — последовательность выполнения

У меня в приложении 2 гарда, AuthGuard и AccessGuard. AuthGuard защищает все страницы, как следует из названия, и сохраняет объект сеанса в GlobalService, а AccessGuard зависит от некоторых данных доступа в объекте сеанса, хранимом AuthGuard в GlobalService.

Проблема возникает, когда AuthGuard возвращает Observable, а затем одновременно AccessGuard выполняет проверку объекта сеанса, который еще не прибыл, и код ломается. Есть ли какой-либо другой способ ограничить выполнение AccessGuard до тех пор, пока не прибудет объект сеанса, или какой-либо другой способ обойти это состояние гонки?

#Примечание. Я не объединял логику AccessGuard с AuthGuard, так как только некоторые маршруты нужно проверять на наличие доступа, в то время как для всех остальных требуется аутентификация. Например, страница «Учетные записи» и страница «БД» доступны для всех, но для «Управления пользователями» и «Панели мониторинга» требуются внешние параметры доступа, которые исходят от объекта сеанса.

export const routes: Routes = [
  {
    path: 'login',
    loadChildren: 'app/login/login.module#LoginModule',
  },
  {
    path: 'logout',
    loadChildren: 'app/logout/logout.module#LogoutModule',
  },
  {
    path: 'forget',
    loadChildren: 'app/forget/forget.module#ForgetModule',
  },{
    path: 'reset',
    loadChildren: 'app/reset/reset.module#ResetModule',
  },

    path: 'pages',
    component: Pages,
    children: [
      { path: '', redirectTo: 'db', pathMatch: 'full' },
      { path: 'db', loadChildren: 'app/pages/db/db.module#DbModule' },
      { path: 'bi', loadChildren: 'app/pages/dashboard/dashboard.module#DashboardModule', canActivate:[AccessableGuard] },
      { path: 'account', loadChildren: 'app/pages/account/account.module#AccountModule' },
      { path: 'um', loadChildren: 'app/pages/um/um.module#UserManagementModule', canActivate:[AccessableGuard] },
    ],
    canActivate: [AuthGuard]
  }
];

export const routing: ModuleWithProviders = RouterModule.forChild(routes);

#EDIT: добавление защитных кодов

Защита авторизации:

canActivate(route:ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean{
  return new Observable<boolean>( observer => {
    this._dataService.callRestful('POST', params.SERVER.AUTH_URL + urls.AUTH.GET_SESSION).subscribe(
        (accessData) => {
          if (accessData['successful']) {
            observer.next(true);
            observer.complete();
            console.log("done");
          }
          else {
            observer.next(false);
            observer.complete();
          }
        });
  });
}

Доступная защита:

canActivate(route:ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean{        
if(this._dataService.getModulePermission(route.routeConfig.path.toUpperCase()) < 2){
        return false;
      }
      return true;
    }

#ПРИМЕЧАНИЕ: _dataService — это GlobalService, в котором хранятся разрешения на доступ от AuthGuard.


person Akul Narang    schedule 10.05.2017    source источник


Ответы (4)


Взгляните на это руководство по Angular (ссылка) . «Если вы использовали API реального мира, может возникнуть некоторая задержка, прежде чем данные для отображения будут возвращены с сервера. Вы не хотите отображать пустой компонент во время ожидания данных.

Предпочтительно предварительно получать данные с сервера, чтобы они были готовы к моменту активации маршрута. Это также позволяет обрабатывать ошибки перед маршрутизацией к компоненту...

Таким образом, вы хотите отложить рендеринг маршрутизируемого компонента до тех пор, пока не будут получены все необходимые данные.

Вам нужен резолвер».

person Fabrício Hedlund    schedule 23.05.2017
comment
Эй, спасибо. Но CanActivateChild очаровал меня. - person Akul Narang; 25.05.2017
comment
@AkulNarang Как вы использовали CanActivate для решения этой проблемы? Кажется, это довольно распространенная проблема с гвардейцами, и нет хорошего способа ее решить. На самом деле не нужно ставить преобразователь на каждый маршрут, который нуждается в этом типе защиты. - person ToddB; 21.09.2017
comment
Какую версию Angular вы используете? - person Akul Narang; 22.09.2017

Я выбрал другой путь --- вложил свои охранники и сделал их зависимыми друг от друга.

У меня есть RequireAuthenticationGuard и RequirePermissionGuard. Для большинства маршрутов они должны работать оба, но есть определенный порядок, который мне требуется.

RequireAuthenticationGuard зависит от моих служб аутентификации, чтобы проверить, аутентифицирован ли текущий сеанс.

RequirePermissionGuard зависит от моих сервисов authZ, чтобы проверить, авторизован ли текущий сеанс для маршрута.

Я добавляю RequireAuthenticationGuard в качестве зависимости конструктора от RequirePermissionGuard и начинаю проверять разрешения только в том случае, если аутентификация была определена.

require-authentication.guard.ts

constructor(
    private userSessionSerivce: UserSessionService) {}

canActivate(
    _route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot,
): Observable<boolean> {
    return this.validateAuthentication(state.url);
}

require-permission.guard.ts

constructor(
    private permissionService: PermissionService,
    /**
    * We use the RequireAuthenticationGuard internally
    * since Angular does not provide ordered deterministic guard execution in route definitions
    *
    * We only check permissions once authentication state has been determined
    */
    private requireAuthenticationGuard: RequireAuthenticatedGuard,
) {}

canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot,
): Observable<boolean> {
    const requiredPermissions: Permission[] = next.data.permissions || [];

    return this.requireAuthenticationGuard
        .canActivate(next, state)
        .pipe(
            mapTo(this.validateAuthorization(state.url, requiredPermissions)),
        );
}
person seangwright    schedule 07.09.2018
comment
должен сказать, что это, вероятно, самый элегантный способ сделать это. - person Ricardo Saracino; 01.03.2019
comment
Я пробовал это, но служба PermitGuard вызывается до завершения authGuardService. - person Vinit Singh; 22.07.2020
comment
Похоже, что если вы соедините их таким образом, вы не должны помещать RequireAuthenticatedGuard в массив CanActivate. Если вы это сделаете, он будет вызван дважды. Один раз маршрутизатором и один раз охраной разрешений. - person Geo242; 13.05.2021
comment
@Geo242 Geo242 Не должно иметь большого значения, сколько раз вызываются эти методы. Важно то, что это дорогие звонки? Если это так, результат может быть кэширован. Кроме того, было бы идеально, если бы источником состояния AuthN/AuthZ был горячий наблюдаемый объект. Таким образом, его можно было прочитать без запуска какой-либо дорогостоящей операции. - person seangwright; 14.05.2021

Использование Master Guard для срабатывания защиты приложений может помочь.

РЕДАКТИРОВАТЬ: Добавление фрагмента кода для лучшего понимания.

Я столкнулся с подобной проблемой, и вот как я ее решил -


Решение

Идея состоит в том, чтобы создать главного охранника и позволить главному охраннику управлять исполнением других охранников.

Конфигурация маршрутизации в этом случае будет содержать главную защиту в качестве единственной защиты.

Чтобы главный сторож знал о сторожах, которые будут активированы для определенных маршрутов, добавьте свойство data в Route.

Свойство data — это пара ключ-значение, которая позволяет нам присоединять данные к маршрутам.

Затем к данным можно получить доступ в сторожах, используя параметр ActivatedRouteSnapshot метода canActivate в стороже.

Решение выглядит сложным, но после интеграции в приложение оно обеспечит правильную работу охранников.

Следующий пример объясняет этот подход -


Пример

<сильный>1. Объект констант для сопоставления всех средств защиты приложений -

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

<сильный>2. Защита приложений –

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. Конфигурация маршрутизации —

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. Главный страж -

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

Проблемы

Одной из проблем при таком подходе является рефакторинг существующей модели маршрутизации. Однако это можно сделать по частям, так как изменения не являются критическими.

Надеюсь, это поможет.

person planet_hunter    schedule 07.12.2017
comment
это здорово, но что, если нам нужно, чтобы оба охранника прошли верно? Я реализовал это решение, и маршрут можно активировать до тех пор, пока один из охранников возвращает true, что как бы сводит на нет идею наличия нескольких охранников. - person ChumiestBucket; 05.02.2020
comment
Я думаю, что вы что-то упускаете, этот подход работает как условие AND, поэтому, если какой-либо из охранников выйдет из строя, маршрут не будет поражен. Если вы видите метод executeGuards, он вызывается рекурсивно, только если предыдущий охранник проходит true или promise<true> - person planet_hunter; 06.02.2020

Просто создайте основную защиту, которая вводит вспомогательные защиты, вот пример:

app.guard.ts

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { GuardA } from '...';
import { GuardB } from '...';

@Injectable({
    providedIn: 'root',
})
export class AppGuard implements CanActivate {

    constructor(
        // inject your sub guards
        private guardA: GuardA,
        private guardB: GuardB,
    ) {
    }

    public async canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
        for (const guard of this.getOrderedGuards()) {
            if (await guard.canActivate(next, state) === false) {
                return false;
            }
        }
        return true;
    }

 // -> Return here the sub guards in the right order
    private getOrderedGuards(): CanActivate[] {
        return [
            this.guardA,
            this.guardB,
        ];
    }
}

Затем в вашем приложении-routing.module.ts

const routes: Routes = [
    {
        path: 'page',
        loadChildren: './pages.module#PageModule',
        canActivate: [AppGuard],
    }
];

Конечно, вы должны управлять своими модулями так, чтобы охранники были предоставлены (понять вводимые) в ваш AppGuard.

person Flavien Volken    schedule 13.11.2019