Маршрутизация Angular2 может активировать и AuthGuard (JWT) с параметром роли пользователя

В этом проекте exaple с аутентификацией JWT мы видим, как чтобы разрешить доступ к какому-либо маршруту только аутентифицированным пользователям:

import { RouterConfig } from '@angular/router';
import { Home } from './home';
import { Login } from './login';
import { Signup } from './signup';
import { AuthGuard } from './common/auth.guard';

export const routes: RouterConfig = [
  { path: '',       component:  Login },
  { path: 'login',  component: Login },
  { path: 'signup', component: Signup },
  { path: 'home',   component: Home, canActivate: [AuthGuard] },
  { path: '**',     component: Login },
];

Я хотел бы сделать шаг дальше и также указать, какая роль пользователя имеет «доступ» к маршруту, но я не знаю, как передать аргумент в canActivate AuthGuard (src). Поэтому я хотел бы добиться чего-то вроде этого (например, у меня две роли: администратор и сотрудник):

  { path: 'home',   component: Home, canActivate: [AuthGuard] },
  { path: 'users',   component: AdminUsers, canActivate: [AuthGuard('Admin')] },
  { path: 'users',   component: Employees, canActivate: [AuthGuard('Employee')] },

Где мой AuthGuard может выглядеть примерно так (где userRole (= Admin или Employee или null) передается параметр AuthGuard):

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private router: Router) {}

  canActivate(userRole) {
    if (!userRole || JWT.user().role == userRole) {
      return true;
    }

    this.router.navigate(['/login']);
    return false;
  }
}

где JWT.user.role - это помощник, который читает роль пользователя, хранящуюся в токене JWT. Есть ли способ сделать что-то похожее на идею выше?


person Kamil Kiełczewski    schedule 15.07.2016    source источник
comment
Мы не можем расширить AuthGuard как AdminAuthGuard и UserAuthGuard и передать их в ловушку canActivate в механическом объявлении.   -  person Arpit Agarwal    schedule 15.07.2016
comment
Я знаю, это способ - это обходной путь. Но я хотел бы знать, существует ли другой способ, похожий на идею вышеупомянутого вопроса.   -  person Kamil Kiełczewski    schedule 15.07.2016
comment
Я так не думаю, так как при объявлении маршрута вы указываете только класс охраны. Однако вы можете передать несколько классов.   -  person Arpit Agarwal    schedule 15.07.2016
comment
Я новичок в Angular5, и я борюсь с той же проблемой. Если ваша проблема решена, не могли бы вы поделиться своим кодом?   -  person Vinay Pandya    schedule 23.02.2018


Ответы (3)


Подпись для CanActivate не позволит вам передать userRole, как вы хотите. https://github.com/angular/angular/blob/2.0.0-rc.4/modules/%40angular/router/src/interfaces.ts#L54

Вероятно, лучше всего делать отдельные классы для каждого случая вашей роли пользователя. Это также руководство в официальных документах: https://angular.io/docs/ts/latest/api/router/index/CanActivate-interface.html

person cienki    schedule 15.07.2016
comment
этот ответ был правильным, в то время, когда я задаю вопрос - это причина, по которой здесь присутствует зеленая «галочка». Но для новой угловой версии есть лучшее решение (читайте ответы ниже) - person Kamil Kiełczewski; 13.10.2016

You can set the data parameter of the route with the role like this

const appRoutes: Routes = [
{ 
  path: 'account/super-secure', 
  component: SuperSecureComponent, 
  canActivate: [RoleGuard], 
  data: { roles: ['super-admin', 'admin'] } 
}];

а затем поместите это в canActivate из RoleGuard:

canActivate(route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): boolean {

    let roles = route.data["roles"] as Array<string>;
    return (roles == null || roles.indexOf("the-logged-user-role") != -1);
}

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

person Arman    schedule 05.10.2016
comment
Когда я пытаюсь использовать ваше решение, я вижу следующую ошибку: Property 'data' does not exist on type 'ActivatedRouteSnapshot'. Я думаю, что ваше решение работает только для старых версий angular2 - person Kamil Kiełczewski; 07.10.2016
comment
@Kamil Kietczewski, решение работает как минимум с angular 2 (~ 2.0.1) и с новым роутером ~ 3.0.1. Возможно, вы используете старый маршрутизатор. - person Arman; 10.10.2016
comment
Я тестировал его для Angular 2.0.0-rc.3 - этой версии я использую в своем проекте - но я попытаюсь обновить свой проект и протестировать ваше решение ... так что наберитесь терпения - person Kamil Kiełczewski; 10.10.2016
comment
если у ваших пользователей может быть несколько ролей, вы можете отредактировать это, чтобы выяснить пересечение массива route.roles и массива user.roles: user.roles.filter(r => route.roles.indexOf(r) !== -1).length > 0 - person morphatic; 25.11.2016
comment
Это должен быть принятый ответ imo. Спасибо, что показали нам этот изящный трюк! - person Riscie; 02.02.2017
comment
@ KamilKiełczewski знает, как реализовать это решение с помощью angular 4, я тестирую, но переменная ролей в canActivate не определена. - person xzegga; 04.04.2017
comment
@xzegga я еще не использовал angular4: P - person Kamil Kiełczewski; 04.04.2017
comment
Кажется, это правильный путь для доступа к маршрутам, но как насчет отображения или скрытия компонентов в зависимости от роли? - person Mese; 06.04.2017
comment
Вот пример того, как использовать data для поиска определенной роли: ryanchenkie.com/angular -authentication-using-route -guards - person cienki; 24.07.2017
comment
Моя большая проблема с этим подходом заключается в том, что он ничего не дает в виде предложения разработчику относительно того, какие свойства передать с data для охраны. Более того, возможно, что несколько охранников захотят использовать одно и то же свойство из data для разных целей. На мой взгляд, эти две причины делают фабричный метод лучше. - person crush; 06.06.2019

ПРИМЕЧАНИЕ: применимо для angular-rc.4 ‹

@ KamilKiełczewski, @NikolayRusev,

  1. добавлены в маршрут дополнительные данные с массивом маршрутов:

...
{
    path: "customers",
    component: CustomersCmp,
    data: { roles: ["admin"] }
},
...

а в CanActivate вы можете получить path из первого параметра, выполнить поиск по тому же пути в конфигурации маршрута и получить описанные вами роли из данных:

public canActivate(route: ActivatedRouteSnapshot): boolean {
    let path = route._urlSegment.pathsWithParams[0].path;
    let roles;

    if (route._routeConfig.path == path) {
        roles = route._routeConfig.data.roles
    } else {
        roles = route._routeConfig.children.find(_route => _route.path == path).data.roles;
    }

    if (...) {
        return true;
    }

    return false;
}

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

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

  1. you can extend router-outlet
person Andrei Shostik    schedule 17.08.2016
comment
Было бы неплохо услышать комментарий от одного из членов команды angular2. На данный момент кажется, что большинство людей делают это по-вашему, Андрей Шостик, что кажется хакерским. Вам нужно прочитать роли при загрузке, а затем пройти свои маршруты и проверить роли в соответствии с маршрутом. Если ограничение доступа к данным в CanActivate является преднамеренным, мы не должны его использовать. и знать правильный путь было бы неплохо. Если это не намеренно, нам нужно знать правильный обходной путь. Похоже, такой же подход: stackoverflow.com/a/39097563/145334 - person Yaroslav Yakovlev; 19.09.2016
comment
К моему предыдущему комментарию эта проблема на github кажется актуальной: github.com/angular/angular/issues / 9001 - person Yaroslav Yakovlev; 19.09.2016
comment
Также отправил проблему именно для этого случая: github.com/angular/angular/issues/11708 - person Yaroslav Yakovlev; 19.09.2016