基于Vue實(shí)現(xiàn)后臺(tái)系統(tǒng)權(quán)限控制

原文地址:http://refined-x.com/2017/08/29/基于Vue實(shí)現(xiàn)后臺(tái)系統(tǒng)權(quán)限控制/,轉(zhuǎn)載注明出處

用Vue這類雙向綁定框架做后臺(tái)系統(tǒng)再適合不過(guò),后臺(tái)系統(tǒng)相比普通前端項(xiàng)目除了數(shù)據(jù)交互更頻繁以外,還有一個(gè)特別的需求就是對(duì)用戶的權(quán)限控制,那么如何在一個(gè)Vue應(yīng)用中實(shí)現(xiàn)權(quán)限控制呢?下面是我的一點(diǎn)經(jīng)驗(yàn)。

權(quán)限控制是什么

在權(quán)限的世界里服務(wù)端提供的一切都是資源,資源可以由請(qǐng)求方法+請(qǐng)求地址來(lái)描述,權(quán)限是對(duì)特定資源的訪問(wèn)許可,所謂權(quán)限控制,也就是確保用戶只能訪問(wèn)到被分配的資源。具體的說(shuō),前端對(duì)資源的訪問(wèn)通常是由界面上的按鈕發(fā)起,比如刪除某條數(shù)據(jù);或由用戶進(jìn)入某一個(gè)頁(yè)面發(fā)起,比如獲取某個(gè)列表數(shù)據(jù)。這兩種形式覆蓋了資源請(qǐng)求的大部分場(chǎng)景,因此權(quán)限控制也可以被籠統(tǒng)的分成菜單權(quán)限控制和按鈕權(quán)限控制。

Vue菜單權(quán)限控制

菜單是對(duì)路由的直接體現(xiàn),菜單控制實(shí)際上就是路由控制。實(shí)現(xiàn)路由控制一個(gè)簡(jiǎn)單的方式是,在路由的before鉤子里校驗(yàn)當(dāng)前即將跳轉(zhuǎn)的路由地址是否有權(quán)訪問(wèn),根據(jù)校驗(yàn)結(jié)果決定路由是否放行,偽碼:

router.beforeEach((to, from, next) => {
    //權(quán)限校驗(yàn)
    let pass = valid(to);
    if(!pass){
        return console.log('無(wú)權(quán)訪問(wèn)');
    }
    next();
});

這種實(shí)現(xiàn)方式既簡(jiǎn)單又直觀,用于簡(jiǎn)單的系統(tǒng)非常合適,但這么做本質(zhì)上是將所有路由全部注冊(cè)了,直接帶來(lái)的缺點(diǎn)有兩個(gè):一、如果路由組件不是按需加載的話,應(yīng)用將加載大量冗余代碼;二、每次跳轉(zhuǎn)都要遍歷一次完整路由是對(duì)計(jì)算能力的浪費(fèi),對(duì)于路由總數(shù)較大的應(yīng)用很不可取。

理想的實(shí)現(xiàn)方式是本地保存完整路由,但并不立即初始化Vue應(yīng)用,待用戶登錄拿到權(quán)限后,用菜單權(quán)限篩選出可用路由,再用可用路由初始化Vue應(yīng)用。也就是說(shuō),要將登錄頁(yè)獨(dú)立出去做成一個(gè)單獨(dú)的頁(yè)面,登錄后將用戶數(shù)據(jù)保存在本地,再通過(guò)url跳轉(zhuǎn)到Vue應(yīng)用所在頁(yè)面,Vue應(yīng)用啟動(dòng)前通過(guò)本地用戶數(shù)據(jù)完成路由篩選,然后初始化Vue應(yīng)用,偽碼如下:

//main.js
let user = sessionStorage.getItem('user');
if (user) {
    user = JSON.parse(user);
    //篩選得到實(shí)際路由
    let fullPath = require('fullPath.js');
    let routes = filter(fullPath, user.menus);
    //創(chuàng)建路由對(duì)象
    let router = new Router({routes});
    //生成Vue實(shí)例
    new Vue({
        el: '#app',
        router,
        render: h => h(App)
    });
}else{
    location.href = '/login/';
}

這樣我們就根據(jù)用戶權(quán)限生成了一套"定制"路由,這時(shí)我們還希望能直接用路由生成導(dǎo)航菜單,常規(guī)的路由數(shù)據(jù)可能無(wú)法滿足菜單組件的需求,所以我們可以事先在路由的meta數(shù)據(jù)里維護(hù)上菜單數(shù)據(jù),比如菜單名稱菜單圖標(biāo)等,只要在模板中通過(guò)$router.options就可以訪問(wèn)到當(dāng)前路由數(shù)據(jù),如果使用element-ui的菜單組件實(shí)現(xiàn),代碼大致是這樣的:

<el-menu router>
    <el-menu-item v-for="(route, index) in $router.options.routes[2].children"
    :route="route"
    :index="route.name">
        <i class="ion" v-html="route.icon"></i>{{route.name}}
    </el-menu-item>
</el-menu>

當(dāng)然這樣只能循環(huán)出一級(jí)菜單,如果還有二級(jí)路由需要對(duì)應(yīng)二級(jí)菜單的話,就得判斷并循環(huán)children節(jié)點(diǎn),比較簡(jiǎn)單就不放更多代碼了,菜單權(quán)限控制到這里就完成了。

Vue按鈕權(quán)限控制

按鈕權(quán)限控制與菜單權(quán)限控制的實(shí)現(xiàn)思路類似,也是根據(jù)用戶權(quán)限判斷各個(gè)按鈕的顯示與否,方式無(wú)非是v-if或自定義指令,而且只要將v-if背后的權(quán)限校驗(yàn)邏輯抽象成方法,無(wú)論是代碼量還是使用形式上都跟自定義指令幾乎一樣,但v-if的特點(diǎn)是它會(huì)響應(yīng)數(shù)據(jù)變化,因此隨著應(yīng)用的運(yùn)行會(huì)頻繁觸發(fā)權(quán)限校驗(yàn),而權(quán)限在應(yīng)用的整個(gè)生命周期內(nèi)其實(shí)只需校驗(yàn)一次,為了避免無(wú)謂的程序執(zhí)行,這里可以用自定義指令來(lái)實(shí)現(xiàn),偽碼:

Vue.directive('has', {
  bind: function (el, binding) {
    if(!has(binding.value)){
        el.parentNode.removeChild(el);
    }
  }
});

//用法:
<btn v-has='get,/sources'>按鈕</btn>

注意在指令bind回調(diào)里有一個(gè)has()方法,這就是權(quán)限校驗(yàn)方法,我們同時(shí)將這個(gè)方法全局混合到Vue對(duì)象中,使應(yīng)用里的每個(gè)組件都可以訪問(wèn)到這個(gè)方法,便于為界面上的v-if提供支持,例如:

<div v-if="has('get,/sources') && something">
    一個(gè)需要同時(shí)具備'get,/sources'權(quán)限和somthing為真值才顯示的div
</div>

這樣一來(lái)凡是需要依據(jù)權(quán)限實(shí)現(xiàn)的按鈕顯隱控制和界面變化都可以很方便的實(shí)現(xiàn)。

但按鈕權(quán)限控制真正麻煩的地方不在于如何實(shí)現(xiàn),而在于高昂的維護(hù)成本。我們假設(shè)按鈕Btn綁定了點(diǎn)擊回調(diào)Fn,回調(diào)Fn里發(fā)起了請(qǐng)求Req,請(qǐng)求Req需要某個(gè)資源的訪問(wèn)權(quán)限,最終你要根據(jù)用戶是否擁有Req的權(quán)限,決定Btn是否顯示,而Req跟Btn之間并沒(méi)有直接關(guān)聯(lián),所以我們就要人肉維護(hù)他們的關(guān)系,一個(gè)復(fù)雜項(xiàng)目里的按鈕有個(gè)幾十上百都很正常,隨著業(yè)務(wù)的變更去維護(hù)這么多按鈕的權(quán)限,想想都頭疼。

有一個(gè)方法可以繞開這個(gè)爛攤子,那就是前端放棄對(duì)視圖層的控制,退到請(qǐng)求層面,在請(qǐng)求發(fā)起前集中攔截,這時(shí)可以直接根據(jù)請(qǐng)求方法和請(qǐng)求地址來(lái)校驗(yàn)權(quán)限,除了實(shí)現(xiàn)一個(gè)攔截器之外不需要額外的代碼,可以說(shuō)非常優(yōu)雅了。以axios為例,攔截器大概長(zhǎng)這樣:

axios.interceptors.request.use(function (config) {
  if(!has(config)){
  //驗(yàn)證不通過(guò)
    return Promise.reject({
      message: `no permission`
    });
  }
  return config;
});

但如果僅僅這樣做權(quán)限控制,界面上將顯示出所有的按鈕,用戶看到的按鈕卻不一定可以點(diǎn)擊,這種體驗(yàn)我認(rèn)為只能停留在理論層面,根本無(wú)法應(yīng)用到實(shí)際產(chǎn)品中。請(qǐng)求控制可以作為整個(gè)控制體系的第二道防線,或某些特殊情況下的輔助手段,最終還是要回到按鈕控制的思路上來(lái)。

那么怎樣能盡可能方便的采集到每個(gè)按鈕所需的權(quán)限呢?按鈕和權(quán)限之間隔著兩層?xùn)|西,第一層是click回調(diào),第二層是回調(diào)里的AJAX請(qǐng)求,不想人肉維護(hù)就得想辦法突破這兩層隔閡,讓按鈕和權(quán)限產(chǎn)生聯(lián)系,按鈕必然要綁定click事件,最理想的采集方式是在綁定事件的同時(shí)得到所需權(quán)限,讓一切自然而然的發(fā)生,比如這樣,

<btn v-do="Fn">按鈕</btn>

如果Fn能以某種形式采集到內(nèi)部的AJAX請(qǐng)求參數(shù),并轉(zhuǎn)化成權(quán)限信息傳遞出來(lái)就完美了,然而我沒(méi)找到可行的方法,并且這種形式在應(yīng)用上也存在缺陷,因?yàn)椴灰欢總€(gè)操作按鈕都會(huì)發(fā)起AJAX請(qǐng)求,比如編輯按鈕本身并不會(huì)觸發(fā)請(qǐng)求,真正觸發(fā)請(qǐng)求的是另一個(gè)保存按鈕,所以這個(gè)思路只是看起來(lái)很美。

那么退而求其次的做法是讓按鈕和請(qǐng)求聯(lián)系起來(lái),比如說(shuō)按鈕涉及一個(gè)名稱為A的請(qǐng)求,那么我希望權(quán)限指令可以這樣寫,

<btn v-has="A" @click="Fn">按鈕</btn>

比完美形態(tài)是差了不少,但起碼不需要手動(dòng)維護(hù)到'get,/resources'這個(gè)級(jí)別了,這里對(duì)A的實(shí)現(xiàn)可以有多種形式,比如A可以是一個(gè)包含兩個(gè)屬性的對(duì)象:

const A = {
  p: ['put,/menu/**'],
  r: params => {
    return axios.put(`/menu/${params.id}`, params)
  }
};

//用作權(quán)限:
<btn v-has="[A]" @click="Fn">按鈕</btn>
//用作請(qǐng)求:
function Fn(){
    A.r().then((res) => {})
}

通常我們會(huì)將項(xiàng)目里所有的api放在一個(gè)api模塊里集中管理,在寫api時(shí)順便就把權(quán)限給維護(hù)了,換來(lái)的是在組件界面里可以直接用請(qǐng)求名稱來(lái)描述權(quán)限,而不需要來(lái)回奔波于界面和api模塊之間,一定程度上實(shí)現(xiàn)了關(guān)注點(diǎn)分離,而且has指令還可以進(jìn)一步做優(yōu)化,例如參數(shù)只需要接收A,指令內(nèi)部根據(jù)約定自動(dòng)訪問(wèn)A.p來(lái)獲取權(quán)限,還可以接收數(shù)組,允許多個(gè)權(quán)限聯(lián)合校驗(yàn)。

后記

好了,這就是我對(duì)前端權(quán)限控制的一些實(shí)踐和思考,如有不當(dāng)歡迎指正。

最后吐槽一下Element-UI,真心難看。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,714評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,776評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,210評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,654評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,638評(píng)論 25 708
  • 有時(shí), 幸福很簡(jiǎn)單; 騎一輛摩拜單車, 走一條清凈的大道, 聽一首思緒萬(wàn)千的歌曲, 游走在, 既熟悉又陌生的城市,...
    金石已開閱讀 208評(píng)論 0 3
  • 選擇做程序員就要有一顆保持學(xué)習(xí)的心,因?yàn)榧夹g(shù)更新快、需求變化快……所以得時(shí)刻保持學(xué)習(xí)才能不被淘汰。正因?yàn)槿绱耍艺J(rèn)...
    coderpwh閱讀 2,708評(píng)論 1 36
  • 理解并接受你也會(huì)犯錯(cuò)誤。關(guān)鍵在于:在攜帶盲點(diǎn)投入生產(chǎn)之前要先找到它們。幸運(yùn)的是,在我們的作業(yè)中,除了在實(shí)驗(yàn)室開發(fā)火...
    陳加新閱讀 1,193評(píng)論 3 16
  • 讓我松了一口氣的是,微微終于還是得到了幸福。 那個(gè)美麗可愛(ài)率真的女孩,那個(gè)在愛(ài)里橫沖直撞、勇往直前、義無(wú)反顧的女孩...
    高小花0218閱讀 259評(píng)論 0 0