Angular - blokowanie dostępu do ścieżki

Stronę tą wyświetlono już: 13 razy

Wstęp

Różni użytkownicy stworzonej w Angularze aplikacji mogą a nawet i muszą mieć różne uprawnienia. W tym celu użytkownik po zweryfikowaniu danych logowania powinien otrzymać od strony serwera informację zwrotną w postaci JWT tokena. Z kolei tą informację trzeba przechować w local storage-u, który umożliwia przechowywanie danych nawet gdy użytkownik zamknie przeglądarkę. Każdy JWT token ma swoją datę ważności oraz inne informacje, które mogą zostać zdekodowane. To powoduje, że każdy może nadpisać JWT tokena, jednakże w taki sposób nadpisany JWT token nie będzie już ważny albowiem przy weryfikacji jego po stronie serwera wyjdzie na jaw, że został on nadpisany a tym samym dostęp do ścieżki będzie zablokowany.

Na tej podstronie opiszę ogólny zarys używania mechanizmu blokowania dostępu do ścieżek z pominięciem samego JWT tokena bo wymagało by to obsługi od strony serwera jak i użytkownika.

Przygotowanie ścieżek i komponentów pod nie podpiętych

Potrzebne będą cztery komponenty, które zostaną podpięte do ścieżek routingu:

Listing 1
  1. const routes: Routes = [
  2. {
  3. path: 'userTypeChoose',
  4. component: ChooseUserTypeComponent,
  5. children: [
  6. {
  7. path: 'userView',
  8. component: UserViewComponent,
  9. data: { canActivate: 'user' },
  10. canActivate: [AuthGuard]
  11. },
  12. {
  13. path: 'superUserView',
  14. component: SuperUserViewComponent,
  15. data: { canActivate: 'superUser' },
  16. canActivate: [AuthGuard]
  17. },
  18. {
  19. path: 'superExtraUserView',
  20. component: SuperExtraUserViewComponent,
  21. data: { canActivate: 'superExtraUser' },
  22. canActivate: [AuthGuard]
  23. }
  24. ]
  25. }
  26. ];

Pod każdą ścieżkę został podpięty serwis, którego jedynym zadaniem będzie sprawdzanie, czy użytkownik może wleźć na daną ścieżkę, czy też nie. Oto jak deklaracja klasy tego serwisu wygląda:

Listing 2
  1. import { Injectable } from '@angular/core';
  2. import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
  3. import { AuthService } from './auth.service';
  4. @Injectable({
  5. providedIn: 'root'
  6. })
  7. export class AuthGuard implements CanActivate {
  8. constructor(private authService: AuthService,
  9. private router: Router) {
  10. }
  11. canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
  12. if (next.data.canActivate === this.authService.userType) {
  13. return true;
  14. }
  15. return false;
  16. }
  17. }

Jak widać nic ciekawego tutaj nie ma, serwis ten po prostu implementuje interfejs CanActivate , który to z kolei wymusza obsługę metody canActivate. Metoda ta przyjmuje dwa argumenty, z czego pierwszy wykorzystuję by w jakże przebiegły sposób dobrać się do danych podpiętych w routingu do danej ścieżki. Tak się jakoś szczęśliwie składa, że dane te zawierają informacje o typie użytkownika, jaki został w danej chwili wybrany. Istotną rolę odgrywa tutaj również zmienna authService, która to przechowuje informacje o typie wybranego użytkownika. Kod tegoż serwisu wygląda następująco:

Listing 3
  1. import { Injectable } from '@angular/core';
  2. @Injectable({
  3. providedIn: 'root'
  4. })
  5. export class AuthService {
  6. constructor() { }
  7. userType = '';
  8. }

I znów, nic ciekawego, po prostu przechowuję tutaj nazwę typu użytkownika. Wracając jednak jeszcze na chwilę do serwisu AuthGuard i jego metody canActivate to trzeba dopowiedzieć, że gdy metoda ta zwraca false dostęp do ścieżki zostaje zablokowany. Zaś gdy zwraca true dostęp do ścieżki jest przyznany.

Czas najwyższy pogrzebać w kodzie komponentu ChooseUserTypeComponent co też i z najdzikszą rozkoszą czynię zaczynając od kodu HTML:

Listing 4
  1. <div>
  2. <mat-radio-group [(ngModel)]="userType" (change)="changeUserType()">
  3. <mat-radio-button value="user">użytkownik</mat-radio-button>
  4. <mat-radio-button value="superUser">super użytkownik</mat-radio-button>
  5. <mat-radio-button value="superExtraUser">super extra użytkownik</mat-radio-button>
  6. </mat-radio-group>
  7. </div>
  8. <div>
  9. <button mat-raised-button routerLink="/heroes/userView">Widok użytkownika</button>
  10. <button mat-raised-button routerLink="/heroes/superUserView">Widok super użytkownika</button>
  11. <button mat-raised-button [routerLink]="'/heroes/superExtraUserView'">Widok super ekstra użytkownika</button>
  12. </div>

Jak widać wykorzystuję tutaj kilka kontrolek mat-radio-button w celu umożliwienia wyboru użytkownika oraz przyciski, które umożliwią przełączenie się na ścieżkę dostępną dla danego typu użytkownika. Kod klasy komponentu będzie wyglądał mniej więcej tak:

Listing 5
  1. @Component({
  2. selector: 'app-choose-user-type',
  3. templateUrl: './choose-user-type.component.html',
  4. styleUrls: ['./choose-user-type.component.css']
  5. })
  6. export class ChooseUserTypeComponent {
  7. userType = '';
  8. constructor( private authService: AuthService ) {
  9. }
  10. changeUserType() {
  11. this.authService.userType = this.userType;
  12. }
  13. }

Ot i cała filozofia. Teraz po wybraniu jednej z trzech opcji będzie możliwe przejście tylko na ścieżkę, która odpowiada danemu typowi użytkownika.

Komentarze