開箱即用的 GoWind Admin|風行,企業(yè)級前后端一體中后臺框架:前端權限控制

開箱即用的 GoWind Admin|風行,企業(yè)級前后端一體中后臺框架:前端權限控制

在企業(yè)級中后臺系統(tǒng)中,前端權限控制是保障數(shù)據(jù)安全、規(guī)范用戶操作邊界的核心能力。風行·GoWind Admin 前端權限控制核心聚焦于功能權限管控,根據(jù)控制粒度的不同,分為「頁面級權限」和「按鈕級權限」兩大模塊,覆蓋從“頁面訪問”到“操作執(zhí)行”的全鏈路權限管控需求。本文將詳細拆解兩種權限的實現(xiàn)原理、啟用方式、核心代碼及最佳實踐,助力開發(fā)者快速落地權限管控方案。

一、頁面級權限:管控頁面訪問邊界

頁面級權限的核心目標是控制用戶能否訪問特定頁面,主要通過「菜單隱藏」和「路由攔截」兩種手段實現(xiàn)——未授權用戶既無法在側(cè)邊欄看到目標菜單,也無法通過直接輸入URL跳過菜單訪問頁面,進而實現(xiàn)對用戶訪問“財務報表”“人事管理”等核心頁面的精準管控。

根據(jù)路由配置的管控主體不同,頁面級權限分為「后端控制」和「前端控制」兩種模式,適配不同復雜度的權限場景,開發(fā)者可根據(jù)項目需求靈活選擇。

1.1 后端控制模式(推薦復雜權限場景)

核心原理

采用“后端動態(tài)下發(fā)路由”的方式實現(xiàn)權限管控:前端啟動時僅初始化通用路由(如登錄頁、403頁),登錄后通過調(diào)用后端接口獲取「符合當前用戶權限的路由配置數(shù)據(jù)」,前端將該數(shù)據(jù)轉(zhuǎn)換為框架可識別的路由結(jié)構(gòu)后,通過 router.addRoute 動態(tài)添加到路由實例中,最終實現(xiàn)頁面權限的動態(tài)適配。

適用場景與優(yōu)缺點

  • 適用場景:企業(yè)級復雜權限系統(tǒng)(如多租戶、多角色動態(tài)配置、權限頻繁變更)、需要統(tǒng)一管控路由配置的場景。
  • 優(yōu)點:權限配置完全由后端統(tǒng)一管控,前端無需修改代碼即可適配權限變更,擴展性強、維護成本低。
  • 缺點:需前后端協(xié)同定義路由數(shù)據(jù)格式,前端需開發(fā)路由數(shù)據(jù)轉(zhuǎn)換邏輯,初期開發(fā)成本略高。

啟用步驟與核心配置

1. 修改環(huán)境變量:

編輯前端項目根目錄的.env 配置文件,將 VITE_ROUTER_ACCESS_MODE 的值設置為 backend,啟用后端控制模式:

# 路由的訪問模式:frontend(前端控制),backend(后端控制)
VITE_ROUTER_ACCESS_MODE=backend
2.核心邏輯代碼:

后端路由的獲取與動態(tài)生成邏輯封裝在 src/router/access.ts 文件中,核心代碼如下:

async function generateAccess(options: GenerateMenuAndRoutesOptions) {
  // 其他初始化邏輯...

  // 核心:根據(jù)訪問模式生成可訪問路由,后端模式下通過接口拉取路由數(shù)據(jù)
  return await generateAccessible(preferences.app.accessMode, {
    ...options,
    // 異步拉取后端路由配置的方法
    fetchMenuListAsync: async () => {
      // 加載中提示
      message.loading({
        content: `${$t('common.loadingMenu')}...`,
        duration: 1.5,
      });
      // 調(diào)用后端接口獲取路由數(shù)據(jù)(defRouterService為后端生成的API客戶端)
      const data = (await defRouterService.ListRoute({})) ?? [];
      // 返回路由列表(需與后端約定數(shù)據(jù)結(jié)構(gòu):items為路由數(shù)組)
      return data.items ?? [];
    },
    // 其他配置...
  });
}
關鍵注意事項
  1. 前后端數(shù)據(jù)格式約定:需確保后端返回的路由數(shù)據(jù)包含 path(路由路徑)、name(路由名稱)、meta(菜單信息/權限標識)等核心字段,否則前端無法正常轉(zhuǎn)換為可用路由。
  2. 異常處理:需補充接口請求失敗的降級邏輯(如返回空路由列表,引導用戶重新登錄),避免因后端服務異常導致前端路由加載失敗。

1.2 前端控制模式(適配簡單固定角色場景)

核心原理

采用“前端預定義路由權限”的方式實現(xiàn)管控:前端代碼中固定定義所有路由,并為需要權限管控的路由配置 authority 字段(指定可訪問的角色碼);系統(tǒng)初始化時僅加載通用路由,用戶登錄后獲取其角色信息,通過角色匹配篩選出可訪問的路由,再通過 router.addRoute 動態(tài)添加到路由實例中,實現(xiàn)權限過濾。

適用場景與優(yōu)缺點

  • 適用場景:角色體系固定(如僅超級管理員、普通管理員、用戶三類角色)、權限變更頻率低的簡單系統(tǒng)。
  • 優(yōu)點:前后端耦合度低,前端獨立管控權限,開發(fā)簡單、調(diào)試便捷。
  • 缺點:權限變更需修改前端代碼并重新部署,擴展性差;角色數(shù)量增多時,路由權限配置維護成本會顯著上升。

啟用步驟與核心配置

1. 修改環(huán)境變量:

編輯 .env 配置文件,將 VITE_ROUTER_ACCESS_MODE 的值設置為 frontend,啟用前端控制模式:

# 路由的訪問模式:frontend(前端控制),backend(后端控制)
VITE_ROUTER_ACCESS_MODE=frontend
2. 配置路由權限標識:

在前端預定義的路由數(shù)組中,為需要權限管控的路由添加 meta.authority 字段,值為后端約定的角色碼數(shù)組(如['super'] 表示僅超級管理員可訪問):

// 示例:系統(tǒng)管理模塊路由配置(src/router/routes/system.ts)
const system: RouteRecordRaw[] = [
  {
    path: '/system',
    name: 'System',
    component: Layout, // 布局組件
    meta: {
      title: '系統(tǒng)管理', // 菜單名稱
      authority: ['super', 'admin'], // 可訪問角色:超級管理員、普通管理員
      icon: 'icon-settings', // 菜單圖標
    },
    children: [
      {
        path: 'user',
        name: 'SystemUser',
        meta: {
          title: '用戶管理',
          authority: ['super'], // 僅超級管理員可訪問
        },
      },
    ],
  },
];

權限規(guī)則說明:未配置 authority 字段:所有用戶可見(如首頁、幫助中心)。

3. authority 為空數(shù)組([]):

所有用戶不可見(如內(nèi)部測試頁面)。

4. authority 為角色碼數(shù)組:

僅數(shù)組內(nèi)角色的用戶可見。

5. 用戶角色數(shù)據(jù)適配:

前端權限篩選依賴用戶角色信息,需確保后端返回的用戶數(shù)據(jù)中包含 roles 字段(角色碼列表數(shù)組),并在前端存儲:

  1. Protobuf 定義(后端返回用戶數(shù)據(jù)結(jié)構(gòu))
message User {
    repeated string roles = 1; // 角色碼列表,如 ["super", "admin"]
}
  1. 前端存儲角色信息(src/store/auth.ts)
// 設置登錄用戶信息,確保 userInfo.roles 為角色碼數(shù)組
authStore.setUserInfo(userInfo);
// 后續(xù)路由篩選會自動讀取 userStore.userRoles 進行匹配

特殊場景:菜單可見但禁止訪問

部分業(yè)務場景下需實現(xiàn)“菜單可見但點擊后跳轉(zhuǎn)403無權限頁面”(如引導普通用戶申請權限),可通過配置 meta.menuVisibleWithForbidden: true 實現(xiàn):

const system: RouteRecordRaw[] = [
  {
    path: '/report',
    name: 'Report',
    meta: {
      title: '財務報表',
      authority: ['super'], // 僅超級管理員可訪問
      menuVisibleWithForbidden: true, // 未授權用戶可見菜單,點擊跳轉(zhuǎn)403
    },
  },
];

二、按鈕級權限:管控操作執(zhí)行權限

按鈕級權限是更細粒度的功能權限管控,用于控制用戶能否執(zhí)行特定操作(如“新增用戶”“刪除訂單”“導出報表”)。GoWind Admin 支持通過「權限碼(Permission Code)」和「角色碼(Role Code)」兩種維度實現(xiàn)按鈕級權限控制,適配不同的權限管控粒度需求。

核心依賴 @vben/access 權限組件庫,提供「組件方式」「API方式」「指令方式」三種使用形態(tài),開發(fā)者可根據(jù)組件復用性、代碼簡潔性需求靈活選擇。

2.1 權限碼控制(推薦:最小粒度權限管控)

權限碼是系統(tǒng)中最小粒度的權限標識,用于唯一標記單個操作權限(如 user:add 表示新增用戶、order:delete 表示刪除訂單)。權限碼由后端接口返回,前端通過判斷當前用戶是否擁有目標權限碼,控制按鈕的顯示/隱藏。

核心流程

  1. 用戶登錄后,前端調(diào)用defRouterService.ListPermissionCode({}) 接口拉取當前用戶的權限碼列表。
  2. 前端將權限碼列表存儲到accessStore 中,供全局權限判斷使用。
  3. 通過 @vben/access 提供的能力,基于權限碼判斷按鈕是否顯示。

核心代碼(權限碼獲取與存儲)

// src/store/auth.ts
/**
 * 登錄后初始化用戶信息與權限碼
 */
async function initAuthData() {
  // 并行拉取用戶信息和權限碼
  const [fetchUserInfoResult, accessCodes] = await Promise.all([
    fetchUserInfo(), // 拉取用戶基礎信息
    fetchAccessCodes(), // 拉取權限碼列表
  ]);

  const userInfo = fetchUserInfoResult;
  // 存儲用戶信息到狀態(tài)管理
  userStore.setUserInfo(userInfo);
  // 存儲權限碼到狀態(tài)管理(accessCodes.codes 為權限碼數(shù)組,如 ["user:add", "order:edit"])
  accessStore.setAccessCodes(accessCodes.codes);
}

/**
 * 拉取用戶基礎信息
 */
async function fetchUserInfo() {
  return (await defAuthnService.GetMe({ id: 0 })) as UserInfo;
}

/**
 * 拉取用戶權限碼列表
 */
async function fetchAccessCodes() {
  return await defRouterService.ListPermissionCode({});
}

三種使用方式

方式1:組件方式(推薦,適合單個/少量按鈕)

使用 AccessControl 組件包裹按鈕,通過 codes 屬性指定所需權限碼,type="code" 表示基于權限碼判斷:

// src/views/system/user/index.vue
<script lang="ts" setup>
import { AccessControl } from '@vben/access';
</script>

<template>
  <!-- 需要指明 type="code" -->
  <AccessControl :codes="['product:add']" type="code">
    <Button> 添加商品 ["product:add"] </Button>
  </AccessControl>
  <AccessControl :codes="['product:edit']" type="code">
    <Button> 編輯商品 ["product:edit"] </Button>
  </AccessControl>
</template>

方式2:API方式(適合復雜邏輯判斷場景)

通過 useAccess鉤子獲取 hasAccessByCodes 方法,在模板中通過v-if 控制按鈕顯示,支持結(jié)合其他業(yè)務邏輯判斷:

// src/views/system/user/index.vue
<script lang="ts" setup>
import { useAccess } from '@vben/access';

const { hasAccessByCodes } = useAccess();
</script>

<template>
  <Button v-if="hasAccessByCodes(['product:add'])">
    添加商品 ["product:add"]
  </Button>
  <Button v-if="hasAccessByCodes(['product:edit'])">
    編輯商品 ["product:edit"]
  </Button>
</template>

方式3:指令方式(適合批量按鈕權限控制,簡潔高效)

使用v-access:code 指令直接綁定權限碼,支持單個權限碼(字符串)或多個權限碼(數(shù)組):

// src/views/system/user/index.vue
<template>
  <Button class="mr-4" v-access:code="'product:add'">
    添加商品 ["product:add"]
  </Button>
  <Button class="mr-4" v-access:code="['product:edit']">
    編輯商品 ["product:edit"]
  </Button>
</template>

2.2 角色碼控制(適配粗粒度操作管控)

角色碼控制基于用戶所屬角色實現(xiàn)按鈕權限管控,適用于對操作權限要求不精細的場景(如“所有管理員均可查看操作日志”)。核心依賴 useUserStore 中存儲的userRoles 角色碼列表,通過判斷用戶角色是否在目標角色范圍內(nèi),控制按鈕顯示。

核心邏輯(角色碼獲取與判斷)

// src/store/user.ts
import { useUserStore } from '@vben/stores';

/**
 * 角色權限判斷:判斷用戶是否擁有目標角色中的任一角色
 * @param roles 目標角色碼列表
 */
function hasAccessByRoles(roles: string[]) {
  const userStore = useUserStore();
  const userRoleSet = new Set(userStore.userRoles); // 用戶當前角色集合
  // 計算目標角色與用戶角色的交集,有交集則擁有權限
  const intersection = roles.filter(item => userRoleSet.has(item));
  return intersection.length > 0;
}

// 角色碼來源:用戶登錄時從后端拉取并存儲
async function fetchUserInfo() {
  const userInfo = await defAuthnService.GetMe({ id: 0 });
  // 存儲角色信息到狀態(tài)管理
  const roles = userInfo?.roles ?? [];
  userStore.setUserRoles(roles);
  return userInfo;
}

三種使用方式

方式1:組件方式

使用 AccessControl 組件,通過 type="role" 指定基于角色碼判斷:

// src/views/system/log/index.vue
<script lang="ts" setup>
import { AccessControl } from '@vben/access';
</script>

<template>
  <AccessControl :codes="['super']" type="role">
    <Button> Super 角色可見 </Button>
  </AccessControl>
  <AccessControl :codes="['admin']" type="role">
    <Button> Admin 角色可見 </Button>
  </AccessControl>
  <AccessControl :codes="['user']" type="role">
    <Button> User 角色可見 </Button>
  </AccessControl>
  <AccessControl :codes="['super', 'admin']" type="role">
    <Button> Super & Admin 角色可見 </Button>
  </AccessControl>
</template>
方式2:API方式

通過 useAccess 鉤子獲取 hasAccessByRoles 方法,結(jié)合 v-if 控制顯示:

// src/views/system/log/index.vue
<script lang="ts" setup>
import { useAccess } from '@vben/access';

const { hasAccessByRoles } = useAccess();
</script>

<template>
  <Button v-if="hasAccessByRoles(['super'])"> Super 賬號可見 </Button>
  <Button v-if="hasAccessByRoles(['admin'])"> Admin 賬號可見 </Button>
  <Button v-if="hasAccessByRoles(['user'])"> User 賬號可見 </Button>
  <Button v-if="hasAccessByRoles(['super', 'admin'])">
    Super & Admin 賬號可見
  </Button>
</template>
方式3:指令方式

使用 v-access:role 指令直接綁定角色碼,支持單個或多個角色:

// src/views/system/log/index.vue
<template>
  <Button class="mr-4" v-access:role="'super'"> Super 角色可見 </Button>
  <Button class="mr-4" v-access:role="['super']"> Super 角色可見 </Button>
  <Button class="mr-4" v-access:role="['admin']"> Admin 角色可見 </Button>
  <Button class="mr-4" v-access:role="['user']"> User 角色可見 </Button>
  <Button class="mr-4" v-access:role="['super', 'admin']">
    Super & Admin 角色可見
  </Button>
</template>

三、權限控制最佳實踐

3.1 控制方式選擇建議

管控場景 推薦控制方式 理由
復雜多租戶、動態(tài)角色權限 頁面級:后端控制;按鈕級:權限碼控制 后端統(tǒng)一管控權限配置,適配頻繁變更,減少前后端協(xié)同成本
簡單固定角色(3類以內(nèi))、權限變更少 頁面級:前端控制;按鈕級:角色碼控制 前端獨立開發(fā)調(diào)試,降低初期開發(fā)成本,適配簡單場景
核心操作(如刪除、導出) 權限碼控制 + 后端接口二次校驗 前端控制僅隱藏按鈕,后端接口校驗防止惡意請求,雙重保障安全

3.2 核心注意事項

  • 前端權限僅為“顯示控制”,不可替代后端校驗:前端可通過隱藏菜單/按鈕阻止普通用戶操作,但惡意用戶可能通過偽造請求繞過前端控制。因此,所有權限相關的接口必須在后端進行二次校驗(如驗證用戶是否擁有操作權限碼),確保數(shù)據(jù)安全。
  • 權限數(shù)據(jù)緩存與刷新:用戶角色/權限變更后,需及時刷新前端權限狀態(tài)(如重新拉取權限碼、重新生成路由),避免權限變更不生效。
  • 異常降級處理:后端路由/權限碼接口請求失敗時,需添加降級邏輯(如跳轉(zhuǎn)403頁、提示“權限加載失敗,請重試”),提升用戶體驗。

四、項目源碼與參考資料

4.1 項目源碼

4.2 參考資料

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容