Давайте начнем создавать наш проект 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 здесь.
Спасибо за чтение.