Передайте параметр в охорону маршруту


102

Я працюю над додатком, який має багато ролей, і мені потрібно використовувати охоронці, щоб блокувати навігацію до частин програми на основі цих ролей. Я усвідомлюю, що можу створити окремі класи охорони для кожної ролі, але волію мати один клас, в який я міг би якось передати параметр. Іншими словами, я хотів би мати можливість зробити щось подібне до цього:

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

Але оскільки все, що ви передаєте, - це типове ім’я вашого охоронця, не можете придумати, як це зробити. Чи варто мені просто кусати кулю і писати окремі класи охорони на роль і руйнувати мою ілюзію елегантності, маючи натомість один параметризований тип?

Відповіді:


218

Замість використання forRole()ви можете зробити це:

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

і використовуйте це у своєму RoleGuard

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

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

Відмінний варіант також, дякую. Мені подобається заводський підхід Алуана, хоча він трохи кращий, хоча, але дякую, що розширив мої можливості щодо можливостей!
Брайан Нойес

7
Я думаю, що безпека цих даних не має значення. Ви повинні використовувати автентифікацію та авторизацію на стороні сервера. Я думаю, суть охоронця полягає не в тому, щоб повністю захистити вашу заявку. Якщо хтось "зламає" його і перейде на сторінку адміністратора, він / вона не отримає захищені дані з сервера, лише побачивши компоненти адміністратора, що, на мою думку, нормально. Я думаю, що це набагато краще рішення, ніж прийняте. Прийняте рішення порушує введення залежності.
bucicimaci

1
Це хороше рішення, і воно чудово працює в моєму загальному AuthGuard.
SAV

3
Це рішення чудово працює. Моє питання полягає в тому, що він спирається на рівень непрямості. Ні в якому разі хтось, дивлячись на цей код, не зрозуміє, що rolesоб'єкт і охорона маршруту пов'язані, не знаючи, як код працює достроково. Це жахливо, що Angular не підтримує спосіб зробити це більш декларативно. (Щоб було зрозуміло, це те, що я прикриваю Angular, не це цілком розумне рішення.)
KhalilRavanna

1
@KhalilRavanna дякую, так, але я використовував це рішення багато разів тому, і я перейшов до іншого рішення. Моє нове рішення - це один RoleGaurd і один файл з ім'ям "access.ts" з картою <URL, AccessRoles> постійною в ньому, потім я використовую його в RoleGaurd. Якщо ви хочете контролювати доступ у своїй програмі, я вважаю, що цей новий спосіб набагато кращий, особливо коли у вас більше одного додатка в одному проекті.
Хасан Бехешті,

11

Ось мій приклад щодо цього та можливе рішення проблеми відсутнього постачальника.

У моєму випадку ми маємо охоронця, який приймає дозвіл або список дозволів як параметр, але це те саме, що має роль.

У нас є клас спілкування з охоронцями з дозволом або без нього:

@Injectable()
export class AuthGuardService implements CanActivate {

    checkUserLoggedIn() { ... }

Це стосується перевірки активного сеансу користувача тощо.

Він також містить метод, який використовується для отримання власного охоронного дозволу, який насправді залежить від самого AuthGuardServiceсебе

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

Це дозволяє нам використовувати метод для реєстрації деяких користувацьких захистів на основі параметра дозволів у нашому модулі маршрутизації:

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

Цікавою частиною forPermissionє AuthGuardService.guards.push- це в основному гарантує, що будь-який час forPermissionsвикликається для отримання власного класу охорони, він також буде зберігати його в цьому масиві. Це також статично для основного класу:

public static guards = [ ]; 

Тоді ми можемо використовувати цей масив для реєстрації всіх охоронців - це нормально, доки ми переконаємось, що до того моменту, як модуль програми реєструє цих постачальників, маршрути були визначені та створені всі класи охорони (наприклад, перевірити порядок імпорту та утримуйте цих провайдерів якомога нижче у списку - наявність модуля маршрутизації допомагає):

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

Сподіваюся, це допомагає.


1
Це рішення дає мені статичну помилку: ПОМИЛКА в помилці зустрічається при статичному вирішенні значень символів.
Arninja

Це рішення спрацювало для мене для розробки, але коли я ERROR in Error during template compile of 'RoutingModule' Function calls are not supported in decorators but 'PermGuardService' was called.
створюю

Чи працює це з модулями лінивого завантаження, які мають власні модулі маршрутизації?
розчавити

2

Рішення @ AluanHaddad дає помилку "немає постачальника". Ось виправлення для цього (він відчуває себе брудно, але мені бракує навичок, щоб зробити його кращим).

Концептуально я реєструю як постачальник кожен динамічно сформований клас, створений roleGuard .

Отже, для кожної перевіреної ролі:

canActivate: [roleGuard('foo')]

ти повинен мати:

providers: [roleGuard('foo')]

Однак рішення @ AluanHaddad як є генерує новий клас для кожного виклику roleGuard, навіть якщо rolesпараметр однаковий. Його використання lodash.memoizeвиглядає так:

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

Зверніть увагу, кожна комбінація ролей генерує новий клас, тому вам потрібно зареєструвати як постачальника кожну комбінацію ролей. Тобто якщо у вас є:

canActivate: [roleGuard('foo')]і canActivate: [roleGuard('foo', 'bar')]вам доведеться зареєструвати обидва:providers[roleGuard('foo'), roleGuard('foo', 'bar')]

Кращим рішенням буде автоматична реєстрація провайдерів у глобальній колекції провайдерів всередині roleGuard, але, як я вже говорив, мені бракує навичок, щоб це реалізувати.


Мені дуже подобається такий функціональний підхід, але закриття мікшину з DI (класи) виглядає як накладне.
БІЛЛ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.