Skip to content

Vue Router 導航守衛:守護您的應用程式頁面安全

Vue Router 導航守衛:beforeEach 與 meta

在開發網頁應用時,我們常需要限制某些頁面的存取權限。導航守衛 (Navigation Guards) 就如同網頁的保安或海關,當使用者試圖切換路徑時,守衛會攔下請求並執行檢查,例如:檢查登入狀態、驗證權限,最後決定放行、取消,或是改派到其他頁面。

核心觀念:全域前置守衛 beforeEach

最常用的導航守衛是 router.beforeEach。它會在導航觸發後、進入目標頁面元件前被呼叫。您可以透過 to 參數取得目標路由的資訊。常用的情境如下:

  • 驗證使用者是否已登入(Token 檢查)。
  • 檢查使用者是否有特定職務權限(Role-based Access Control)。
  • 頁面切換時的數據預載或進度條顯示。

前端守衛不等於安全

導航守衛保護的是 使用體驗,不是資料。前端的任何判斷都能被修改,真正的權限必須由 後端在每一支 API 驗證。守衛的價值在於讓使用者不會進到一個註定失敗的頁面。

檔案架構與內容

我們將新增一個 Login.vue 頁面來進行使用者登入。

src/
 ├─ router/
 │   └─ index.js
 ├─ views/
 │   ├─ member/
 │   │   ├─ Profile.vue
 │   │   └─ Orders.vue
 │   ├─ Member.vue
 │   ├─ Login.vue         # 使用者登入
 │   └─ ...
 ├─ App.vue
 └─ main.js

實作步驟:定義需要權限的路由

改寫程式碼

在導航守衛加入之前,我們需要改寫一下程式碼,讓之後的應用更方便。

js
import { createRouter, createWebHistory } from 'vue-router';

const router = createRouter({
...
});

export default router;

先把 createRouter 的結果 存成變數再匯出,之後才有 router 這個實體可以掛上守衛。這也是與 靜態路由 那篇直接 export default createRouter({...}) 的唯一差別。

指定需要定義權限的路由

首先,我們在路由表中使用 meta 欄位來標記哪些路徑需要經過驗證。將需要加入導航守衛的路由,設定 meta: { requiresAuth: true }

js
...

const router = createRouter({
...,
  routes: [
...,
    {
      path: '/member',
      component: () => import('@/views/Member.vue'),
      meta: { requiresAuth: true },
      children: [ 略... ],
    },
...
  ],
});

...

TIP

如果您有 巢狀路由to.meta 會自動合併父層與子層的 meta 標籤,這讓您只需要在父層路由定義 requiresAuth: true,底下所有的子路由都會受到保護。

撰寫攔截邏輯

利用 router.beforeEach 檢查目標路由是否包含 requiresAuth,並確認 localStorage 中是否存在 token。

js
...

const router = createRouter({
...,
});

router.beforeEach((to) => {
  // 1. 檢查目標路由是否需要登入權限
  if (to.meta.requiresAuth) {
    const token = localStorage.getItem('token');

    // 2. 如果有 token,放行進入頁面
    if (token) {
      return true;
    }

    // 3. 無 token,強制重新導向至登入頁
    return {
      path: '/login',
    };
  }
  return true;
});

...

重要觀念

在實務專案中,僅檢查 token 是否存在是不夠的,通常還會呼叫 API 驗證 token 的有效性或解析 JWT 過期時間。

小心無限重導向

如果 登入頁自己也被標記了 requiresAuth,未登入時導向登入頁又會被守衛攔下,再導向登入頁,於是陷入無窮迴圈。除了確認登入頁沒有加上標記,也建議在導向前先判斷目標是不是已經是登入頁。

進階:登入後回到原本想去的頁面

使用者原本要看訂單卻被攔到登入頁,登入完卻被丟回首頁,體驗並不好。做法是把 原路徑記在 query 裡

js
router.beforeEach((to) => {
  if (!to.meta.requiresAuth) return true;

  const token = localStorage.getItem('token');
  if (token) return true;

  // 把原本要去的完整路徑帶上,登入後才知道該回哪裡
  return {
    path: '/login',
    query: { redirect: to.fullPath },
  };
});

登入成功後再讀出來跳回去,沒有這個參數時就走預設首頁:

js
const route = useRoute();
const router = useRouter();

function onLoginSuccess() {
  router.push(route.query.redirect || '/');
}

routerouter 的分工可以參考 動態路由

登入頁面實作

在登入元件中,當使用者成功登入後,我們將 token 存入瀏覽器儲存空間,並引導使用者前往受保護的頁面。

Login.vue

vue
<script setup>
import { useRouter } from "vue-router";

const router = useRouter();

function setToken() {
  localStorage.setItem("token", "user_access_token_sample");
  router.push("/member");
}
</script>

<template>
  <h1>Login</h1>
  <input type="button" value="點我模擬登入" @click="setToken">
</template>

實務上登入狀態通常不只存在 localStorage,也會同步到 Pinia 的 store,讓導覽列與各頁面都能直接讀取使用者資訊。

導航守衛的傳回值說明

在 Vue Router 4 中,beforeEach 的傳回值決定了導航的去向:

  • true:放行導航。
  • false:取消導航,停留在目前頁面。
  • { path: '/login' }:路由物件,往指定的路徑。

不用再寫 next 了

Vue Router 3 時代習慣呼叫 next(),在 Vue Router 4 已改為 回傳值決定結果,不回傳任何值等同放行。兩種寫法混用容易造成導航卡住或執行兩次,新專案請統一使用回傳值。

與 Axios 攔截器的分工

守衛與 Axios 攔截器 常被誤認為重複,實際上是兩道不同時機的防線:

時機負責的人處理什麼
進入頁面前導航守衛沒有憑證就別讓使用者看到空白頁
API 回應後Axios 攔截器Token 用到一半過期,清除憑證並導回登入

兩者一起使用,才能同時顧到 進得去用得下去

總結

透過 路由 Meta 設定導航守衛 的結合,能以極精簡的程式碼管理整站的頁面存取權限。這不僅能防止未授權使用者進入敏感頁面,更能大幅提升 Web App 的使用者體驗。

常見問題

beforeEach 的回傳值代表什麼?

回傳 true 或不回傳任何值表示放行,false 表示取消導航停在原頁,回傳路由物件則改導向指定頁面。Vue Router 4 已不需要再呼叫 next

為什麼守衛會造成無限重導向?

通常是登入頁本身也被標記為需要驗證,未登入時導向登入頁又被守衛攔下再導向登入頁。請確認登入頁沒有 requiresAuth,並在導向前判斷目標是否已是登入頁。

子路由需要每一頁都寫 meta 嗎?

不需要。to.meta 會自動合併父層與子層的設定,在父路由寫一次 requiresAuth 就能保護底下所有子頁面。

登入後要怎麼回到原本想去的頁面?

導向登入頁時把原路徑放進 query,例如:redirect 參數,登入成功後再讀出來 push 回去。沒有該參數時就導向預設首頁。

只檢查前端的 token 就安全了嗎?

不安全。導航守衛只是使用體驗上的攔截,任何人都能修改前端狀態。真正的權限必須由後端在每支 API 驗證,前端守衛只負責避免使用者看到無法使用的畫面。

導航守衛和 Axios 攔截器該用哪一個?

兩者互補。守衛在進入頁面前檢查,避免白畫面;Axios 攔截器 處理 API 回應 401 的情況,例如:Token 在使用中過期時清除憑證並導回登入頁。

延伸閱讀