首先還是謝謝各位童鞋的大大的贊贊,你們的支援是我前進的動力!上週寫了一篇從0到1搭建element後臺框架,很多童鞋留言提到許可權問題,這一週就給大家補上。GitHub
一、jwt授權認證
現在大多數專案都是採用jwt授權認證,也就是我們所熟悉的token登入身份校驗機制,jwt的好處多多,由於jwt是由服務端生成,中間人修改密串後,服務端會校驗不過,安全有效。一般呆在請求頭上的Authorization
裡面。前端童鞋一般獲取token後通過vuex儲存起來,隨後資料持久化存到session中。
首先在路由跳轉的時候需要驗證vuex
是否儲存了token,如果沒有token的話直接跳到登陸頁面獲取token。
if (to.path !== '/login' && !store.state.token) {
next('/login')
NProgress.done() // 結束Progress
} else {
next();
}
複製程式碼
詳細請看專案中的router.js
本地存在token之後,我們在每次請求介面的時候都需要帶上token來驗證token的合法性。
//在請求前攔截
if (store.state.token) {
config.headers["Authorization"] = "Bearer " + store.state.token;
}
複製程式碼
如果token不合法,全域性錯誤處理,直接跳到登陸頁面
case 401:
messages("warning", "使用者登陸過期,請重新登陸");
store.state.commit('COMMIT_TOKEN','')
setTimeout(() => {
router.replace({
path: "/login",
query: {
redirect: router.currentRoute.fullPath
}
});
}, 1000);
break;
複製程式碼
詳細程式碼看專案中的request.js
二、選單許可權
本專案中,我主要是通過後端傳過來的角色型別來判斷導航選單的顯示與隱藏。
也就是說首先前端請求介面,後端返回token,以及對應的角色,比如專案中用admin
登陸的話,roles=['admin']
,用user
登陸的話roles=['user']
。
接下來我這邊設計了一份選單表和一份路由表,路由表主要是為了註冊路由,不需要考慮層級關係。而選單表需要考慮層級關係,裡面可以配置主選單,子選單,圖示等等一系列的東西,當然選單表最好是通過介面資料從後端傳過來。值得注意的是無論是選單表,還是路由表,裡面都有一個meta
配置項。裡面可以配置我們的角色許可權。路由表對應的選單表角色許可權需要一致。沒有配置角色許可權的選單預設都開放。
menu.js
其中一項premission選項中配置了一個roles:['admin']
{
icon: "el-icon-question",
index: "premission",
title: "許可權測試",
subs: [{
index: "permission",
title: "選單測試",
meta: {
roles: ['admin']
}
},
{
index: "permissionBtn",
title: "按鈕許可權",
},
]
}
複製程式碼
router.js
其中一項premission選項中配置了一個roles:['admin']
{
path: '/permission',
component: getComponent('permission', 'permission'),
meta: {
title: '選單許可權',
roles: ['admin']
}
},
複製程式碼
現在我們開始編寫選單邏輯,進入Aside.vue
,首先根據角色過濾選單表menu.js
/**
* @param {Arrary} menus 選單
* @param {Arrary} roles 角色
* @return {Arrary} res 過濾後的選單
*/
filterMenus(menus, roles) {
const res = [];
menus.forEach(route => {
const tmp = { ...route };
//hasPermission判斷許可權是否匹配
if (this.hasPermission(roles, tmp)) {
if (tmp.subs) {
tmp.subs = this.filterMenus(tmp.subs, roles);
}
res.push(tmp);
}
});
return res;
},
複製程式碼
/**
* 通過meta.role判斷是否與當前使用者許可權匹配
* @param roles
* @param menu
*/
hasPermission(roles, menu) {
if (menu.meta && menu.meta.roles) {
return roles.some(role => menu.meta.roles.includes(role));
} else {
return true;
}
},
複製程式碼
獲得過濾後的路由表
computed: {
items() {
let items = this.filterMenus(menu, this.$store.state.roles);
return items;
}
},
複製程式碼
這樣就獲得了許可權選單
到目前為止,許可權控制基本完成,不過在專案執行的過程中,還發現一個bug。本專案中存在一個tagList
,也就是開啟的導航標籤,當使用者從admin
切換到user
的時候開啟的導航標籤依舊存在,也就是說使用者可以通過導航標籤進入premission頁面。此時我這邊直接通過路由攔截來處理此時的情況。
if(to.meta.roles){
to.meta.roles.includes(...store.getters.roles)?next():next('/404')
}else{
next();
}
複製程式碼
沒有許可權的頁面一律進入404頁面。
三、按鈕許可權控制
按鈕級別的許可權說實話一般都通過資料介面來控制是否展示,點選等等情況。如果光有前端來控制絕對不是可行之道。
專案中按鈕許可權註冊全域性自定義指令來完成的。首先src
下面新建一個directive
資料夾,用於註冊全域性指令。在資料夾下新建一個premissionBtn.js
。如果對自定義指令不熟的話可以查閱官方文件。
import Vue from 'vue'
import store from '@/store/store'
//註冊一個v-allowed指令
Vue.directive('allowed', {
inserted: function (el, bingding) {
let roles = store.getters.roles
//判斷許可權
if (Array.isArray(roles) && roles.length > 0) {
let allow = bingding.value.some(item => {
return roles.includes(item)
})
if (!allow) {
if (el.parentNode) {
el.parentNode.removeChild(el)
}
}
}
}
})
複製程式碼
全域性引用main.js
import './directive/premissionBtn'
複製程式碼
那自定義指令如何使用呢?
<div class="premissionBtn">
<el-button type="primary" v-allowed="['admin']">我是隻有admin的時候才能顯示</el-button>
<br>
<el-button type="info" v-allowed="['user']">我是隻有user的時候才能顯示</el-button>
<br>
<el-button type="warning" v-allowed="['admin','user']">我是admin或者user才能顯示</el-button>
<br>
<el-button type="danger">任何角色都可以顯示</el-button>
</div>
複製程式碼
四、優化
上一週專案打包出現了問題,由於打包後的專案體積比較大,以及跟路徑設定問題(~有點尷尬~),導致webpack報錯,專案執行不起來,這裡先跟大家道個歉。
本次主要用了兩個webpack
的外掛,第一個是檔案壓縮外掛compression-webpack-plugin
,另一個是去除console
的外掛uglifyjs-webpack-plugin
,首先開始安裝。
cnpm install compression-webpack-plugin uglifyjs-webpack-plugin --save
複製程式碼
開始配置外掛vue.config.js
let path = require('path');
//去console外掛
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
//gzip壓縮外掛
const CompressionWebpackPlugin = require('compression-webpack-plugin')
function resolve(dir) {
return path.join(__dirname, dir)
}
module.exports = {
//基本路徑
publicPath: './',
//輸出檔案目錄
outputDir: 'dist',
//放置生成的靜態資源 (js、css、img、fonts) 的 (相對於 outputDir 的) 目錄。
assetsDir: 'static',
chainWebpack: config => {
//這裡是對環境的配置,不同的環境對應不同的BASE_URL
config.plugin('define').tap(args => {
args[0]['process.env'].BASE_URL = JSON.stringify(process.env.BASE_URL)
return args;
});
//設定別名
config.resolve.alias
.set('@', resolve('src'))
},
// webpack外掛配置
configureWebpack: config => {
let plugins = [
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false,
drop_debugger: true,
drop_console: true,
},
},
sourceMap: false,
parallel: true,
}),
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
['js', 'css'].join('|') +
')$',
),
threshold: 10240,
minRatio: 0.8,
}),
]
if (process.env.NODE_ENV !== 'development') {
config.plugins = [...config.plugins, ...plugins]
}
},
devServer: {
open: true
},
//定義scss全域性變數
css: {
loaderOptions: {
sass: {
data: `@import "@/assets/scss/global.scss";`
}
}
}
}
複製程式碼
說到這裡也是對上週寫的一篇文章進行補充和完善。文章有點囉嗦~~,謝謝觀看。