本篇介紹Vue Nuxt 3使用Page Meta來管理頁面的角色存取。
目前專案的路由角色權限控管,例如/home/member,是寫死在Middleware middleware/auth.global.ts中,但若頁面一多,繁雜的路由與角色存取對應程式碼會很難管理,所以下面把可存取路由的角色權限定義在各頁面的Page Meta,之後控制頁面的存取就不需要去更動Middleware。要再次強調的是,光靠前端頁面存取控制並無任何安全性,真正的把關還是要後端API進行控管。
事前要求
參考「Vue Nuxt 3 側邊選單權限控管」,了解角色權限控制。
定義Page Meta
在頁面中使用definePageMeta定義自訂屬性requireAuth和roles。requireAuth值為boolean,在Middleware用來判斷路由到此頁面的使用者是否需要驗證。roles為字串陣列,用來設定可存取此頁面的角色。
[會員管理]頁面需要驗證,且使用者必須有[admin]角色。
pages/home/member.vue
definePageMeta({
requireAuth: true,
roles: ['admin']
})
[出口業務]頁面需要驗證,使用者必須是[admin]或[user]角色。
pages/home/export/index.vue
definePageMeta({
requireAuth: true,
roles: ['admin', 'user']
})
[訂單明細]頁面需要驗證,使用者必須是[admin]或[user]角色。
pages/home/export/[orderId].vue
definePageMeta({
middleware: 'check-order-id',
requireAuth: true,
roles: ['admin', 'user']
})
其他頁面沒有定義requireAuth則為false,所以會直接通過Middleware中的檢核。
修改Middleware
調整middleware/auth.global.ts,從to.meta取得Page Meta參數requireAuth判斷該頁面是否需要驗證;修改原本檢查路由與使用者角色的檢核部分,改從參數roles取得頁面設定的可存取角色
middleware/auth.global.ts
import { verifyUser } from "~/server/utils/auth";
export default defineNuxtRouteMiddleware(async (to) => {
if (to.path === "/login") return; // 登入頁面不用驗證
// 頁面definePageMeta沒定義requireAuth: true不用驗證
if (!to.meta.requireAuth) return;
const isLogin = useAuth();
const userState = useUser();
// 檢查SSR路由登入驗證
if (import.meta.server) {
const token = useCookie("token").value;
if (!token) {
return navigateTo("/login");
}
// 同步前端登入狀態
isLogin.value = true;
userState.value = await verifyUser(token);
}
// 檢查SPA路由登入驗證
if (import.meta.client) {
if (!isLogin.value) {
return navigateTo("/login");
}
}
// 檢查路由頁面可存取角色
if (to.meta.roles && userState.value) {
const allowedRoles = to.meta.roles as string[];
if (!allowedRoles.includes(userState.value?.role)) {
return navigateTo("/home");
}
}
});
導覽列使用者名稱
經過以上調整後,未定義requireAuth的頁面不用登入就可在瀏覽器網誌列輸入直接進入,例如/home或/about,未登入的使用者在導覽列顯示使用者名稱為guest及[登入]按鈕,所以修改components/Header.vue如下。
components/Header.vue
<template>
<header class="header">
<!--...-->
<!-- 右上角使用者資訊 -->
<div class="nav-right" v-if="user">
{{ user.name }} ({{ user.role }})
<button @click="logout">登出</button>
</div>
<div v-else>guest
<button @click="login">登入</button>
</div>
</header>
</template>
<script setup lang="ts">
const user = useUser()
const isLogin = useAuth()
const logout = async () => {
await $fetch('/api/auth/logout', { method: 'POST' })
isLogin.value = false
user.value = null
navigateTo('/login')
}
const login = () => {
navigateTo('/login')
}
</script>
<!--...-->
測試
在瀏覽器網址輸入http://localhost:3000/about效果如下。
使用Page Meta管理角色存取的好處是[規則]跟[邏輯]分離:存取規則放在頁面本身,邏輯判斷放在Middleware,這樣提高了可維護性,程式碼也更容易擴充。
沒有留言:
張貼留言