許可權控制是專案中,特別是後臺管理專案中比較常見的功能了
結合實際的專案需求,講講在react中是如何實現許可權控制的
需求:
實現效果:無許可權的使用者沒有該頁面的入口:左側選單無入口,以及直接進入url提示無許可權
https://umijs.org/zh-CN/plugi...
配合@umijs/plugin-access
外掛使用
約定 src/access.ts
檔案為許可權定義檔案,該檔案需要預設匯出一個方法,匯出的方法會在專案初始化時被執行。該方法需要返回一個物件,物件的每一個值就對應定義了一條許可權。具體的介紹見檔案。
我的需求是根據使用者角色來判斷是否有該路由許可權,如果按照檔案的demo來,那麼我需要先:
access.ts
裡輸出物件 {canReadPageA: true, canReadPageB: false...}
access: 'canReadPageA'
, access: 'canReadPageB'
……這樣雖然可以實現,但程式碼量太大,要對每個需要許可權的頁面進行定義和判斷,access.ts 和 route.ts 檔案都需要嵌入大量程式碼
所以我改變了思路,換了另一種方案,也就是方案2
在 access.ts
中,當返回的物件中,值是方法時:
利用這個引數,就可以在 route 里加入我們所需要的資訊
// routes.ts
[
{
name: 'pageA',
path: '/pageA',
component: './pageA',
access: 'auth', // 許可權定義返回值的某個 key
roles: ['admin', 'user'], // role為admin或者user時可以存取pageA頁面
},
{
name: 'pageB',
path: '/pageB',
component: './pageB',
access: 'auth',
roles: ['admin'],// 只有role為admin時可以存取pageA頁面
},
]
我給 route 設定了兩個屬性:
access
值為 access.ts
返回物件的某個key,這裡的話固定為 auth
roles
定義可以有該頁面許可權的角色組在 access.ts
中返回key為 auth 的物件:
// access.ts
let hasAuth = (route: any, roleId?: string) => {
// 關鍵:對比route.roles 和 currentUser.roleId 判斷是否有許可權
return route.roles ? route.roles.includes(roleId) : true;
};
export default function access(initialState: { currentUser?: API.CurrentUser | undefined }) {
const { currentUser } = initialState || {};
return {
auth: (route: any) => hasAuth(route, currentUser?.roleId),
};
}
拿到route裡的資訊和當前使用者資訊進行對比,判斷,返回布林值
對比方案1,方案2優點就是,之後新增頁面時,只需要在 routes.ts
裡定義好該頁面的 access
和 roles
屬性, 不需要改動到 access.ts
實現效果:使用者有選單入口,進入頁面後顯示無許可權
思路:
根據 currentUser
對應的欄位來判斷是否有許可權
程式碼如下:
import { Access } from 'umi';
<Access accessible={currentUser.foo} fallback={<div>暫無許可權</div>}>
Foo content.
</Access>;
缺點:需要在對應的頁面中嵌入程式碼
思路:
通過高階元件 wrappers
實現
在 routes.ts
設定 wrappers
屬性
// routes.ts
[
{
name: 'pageA',
path: '/pageA',
component: './pageA',
wrappers: ['@/wrappers/authA']
},
{
name: 'pageB',
path: '/pageB',
component: './pageB',
wrappers: ['@/wrappers/authB']
},
]
這樣,存取 /pageA
時,會先通過 @/wrappers/authA
做許可權校驗
@/wrappers/authA
中,// wrappers/authA
import { useModel } from 'umi';
export default (props) => {
const { initialState } = useModel('@@initialState');
const { currentUser } = initialState || {};
if (currentUser.authA) {
return <div>{ props.children }</div>;
} else {
return <div>無許可權</div>;
}
}
這樣,根據 currentUser
來判斷是否渲染元件
方案2的優點是:
無須在頁面元件中嵌入相關程式碼,只需要在 routes.tx
中設定 wrappers
,鑑權部分由 @/wrappers/
來處理
然而,缺點就是,如果有多個許可權,如authA, authB, authC…… 那麼則需要在 @/wrappers/
新建多個鑑權檔案
實現效果:
無許可權的按鈕不顯示或者置灰
一般的做法是在元件中判斷
// 不顯示按鈕
{currentUser.auth ? <button>建立</button> : null}
// 置灰
{<button disabled={currentUser.auth}>建立</button>}
但如果有大量的許可權按鈕,那麼將要寫好多次這種程式碼,所以在這裡對按鈕進行一次封裝
// AuthBtn
import React, { useState, useEffect, useRef } from 'react';
import { Button } from 'antd';
const AuthBtn: React.FC<{}> = (props) => {
let { authId, children } = props;
// btnIds 應該有後臺介面返回,告訴前端使用者有哪些按鈕許可權
let btnIds = ['read', 'edit'];
let hasAuth = btnIds.includes(authId);
// 這裡可以根據實際需求封裝
return <Button disabled={!hasAuth}>{children}</Button>;
};
export default AuthBtn;
// index.ts
<AuthBtn authId="read">read 唯讀許可權</AuthBtn>
<AuthBtn authId="write">write 寫入許可權</AuthBtn>
傳入的 authId
需要先和後臺約定好, 還可以根據實際需求傳入type、loading等
這樣,普通按鈕用 Button
, 需要鑑權的使用 AuthBtn
以上就是最近關於許可權控制的實踐,做到對路由、頁面和按鈕層級進行鑑權,每一種都有對應的實現方案,每個方案都有自己的優缺點,旨在更優雅地程式設計!
如果有更好的方案,歡迎評論區留言!