vue後臺管理系統許可權控制思考與實踐

子物發表於2018-12-17

前言

最近在開發管理系統時遇到了任何管理系統都會有的需求---許可權控制,之前也遇到過這種需求,但是架構不完善導致的各種問題使得後期維護非常麻煩,這一次的方案解決了之前的種種問題,現做一次記錄,當然這個架構後期可能會有坑,不過得一步一步的嘗試才能發現並解決問題。

許可權控制需求

因為是單頁面應用,路由交給前端來控制,對於一些需要特定許可權才能檢視的資訊的保護變得尤為重要,如果前端不做好許可權校驗,後端也一時疏忽,就可能就會導致資料洩露。

對於許可權控制,需求大致為如下:

  1. 對於大模組的限制,比如需要通過路由跳轉的模組,這時需要進行路由攔截
  2. 對於小功能的限制,比如一個按鈕,如果沒有特定許可權,那麼這個按鈕就不顯示

安全層面的思考

之前接手了一個管理系統,前端是將許可權列表儲存在storage中來實現長久儲存,這種實現方式是很不可取的,因為hacker可以通過手動更改儲存的資訊來實現獲取特定許可權,甚至系統都沒有做路由攔截,如果知道模組的路由,可以直接通過輸入路由資訊來直接跳轉到特定模組。對於一些模組的許可權,許可權被管理員修改後也無法立即生效,所以對於這幾種情況做了如下思考與實踐。

許可權被管理員修改後立即生效

對於這個需求,我的做法是,獲取到許可權列表後,將許可權資訊儲存在 vuex store 中,並且使用getter函式,對於是否可以使用該許可權進行判斷,這樣一旦許可權資料更新,前端許可權限制功能點會自動修改,從而做到許可權的實時性,大致實現如下:

// vuex state.js
export default {
    userPrivileges: {
        admin: [],
        purchaser: []
    }, // 使用者許可權資訊
}

// vuex getters.js
export default {
    canIUse: state => (role, id) => state.userPrivileges[role].includes(id)
}

// 頁面具體小功能,通過 mapGetters 引入 canIUse 函式
<span v-if="canIUse('admin', 9)">{{scope.row.allocation_subtotal}}</span>

複製程式碼

這樣一來,資料儲存在記憶體中,那麼許可權資訊就無法輕易的被修改,同時對於許可權的判斷也非常簡單,只需要在特定功能點傳入功能點的許可權id就能判斷是否可以使用這個許可權了。

但是將資料儲存在了記憶體中也會遇到一個問題,頁面重新整理怎麼辦?接下來就是講解這種情況。

重新整理頁面也可以進行許可權判斷

對於大模組的許可權攔截,肯定是通過路由鉤子來進行攔截的(這種實現有很多文章講解過,這裡不具體講解),但是通過路由鉤子進行攔截的前提是,許可權資訊得提前存在。

重新整理頁面會存在這種情況,頁面重新整理時,先執行的路由鉤子,再執行的元件生命週期鉤子來請求許可權的列表,此時許可權資訊不存在,那麼頁面跳轉到登陸頁的話,體驗就不夠友好。

所以我的做法是,建立一箇中間頁,如果許可權校驗不通過,那麼跳轉至中間頁,中間頁進行許可權的請求,請求到許可權後,再判斷是否可以跳轉,這樣的話,重新整理頁面體驗就比較好。大致程式碼如下:

// vuex actions.js
// 通過返回一個promise,使得store更新與後續程式碼變為“同步”執行
export default {
    getUserPrivileges({ commit }) {
        return fetch.get({
            url: '/currentstaff'
        }).then(data => {
            commit('SET_USER_PRIVILEGES_INFO', data.data)
            return data.data
        }).catch(e => {

        })
    }
}

// router.js
// 需要驗證許可權的路由
{
    path: 'suppliers',
    component: Suppliers,
    meta: {
        role: 'admin',
        privilegeId: 5
    }
}

function isCanUseThisModule(to, from) {
    return to.matched.every(record => {
        // 利用路由meta儲存相應許可權資訊
        if (record.meta.role) {
            return store.getters.canIUse(record.meta.role, record.meta.privilegeId)
        } else {
            return true // 如果不需要許可權,直接返回true
        }
    })
}

router.beforeEach((to, from, next) => {
    if (isCanUseThisModule(to, from)) {
        next() // 許可權驗證通過,跳轉下一路由
    } else {
        next({
            path: '/main/privilegeValidator' // 許可權驗證不通過時的中間頁
        })
    }
})

// 許可權校驗中間頁程式碼示例
created() {
    this.$store.dispatch('getUserPrivileges').then(data => {
        for (let i = 0; i < data.admin_permissions.length; i++) {
            if (this.canIUse('admin', data.admin_permissions[i])) {
                this.$router.push({
                    path: this.routerList.find(value => value.privilegeId === data.admin_permissions[i]).linkHref
                })
                return
            }
        }
        this.$router.push('/login') // 如果沒有任何許可權,則跳轉登陸頁面
    })
}

複製程式碼

使用者在登陸後也可以跳轉到這個許可權中間頁,進行許可權判斷後再跳轉到對應模組。

尾聲

大致的實現過程就是這樣,希望對大家有所幫助,如果有暗坑還請指出。

相關文章