前端權限控制

歡迎關注個人公衆號睿Talk,獲取我最新的文章:
clipboard.pngjavascript

1、前言

在成熟的電商系統中,權限管理是不可或缺的一個環節。靈活的權限管理有助於管理員對不一樣的人員分配不一樣的權限,在知足業務須要的同時保證敏感數據只對有權限的人開放。筆者最近對系統的權限管理作了一次改造,在此分享一些經驗以供參考。前端

2、權限管理基礎

權限管理通常分如下 3 個基礎概念:java

  • 功能點
  • 角色
  • 用戶

它們之間的關係一句話就能說清楚:一個用戶能夠擁有多個角色,而一個角色能夠包含多個功能。好比一個員工能夠既有收銀員的角色,也有庫管員的角色。對於收銀員這個角色,能夠有開單收銀、查看訂單、查看會員信息等功能點。node

clipboard.png

此外還有 2 個概念:webpack

  • 功能權限
  • 數據權限

它們之間的關係舉例來講明:
想象一個連鎖店的場景,某個門店的管理員具備查看營收的功能權限,和查看本身門店數據的數據權限;高級管理員一樣擁有查看營收的功能權限,而且有更高的數據權限,能夠查看全部門店數據的數據。web

3、前端權限控制

下面咱們聚焦到前端領域,聊聊前端應該怎麼作權限設計。前端本質上只有 1 種權限類型:組件權限。爲了更好的理解和管理,又將組件權限拆分爲如下 3 類:segmentfault

clipboard.png

每個權限最終都會落到權限點上。權限點能夠理解爲一個數據編碼,有這個權限點就說明有對應的功能權限。權限點的編碼要注意 2 點:後端

  • 全局惟一
  • 儘可能短小(減小帶寬消耗,由於一個用戶可能會有不少權限點)

須要控制權限的地方,都要定義一個權限點,而後告訴後端。一個用戶全部的權限點會以數組的形式返回。判斷是否有權限就是從數組中匹配一個元素。下面以 React 爲例,聊聊具體的實現方式。數組

對於頁面的權限判斷,能夠在 React Router 的 onEnter 回調中判斷:緩存

// 編碼映射,下面的 getUrlCodeByName 會用到
export default {
    order_list:          'zaq0', // 訂單列表
    order_detail:        'xsw1', // 訂單詳情
    order_refund_list:   'cde2', // 訂單退款列表
    order_refund_detail: 'vfr3', // 訂單退款詳情
    order_deduct_modify: 'bgt4', // 訂單修改業績
};

function canAccessUrl(urlName) {
    const moduleCode = getUrlCodeByName(urlName); // 權限點通常是一個沒意義的編碼,爲了易於理解,前端作了一個編碼映射
    return accesses.u.indexOf(moduleCode) > -1;   // accesses.u 數組是後端返回的全部 url 權限點
}

function routerOnEnterCheck(urlName) {
    return function routerOnEnter(nextState, replace) {
        if (!canAccessUrl(urlName)) {
            replace('/unauthorized');
        }
    };
}

...

{
    path: 'list',
    getComponent: loadAsync(() => import(/* webpackChunkName: "order" */ '../../order/List')),
    onEnter: routerOnEnterCheck('order_list'),
}

...

對於菜單和組件的權限判斷,大致上長這樣:

// 用以緩存是否有權限訪問組件
const componentAccessCache = {};

/**
 * 檢查訪問組件的權限
 * 一個組件可能會重複 render 屢次,而組件權限的數量可能會超多(上百個),所以將權限緩存起來以提升性能
 */
function canAccessComponent(module, componentName) {
    if (!module || !componentName) {
        console.error(`canAccessComponent ${module} ${componentName} 缺參數`);
    }

    const key = `${module}.${componentName}`;

    let result = componentAccessCache[key];

    if (result !== undefined) {
        return result;
    }

    const moduleCode = getComponentCodeByModuleAndName(module, componentName);  // 權限點通常是一個沒意義的編碼,爲了更易於理解,前端作了一個編碼映射

    result = accesses.c.indexOf(moduleCode) > -1;    // accesses.c 數組是後端返回的全部組件權限點

    componentAccessCache[key] = result;

    return result;
}

class SomeComponent extends PureComponent {

    ...
    
    render() {
        return (
            ...
            {
                canAccessComponent('asset', 'buy_pay') &&
                <Button
                    type="primary"
                    className="btn-buy"
                    onClick={() => (buy(num))}
                >
                當即訂購
                </Button>
            }
            ...
        )
    }
}

組件權限點的判斷也能夠封裝成高階組件的形式,這樣看起來會舒服一點,但本質上是同樣的,再也不贅述。

權限點的獲取筆者放在了 node 端,經過全局變量的形式注入到頁面中,保證首屏的時候呈現的頁面是由權限點過濾過的。此外接口返回的權限點是一個一維數組,爲了加快前端檢索速度,在 node 端根據編碼規則將權限點分爲 3 類(菜單/頁面/組件),具體細節就不細說了。

4、總結

本文介紹了權限管理的基礎知識,還結合 React 講解了前端權限控制的一些細節。技術方案比較簡單,真正麻煩的是每個權限點的定義及錄入,以及對現有系統的改造。改造過程當中能夠分模塊進行迭代,畢竟羅馬不是一天就能建成。

相關文章
相關標籤/搜索