Давайте начнем создавать наш проект Angular со следующей команды:

# "--routing true" auto generate route module
ng new angular-guards --routing true

Затем давайте откроем и выполним проект:

cd angular-guards
npm start

Если все в порядке, при попытке доступа к http://localhost:4200/ вы должны увидеть такую ​​страницу:

Создание структуры проекта

Теперь давайте создадим все файлы, которые мы будем использовать в этом исследовании.

# Creating home page
ng generate component home

# Creating admin page
ng generate component admin

# Creating unauthorized page
ng generate component unauthorized

# Creating role model
ng generate enum role

# Creating has-role guard
ng generate guard has-role
? Which type of guard would you like to create? CanActivate

Давайте отредактируем наш unauthorized.component.html, чтобы показать «Вы не авторизованы!» вместо его дефолтного «авторизованные работы!».

<p>You're not authorized!</p>

А также отредактируйте app.component.html так, чтобы были только наши router-outlet, где наши компоненты будут отображаться в Router.

<router-outlet></router-outlet>

Если вы увидите свою страницу сейчас, они будут пустыми.

Создание маршрутов

Если вы попытаетесь получить доступ к любому маршруту прямо сейчас, скажем, /admin, вы увидите, что этого не происходит. Это потому, что мы еще не зарегистрировали маршрут в модуле маршрута.
Для этого мы отредактируем файл app-routing.module.ts, как показано ниже.

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdminComponent } from './admin/admin.component';
import { HomeComponent } from './home/home.component';
import { UnauthorizedComponent } from './unauthorized/unauthorized.component';

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  { path: 'admin', component: AdminComponent },
  { path: 'unauthorized', component: UnauthorizedComponent },
];

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

Объяснение кода

Сначала мы импортируем RouterModule и Routes. Таким образом, приложение может иметь возможность маршрутизации.

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

Здесь у нас есть два свойства:

  • path: строка, соответствующая URL-адресу в адресной строке браузера.
  • component: компонент, который маршрутизатор должен отображать, когда пользователь переходит по этому пути.

Строка { path: '', redirectTo: 'home', pathMatch: 'full' } в строке 10 говорит, что когда путь пуст (localhost:4200/‹path›), Angular перенаправит на /home путь (localhost:4200/home).

Тег @NgModule инициализирует маршрутизатор и начинает отслеживать изменения местоположения браузера.

Строка imports: [RouterModule.forRoot(routes)] добавляет RouterModule в массив imports AppRoutingModule и настраивает его с помощью массива routes, который мы только что создали. Подробнее о методе forRoot можно посмотреть здесь.

Далее, в строке exports: [RouterModule], мы экспортируем RouterModule, чтобы он был доступен во всем приложении.

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

Служба аутентификации

Здесь мы начнем писать код-заполнитель для нашей системы аутентификации.

В файле roles.ts будут все наши роли приложения. В данном примере только двое, пользователь и админ.

export enum Role {
  USER,
  ADMIN,
}

А файл auth.service.ts будет содержать только метод getUserRole, который мы будем использовать для подделки вызова API для получения роли для этого пользователя.

import { Injectable } from '@angular/core';
import { Role } from './role';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  constructor() { }

  getUserRole(): Role {
    /**
    * fake an API call
    */
    return Role.USER;
  }
}

Объяснение кода

Здесь мы создаем метод getUSerRole, который возвращает только Role USER, чтобы мы могли проверить, какая роль принадлежит нашему пользователю.

В реальной жизни это, вероятно, произойдет из вызова API. Но для изучения предлагаем только, мы притворяемся.

Имеет роль охранника

Теперь мы можем перейти к нашему охраннику по имени has-role.

Здесь мы проверим, есть ли у пользователя роль для доступа к пути.
А вот код:

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';
import { Role } from './role';

export const hasRoleGuard: CanActivateFn = (route, state) => {
  const router: Router = inject(Router);
  const userRole: Role = inject(AuthService).getUserRole();
  const expectedRoles: Role[] = route.data['roles'];

  const hasRole: boolean = expectedRoles.some((role) => userRole === role);

  return hasRole || router.navigate(['unauthorized']);
};

Объяснение кода

Прежде всего, вы должны заметить, что когда мы использовали команду ng generate guard has-role в начале этого исследования, Angular CLI спросил нас, какой тип защиты мы хотели бы использовать, и мы выбрали CanActivate. Когда мы выбрали эту опцию, Angular CLI уже поместил нам тип CanActivateFn в функцию hasRoleGuard.

Обратите внимание, что если интерфейс командной строки Angular помещает класс CanActivate вместо функции CanActivateFn, возможно, вы используете старую версию Angular.
Начиная с Angular 15.2 запись защиты как класса устарела , и нам нужно писать как функцию благодаря функции inject.

Затем мы используем функцию inject, чтобы внедрить Route и наш AuthService в нашу защиту. Подробнее о функции inject можно прочитать здесь.

Затем мы поместим роль пользователя в userRole и поместим роли, которые должны быть у пользователя для доступа к этому пути, в массив expectedRoles — мы увидим больше об этом в следующем разделе.

И затем мы можем проверить с помощью функции массива some, являются ли некоторые из наших expectedRoles userRole, и мы сохраняем эту информацию в hasRole.

Наконец, мы можем вернуть значение hasRole. Если это значение ложно, то пользователь будет перенаправлен на страницу unauthorized. В противном случае пользователь имеет право перейти по этому пути.

Этого недостаточно, чтобы начать работать. Нам все еще нужно изменить наш app-routing.module.ts, чтобы получить значение от нашей защиты.

Добавление нашей охраны на наши маршруты

Теперь, когда мы уже сделали нашу защиту, мы можем вернуться к нашему app-routing.module.ts, чтобы отредактировать его и добавить нашу защиту на путь, который мы хотим защитить.

Мы отредактируем наш путь admin, и теперь код будет выглядеть так:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdminComponent } from './admin/admin.component';
import { hasRoleGuard } from './has-role.guard';
import { HomeComponent } from './home/home.component';
import { Role } from './role';
import { UnauthorizedComponent } from './unauthorized/unauthorized.component';

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  {
    path: 'admin',
    component: AdminComponent,
    canActivate: [hasRoleGuard],
    data: {
      roles: [ Role.ADMIN ]
    }
  },
  { path: 'unauthorized', component: UnauthorizedComponent },
];

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

Объяснение кода

Добавляем в наш путь admin два свойства:

  • canActivate: массив CanActivateFn для определения того, разрешено ли текущему пользователю активировать компонент.
  • data: дополнительные данные, которые мы можем определить для компонента. Эти данные должны иметь строку типа key и value любого типа.

В свойстве canActivate мы помещаем наше hasRoleGuard, чтобы Route мог проверить, может ли пользователь получить доступ к этому компоненту.
А в data мы создаем массив Role и помещаем в него все роли, которые мы хотим пользователь должен иметь доступ к этому компоненту.

И теперь наш код const expectedRoles: Role[] = route.data['roles']; из has-role.guard.ts имеет смысл. Мы получаем data roles, который мы только что создали на нашем маршруте, и он представляет собой массив Role, и сохраняем этот массив в константе expectedRoles для проверки, есть ли у пользователя одна из этих ролей.

Теперь, если вы попытаетесь получить доступ к пути /admin, вы увидите, что мы будем перенаправлены на путь unauthorized, потому что у пользователя есть Role USER вместо ADMIN.

Теперь, если мы вернемся к нашему auth.service.ts и отредактируем наш метод getUserRole, чтобы он возвращал ADMIN вместо USER, вы увидите, что можете получить доступ к пути /admin.

import { Injectable } from '@angular/core';
import { Role } from './role';

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  constructor() { }

  getUserRole(): Role {
    /**
    * fake an API call
    */
    return Role.ADMIN;
  }
}

Заключительные соображения

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

Вы можете увидеть больше об Angular Router и его охранниках здесь, а весь код с некоторыми дополнениями — в моем репозитории GitHub здесь.

Спасибо за чтение.