Angular JWT с доступом к нескольким ролям - PullRequest
4 голосов

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

Моя модель Пользователь имеет следующее:

export class User {
    role: Role[];                // I change - role: Role[] for few roles
    expiresIn: string;
    aud: string;
    iss: string;
    token?: string;
}

export const enum Role {
    Admin = 'admin',
    User = 'user',   
    Engineer = 'engineer'
}

мой бэкэнд дает мой токен с ролями:

//....
role: (2) ["admin", "engineer"]
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ
//....

Если я использую это в методе входа

tokenInfo['http://schemas.microsoft.com/ws/2008/06/identity/claims/role'][0]   - first element in array

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

Я занимаюсь декодированием токенов и получением ролей в сервисе авторизации

signin(username:string, password:string ) {
    return this.http.post<User>(`${environment.apiUrl}${environment.apiVersion}Profile/Login`, {username, password})    
    .pipe(map(user => {
    if (user && user.token) {
      let tokenInfo = this.getDecodedAccessToken(user.token); // decode token
      this.session = {
        token: user.token,
        role: tokenInfo['http://schemas.microsoft.com/ws/2008/06/identity/claims/role'],     - add this [0]
        expiresIn: tokenInfo.exp,
        aud: tokenInfo.aud,
        iss: tokenInfo.iss,            
      }
      localStorage.setItem('currentUser', JSON.stringify(this.session));
      this.currentUserSubject.next(this.session);         
    }
    return this.session;
    }))
} 

метод sigin, например

Login() {
    this.auth.signin(this.signinForm.value.email, this.signinForm.value.password)
        .pipe(first())
        .subscribe(
            data => {
                console.log("User is logged in");
                this.router.navigate(['/dashboard']);
                this.loading = false;
            });
  }

Не уверен, правильно ли я указываю роли множественного доступа

//......
const adminRoutes: Routes = [
{
    path: 'dashboard',
    loadChildren: () => import('./views/dashboard/dashboard.module').then(m => m.DashboardModule),
    canActivate: [AuthGaurd],

},
{
    path: 'books',
    loadChildren: () => import('./views/books/books.module').then(m => m.BooksModule),
    canActivate: [AuthGaurd],
    data: { roles: [Role.Admin] }  <- work fine if 1 role
},
{
    path: 'person',
    loadChildren: () => import('./views/person/person.module').then(m => m.PersonModule),
    canActivate: [AuthGaurd],    
    data: { roles: [Role.Admin, Role.Engineer] }  <- if have 1 role - admin - open
 },
 {
    path: 'eqip',
    loadChildren: () => import('./views/eqip/eqip.module').then(m => m.PersonModule),
    canActivate: [AuthGaurd],    
    data: { roles: [Role.Engineer] }  <- not open becouse only admin role
 }];

const routes: Routes = [
{
    path: '',
    redirectTo: 'applayout-sidebar-compact/dashboard/v1',
    pathMatch: 'full',
},
...
{
    path: '**',
    redirectTo: 'others/404'
}];

@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: true })],
exports: [RouterModule]
})
export class AppRoutingModule { }
//......

и охранную службу

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const url: string = state.url;
const currentUser = this.auth.currentUserValue;    
// in auth.service.ts
// constructor(private http: HttpClient) {
//   this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentUser')));
//   this.currentUser = this.currentUserSubject.asObservable();

// }
// public get currentUserValue(): User {
//   return this.currentUserSubject.value;
// }
if (this.auth.isUserLoggedIn()) {


  // test code
  const ter = route.data.roles.includes(currentUser.role) <- Error now here
  console.log(ter)  



  // main check role code
  // if (route.data.roles && route.data.roles.indexOf(currentUser.role) === -1) {
  //   this.router.navigate(["/"]);
  //   return false;
  // }

  return true;

}
this.auth.setRedirectUrl(url);
this.router.navigate([this.auth.getLoginUrl()]);
return false;

}

токен в localStorage:

aud: "Service"
expiresIn: 1591967261
iss: "USs"
role: ["admin", "engineer"]
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHR....

изменить app-routing.module.ts

@NgModule({
imports: [RouterModule.forRoot(routes, { 
    useHash: true,
    initialNavigation: 'enabled',
    paramsInheritanceStrategy: 'always',
    relativeLinkResolution: 'corrected',
    scrollPositionRestoration: 'enabled',
})],
exports: [RouterModule]

Ошибка

Uncaught (in promise): TypeError: Cannot read property 'includes' of undefined

TypeError: невозможно прочитать свойство 'includes' неопределенного

Ответы [ 3 ]

4 голосов
/ 01 июня 2020

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

Вам нужно использовать const enum, потому что он компилируется до строки.

попробуйте изменить на

export const enum Role {
    Admin = 'admin',
    User = 'user',   
    Engineer = 'engineer'
}

Хотя это имеет и другие последствия. https://www.typescriptlang.org/docs/handbook/enums.html#const -enums

и вместо выполнения indexOf вы можете использовать .includes

route.data.roles.includes(currentUser.role)

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

Возможно, вам потребуется добавить это в конфигурацию вашего маршрутизатора

RouterModule.forRoot([], {
      initialNavigation: 'enabled',
      paramsInheritanceStrategy: 'always', <-- this makes params and data accessible lower down into the tree
      relativeLinkResolution: 'corrected',
      scrollPositionRestoration: 'enabled',
    }),

2 голосов
/ 28 мая 2020

Это действительно зависит от того, как вы обрабатываете свой код AuthGuard. В этом руководстве есть подробное руководство о том, как настроить аутентификацию и авторизацию: https://jasonwatmore.com/post/2018/11/22/angular-7-role-based-authorization-tutorial-with-example

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

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

import { AuthenticationService } from '@/_services';

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
    constructor(
        private router: Router,
        private authenticationService: AuthenticationService
    ) {}

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        const currentUser = this.authenticationService.currentUserValue;
        if (currentUser) {
            // check if route is restricted by role
            if (route.data.roles && route.data.roles.indexOf(currentUser.role) === -1) {
                // role not authorised so redirect to home page
                this.router.navigate(['/']);
                return false;
            }

            // authorised so return true
            return true;
        }

        // not logged in so redirect to login page with the return url
        this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
        return false;
    }
}

Вам также необходимо убедиться, что вы передаете правильные роли в свой AuthGuard.

Если вам нужны более глубокие ограничения в будущем есть также это руководство: Как предотвратить действия по роли пользователя в Angular

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

1 голос
/ 03 июня 2020

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

Измените защиту авторизации на: -

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    const url: string = state.url;
    const currentUser = this.auth.currentUserValue;
    console.log(currentUser);
    if (this.auth.isUserLoggedIn()) {
      if (!route.data.roles || route.data.roles.length === 0) {
        return true;
      }
      if (typeof currentUser.role === 'string' && route.data.roles.includes(currentUser.role)) {
        return true;
      }
      if (Array.isArray(currentUser.role)) {
        for (let i = 0; i < currentUser.role.length; i++) {
          if (route.data.roles.includes(currentUser.role[i])) {
            return true;
          }
        }
      }
      this.router.navigate([this.auth.getLoginUrl()]); //TODO: Change to 403 PAGE (403 forbidden)
      return false;
    }
    this.auth.setRedirectUrl(url);
    this.router.navigate([this.auth.getLoginUrl()]);
    return false;
}
...