iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
Vue.js

打造銷售系統30天修練 - 全集中・Vue之呼吸系列 第 19

Day 19:[Routerの呼吸・貳之型] 路由守衛 - 權限控制與保護

  • 分享至 

  • xImage
  •  

在上一回中,我們成功地建立了應用的導航系統,使用者可以在登入頁與儀表板之間進行切換。但我們也留下了一個問題:即使沒有登入,只要在網址列手動輸入 /dashboard,是否任何人都可以直接闖入系統?

今天,我們將為我們的導航系統聘請一位盡忠職守的保全,它會在每一次頁面跳轉前進行檢查,只有持有合法憑證(已登入)的使用者才能被放行。

這個強大的保全,就是 vue-router 提供的「導航守衛 (Navigation Guards)」。

什麼是導航守衛?

導航守衛,顧名思義,就是在路由發生變化時進行攔截的 Hook。你可以把它想像成一個門禁系統,每當有人試圖從 A 門移動到 B 門時,守衛都會跳出來問幾個問題:

  • 你是誰?(你登入了沒有?)
  • 你要去哪裡?(目標頁面是哪裡?)
  • 你有權限進去嗎?(這個頁面需要特定權限嗎?)

根據回答,守衛可以做出三種決定:

  1. 放行:允許使用者前往目標頁面。
  2. 攔截:取消這次導航,讓使用者留在原地。
  3. 重導向:將使用者引導到另一個頁面(例如:登入頁)。

1.建立一個認證狀態儲存 (Auth Store)

在設定守衛之前,我們需要有一個地方來判斷「使用者是否已登入」。最適合做這件事的,就是我們在 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:提供 loginlogout 方法來更新 token 並同步到 localStorage

(注意:在 Day 17 我們討論過,將 Token 存在 localStorage 有 XSS 風險,HttpOnly Cookie 是更安全的方案。這邊為了講解,我們暫時採用此方法。)

2.標記需要保護的路由

接下來,我們需要告訴路由器,哪些頁面是需要被保護的。我們可以透過路由的 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 限定」的標籤。

3.實現全局前置守衛

萬事俱備,只欠守衛!我們將使用 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

守衛邏輯詳解

  1. router.beforeEach 接收一個回呼函式,該函式有三個參數:to(即將進入的目標路由物件)、from(正要離開的路由物件)、next(一個必須被呼叫的函式,用來解析這個鉤子)。
  2. 在函式內部,我們取得了 authStore 的實例。
  3. 我們的判斷條件是:to.meta.requiresAuth (目標路由需要驗證) 並且 !authStore.isLoggedIn (使用者未登入)。
  4. 如果條件成立,我們呼叫 next({ name: 'login' }),中斷當前的導航,並將使用者重導向到名為 login 的路由。
  5. 如果條件不成立,我們呼叫 next(),表示一切正常,允許這次導航。

最後,別忘了在登入和登出時呼叫 authStore 的方法!

  • LoginView.vue 登入成功後,需要呼叫 authStore.login(your_app_token)
  • DashBoard.vuelogout 方法中,需要呼叫 authStore.logout(),然後再跳轉路由。

總結

恭喜!今天我們成功地為我們的應用程式建立了一套門禁系統。透過「路由守衛」,我們掌握了:

  1. 導航守衛vue-router 中用於權限控制的強大工具。
  2. 使用 Pinia 來集中管理應用的認證狀態是一種常見且高效的模式。
  3. 我們可以利用路由的 meta 屬性來標記需要保護的路由。
  4. router.beforeEach 全局守衛是實現登入驗證邏輯的理想場所。

現在,我們已經為後續開發需要權限的功能奠定了堅實的基礎。

明日,Day 20:[Stateの呼吸・壹之型] Pinia入門 - 全域狀態管理。心を燃やせ 🔥!


上一篇
Day 18:[Routerの呼吸・壹之型] 導航系統 - 理解SPA路由機制
系列文
打造銷售系統30天修練 - 全集中・Vue之呼吸19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言