[從 0 開始的 Angular 生活]No.40 Angular Router 加上路由守衛 (Route Guards)

前言

延續上一篇的情境,現在有了前台與後台的區分。但實務上後台是不允許未經認證的人隨意進入瀏覽的,通常會搭配一個登入介面,而光靠登入介面還不夠,因為使用者很可能會藉由直接輸入網址的方式瀏覽後台頁面,這時候就需要依賴路由守衛 (Route Guards) 了 。

需求

延續上一份 ngRouteDemo 的範例,現在需求為:

  • 前台 (front) - 一般人可以隨意瀏覽的部分
    • index
      • page1
      • page2
      • page3
  • 後台 (back) - 希望加入路由守衛的部分
    • index
      • page1
      • page2
      • page3

範例參考中文 Angular 官方手冊

路由守衛 (Route Guards)

根據官方文件說明,路由守衛被應用的場合可能會是:

  • 該使用者可能無權導航到目標元件
  • 可能使用者得先登入(認證)
  • 在顯示目標元件前,可能得先獲取某些資料。
  • 在離開元件前,你可能要先儲存修改。
  • 你可能要詢問使用者:你是否要放棄本次更改,而不用儲存它們?

可以藉由路由配置中新增守衛,來處理以上這些應用場合。

路由守衛的返回值

路由守衛會返回一個值,以控制路由器的行為:

  • 如果它返回 true 導航過程會繼續
  • 如果它返回 false 導航過程就會終止,且使用者留在原地
    • 當返回 false 時,也可以告訴路由器導航到別處,例如可以導航到登入頁面
  • 如果它返回 UrlTree 則取消當前的導航,並且開始導航到返回的這個 UrlTree

路由守衛的介面種類

路由器可以支援多種守衛介面:

  • CanActivate - 處理導航到某路由的情況
  • CanActivateChild - 處理導航到某子路由的情況
  • CanDeactivate - 來處理從當前路由離開的情況
  • Resolve - 在路由啟用之前獲取路由資料
  • CanLoad - 處理非同步導航到某特性模組的情況

路由守衛檢查的順序
根據手冊上的描述,在分層路由的每個級別上,可以設定多個守衛。

  • 路由器會先按照從最深的子路由,由下往上檢查的順序來檢查 CanDeactivate() 和 CanActivateChild() 守衛
    • 然後它會按照從上到下的順序檢查 CanActivate() 守衛。
    • 如果特性模組是非同步載入的,在載入它之前還會檢查 CanLoad() 守衛。

如果任何一個守衛返回 false ,其它尚未完成的守衛會被取消,這樣整個導航就被取消了。

這樣看起來最適合這個範例使用的應該就是 CanActivate 了。

CanActivate

所以我們要使用 CanActivate 來保護後台的所有頁面不被未授權的使用者看到。

但是在這個範例中,為了方便示範:

  • 將使用 CanActivate 並把路由守衛回傳值設為 false
    • 藉以證明無法透過輸入網址進入保護頁面

建立路由守衛

輸入指令,以下兩種指令則一即可

1
ng generate guard auth/auth

1
ng g g auth/auth

輸入後 Angular CLI 也會很貼心的問我們要使用哪一種

選擇 CanActivate 後按下 Enter ,發現 Angular CLI 建立了兩隻檔案,分別為測試檔以及主要檔案。

觀察 auth.guard

這支由 Angular CLI 建立的檔案,剛打開時什麼都沒有,而且還會跳出錯誤。

1
2
3
4
5
6
7
8
9
10
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';

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

}

顯然的需要在這裡寫一些東西才行。

於是可以先使用官方提供的範例,觀察一下運作情形,稍後再來修改成我們要的。

  • @angular/router 中 import CanActivate
  • 貼上官方的範例程式

auth.guard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, CanActivate } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
console.log('AuthGuard#canActivate 被觸發了');
return true;
}
}

這樣基本的路由守衛配置就完成了。

匯入路由守衛

接著把剛才設定好的路由守衛匯入 back-routing.module 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { IndexComponent} from './index/index.component';
import { Page1Component } from './page1/page1.component';
import { Page2Component } from './page2/page2.component';
import { Page3Component } from './page3/page3.component';
import { AuthGuard } from '../auth/auth.guard';

const routes: Routes = [
{
path: 'back',
component: IndexComponent,
canActivate: [AuthGuard],
children: [
{
path: 'page1',
component: Page1Component,
},
{
path: 'page2',
component: Page2Component,
},
{
path: 'page3',
component: Page3Component,
}
],
},
];

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

搞定,先運行看看吧!

位於前台時

切換到後台時

手動輸入後台的 URL

成功了,接著我們回過頭來研究 auth.guard.ts 檔內的 canActivate() 吧!

調整 auth.guard 內的 canActivate()

可以參考官方提供的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, CanActivate, Router } from '@angular/router';
import { Observable } from 'rxjs';

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

canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
console.log('AuthGuard#canActivate 被觸發了, 你沒有授權!將跳轉回前台頁面');
this.router.navigate(['/front']);
return false;
}
}

為了使未認證的使用者能導航到指定頁面,我們需要匯入 Router 類別,並且在建構式內實例化,接著使用底下的方法 navigate()

強制返回 false ,並且觀察當觸發路由守衛時,是否會正確的導航到指定頁面。

搞定了!測試看看吧。

點擊後台連結,被導航回前台

嘗試輸入後台子頁面的網址仍被路由守衛攔下,由於畫面相同就不重複張貼了。

小結

於是靠著官方手冊的指引,我們成功地建立了一個路由守衛,使得未通過驗證的人不得訪問後台的網頁。

當然這部分省略了很多東西,但我目的僅為了實作一個簡單的路由守衛範例,並驗證它是真的有效,所以就省略了登入步驟,直接設定不管如何都是 false ,藉以觀察當未通過驗證時是否正確導航到指定目的地。

0%