在上一回中,我們成功地建立了應用的導航系統,使用者可以在登入頁與儀表板之間進行切換。但我們也留下了一個問題:即使沒有登入,只要在網址列手動輸入 /dashboard
,是否任何人都可以直接闖入系統?
今天,我們將為我們的導航系統聘請一位盡忠職守的保全,它會在每一次頁面跳轉前進行檢查,只有持有合法憑證(已登入)的使用者才能被放行。
這個強大的保全,就是 vue-router
提供的「導航守衛 (Navigation Guards)」。
導航守衛,顧名思義,就是在路由發生變化時進行攔截的 Hook。你可以把它想像成一個門禁系統,每當有人試圖從 A 門移動到 B 門時,守衛都會跳出來問幾個問題:
根據回答,守衛可以做出三種決定:
在設定守衛之前,我們需要有一個地方來判斷「使用者是否已登入」。最適合做這件事的,就是我們在 main.js
中已經安裝的狀態管理工具——Pinia。
讓我們在 src/stores
資料夾下建立一個新的檔案 auth.js
:
// src/stores/auth.js
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
// 這邊假設 token 存在 localStorage 中
token: localStorage.getItem('pos-auth-token') || null,
}),
getters: {
isLoggedIn: (state) => !!state.token,
},
actions: {
// 登入成功後呼叫此 action
login(token) {
this.token = token;
localStorage.setItem('pos-auth-token', token);
},
// 登出時呼叫此 action
logout() {
this.token = null;
localStorage.removeItem('pos-auth-token');
},
},
})
這個 authStore
非常簡單:
state
:用 token
來記錄登入憑證。我們從 localStorage
初始化它,這樣使用者刷新頁面後,登入狀態不會遺失。getters
:提供一個方便的 isLoggedIn
來判斷 token
是否存在。actions
:提供 login
和 logout
方法來更新 token
並同步到 localStorage
。(注意:在 Day 17 我們討論過,將 Token 存在 localStorage
有 XSS 風險,HttpOnly
Cookie 是更安全的方案。這邊為了講解,我們暫時採用此方法。)
接下來,我們需要告訴路由器,哪些頁面是需要被保護的。我們可以透過路由的 meta
屬性來附加自訂資料。
修改 src/router/index.js
:
// src/router/index.js
// ... imports
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
// ... login route
{
path: '/dashboard',
name: 'dashboard',
component: DashBoard,
meta: { requiresAuth: true } // 加上這個 meta 屬性
}
]
})
// ...
我們為 /dashboard
路由新增了 meta: { requiresAuth: true }
,就像是給這個房間的門上貼了一張「VIP 限定」的標籤。
萬事俱備,只欠守衛!我們將使用 router.beforeEach
來建立一個「全局前置守衛」。它會在每一次路由跳轉發生之前被觸發。
繼續在 src/router/index.js
中加入以下程式碼:
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '../stores/auth' // 引入我們的 auth store
// ... 其他 imports
const router = createRouter({ ... })
// 設置全局前置守衛
router.beforeEach((to, from, next) => {
const authStore = useAuthStore()
// 檢查目標路由是否需要驗證
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
// 如果需要驗證,但使用者未登入
// 則重導向到登入頁面
next({ name: 'login' })
} else {
// 如果不需要驗證,或使用者已登入,則直接放行
next()
}
})
export default router
守衛邏輯詳解:
router.beforeEach
接收一個回呼函式,該函式有三個參數:to
(即將進入的目標路由物件)、from
(正要離開的路由物件)、next
(一個必須被呼叫的函式,用來解析這個鉤子)。authStore
的實例。to.meta.requiresAuth
(目標路由需要驗證) 並且 !authStore.isLoggedIn
(使用者未登入)。next({ name: 'login' })
,中斷當前的導航,並將使用者重導向到名為 login
的路由。next()
,表示一切正常,允許這次導航。最後,別忘了在登入和登出時呼叫 authStore
的方法!
LoginView.vue
登入成功後,需要呼叫 authStore.login(your_app_token)
。DashBoard.vue
的 logout
方法中,需要呼叫 authStore.logout()
,然後再跳轉路由。恭喜!今天我們成功地為我們的應用程式建立了一套門禁系統。透過「路由守衛」,我們掌握了:
vue-router
中用於權限控制的強大工具。meta
屬性來標記需要保護的路由。router.beforeEach
全局守衛是實現登入驗證邏輯的理想場所。現在,我們已經為後續開發需要權限的功能奠定了堅實的基礎。
明日,Day 20:[Stateの呼吸・壹之型] Pinia入門 - 全域狀態管理。心を燃やせ 🔥!