管理系統類項目的登陸與權限功能的實現


前言

筆者最近參與了公司的幾個業務的管理系統類項目,在這樣的項目需求中,相對於所選擇的前端技術棧以及前后端分離協同開發的模式。理解並實現后台項目的業務需求是其中的重點,其中,賬戶的登陸,以及權限驗證與安全性是非常重要的。本文就以一個系統為例講一講如何在各技術與業務相結合完成這樣的需求。所以本文對於技術細節不多贅述,重點說辦法與思想。

需求與設計:

本系統為客服所用的工單系統,所以改系統的用戶均為客服人員,系統並不是由各客服人員自行注冊使用,而是在后台預建好各個客服人員的信息,並通過一個權限配置頁面為每一個客服人員分配相應的權限角色,不同的角色擁有不同的菜單以及按鈕級別的權限,以做到權限控制的目的。

登陸

1 掃碼

系統登陸的方法,如通過在系統做自己的登陸頁實現,現今已經成了一種小眾,且不開放的做法。隨着各個大的平台開放自己OAuth授權。走第三方的授權登陸才是目前主流的做法。優點在於安全性與用戶信息的整合。

本系統在運維層面接入了企業微信登陸, 當用戶輸入系統網址時,會在webserver層跳轉到企業微信掃碼登陸頁。
通過在nginx 中調用access_by_lua_file 模塊,使得對該系統的訪問會判斷在header中是否有sso_uid 這個cookie,
如果沒有的話,使訪問跳轉到一個企業微信掃碼頁面。進行開放授權登陸的操作。
這里放一下nginx的部分server配置

 location /
        {
        access_by_lua_file /opt/soft/nginx/main-conf/zhuanzhuan_zzssoauth.lua;
        proxy_next_upstream http_502 error timeout invalid_header;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header HTTPS-Tag "HTTP";
        proxy_set_header X-Forwarded-Proto $scheme;
                root /opt/web/frontend/ZZKF;
                index index.html;
                autoindex on;
                autoindex_exact_size off;
                autoindex_localtime on;
        }
        location /zzssocheck/ {
                proxy_pass http://zhuanzhuan_zzsso_pool/;
        }

可以看到,在訪問前端根路徑,即系統域名時,先通過access_by_lua_file模塊走lua文件的邏輯,而lua文件內,則是根據訪問的header頭判斷是否跳到微信掃碼登陸頁面了。

掃碼截圖:

在用戶使用企業微信掃碼登陸成功之后,頁面會跳轉回系統自身,並在瀏覽器中當前域下的cookies 中寫入一個sso_uid, 值為使用者的企業微信id。
這樣,我們在前端就方便地通過oauth獲取到了用戶的基本信息。

我們在前端也將sso_uid作為用戶登錄的狀態位,在全局的路由鈎子router.beforeEach中攔截路由,判斷是否已經有sso_uid的cookie,如沒有的話(比如登出操作),使重新進入掃碼頁面。
但值得指出的是:這還並沒有真的登陸到我們的后台系統中。

2 全局路由鈎子

router.beforeEach((to, from, next) => {

...
        // 未登錄且前往的頁面不是登錄頁
        //沒有wxId的 cookie  以及 前往的頁面不是登錄頁

        if (!Cookies.get('sso_uid')) {
           location.reload()
			    // location.href='http://apidoc.zhuanspirit.com'
        }
        if (!Cookies.get('sso_uid') && to.name !== 'login') {
            next({
                 name:  'login'
                 //name: 'home_index'
            });
            //debugger;
			    //  location.href='http://apidoc.zhuanspirit.com'
        }
        //已經登錄且前往的是登錄頁
        else if (Cookies.get('sso_uid') && to.name === 'login') {
            Util.title();
            // next({
            //      name: 'home_index'
            // });
            next();
        }
...
    }
});

3 登陸

在獲取到了用戶的 sso_uid之后,我們取服務端的登陸接口,服務端在獲取到用戶的ssu_uid之后,會與后台預錄入的用戶信息比較,
如果該無該用戶信息,或在黑名單中,則會跳轉到失敗頁,並顯示登陸的失敗信息。
如果登陸成功,服務端會返回用戶的中文名,頭像等基本信息,並在cookie中植入該系統的cookie ,其中的tk為唯一標示用戶身份的一個key,此后每次向服務端發出的接口請求都會帶上此key作為憑據 ,服務端會實時根據此值作出權限操作,例如用戶踢出等。

權限管理

權限的控制方法,都是在路由上做文章,這里分別講一下:

方法1

此方法適用於權限角色數較少,並且角色總數可預期的情況,方法為:
在前端的路由表中每個路由項中加入該路由項的權重值,可以為數字,也可以為 角色名,以表示進入該路由最低需要的權限。
例如:

    {
      path: 'index',
      title: '權限測試頁',
      name: 'accesstest_index',
      access: 0,  //此為權限描述
      component: () =>
        import ('@/views/access/access-test.vue')
    }

在登陸時,服務端根據用戶獲取其角色,然后將用戶的權限值植入前端的cookie中,前端依然是在全局的路由鈎子中,比較cookie中的權限值於 路由中的權限值,如果大於或等於權限要求,則允許進入,否則跳轉到提示頁
比如,用戶的權限值為 1 ,可以進入access值為 0,1 的路由中,但無法進入access值為2 的路由中。
路由鈎子中的權限控制代碼為:

router.beforeEach((to, from, next) => {
           if (curRouterObj && curRouterObj.access !== undefined) { // 需要判斷權限的路由
               if (curRouterObj.access <= parseInt(Cookies.get('access'))) {
next()
               }
               else
               {
                   next({
                       replace: true,
                       name: 'error-403'
                   });
               }
           } else { // 沒有配置權限的路由, 直接通過
		next()
           }
}

此方法也是主流技術棧的一些管理系統類腳手架所提供的方法,例如react技術棧的ant
design pro腳手架,其權限控制功能,也是使用的此方法,在其官方文檔中所敘述:

如需對某些菜單進行權限控制,只須對菜單配置文件 menu.js 中的菜單項設置 authority 屬性即可,代表該項菜單的准入權限,菜單生成文件中會默認調用 Authorized.check 進行判斷處理。

{
 name: '表單頁',
 icon: 'form',
 path: 'form',
 children: [{
   name: '基礎表單',
   path: 'basic-form',
 }, {
   name: '分步表單',
   path: 'step-form',
 }, {
   name: '高級表單',
   authority: 'admin', // 配置准入權限
   path: 'advanced-form',
 }],
}

見官方文檔:
傳送門:權限管理

注:ant design pro的官方文檔在國內可能需要翻牆訪問,ping到的ip地址位於新加坡

這種方法雖然做到了菜單的權限控制,滿足了不同的客服有權限進入不同的菜單,但體驗和私密卻不好,所有人都看到了所有的菜單項。所以為了更好地實現權限功能,我們需要(更好的)方法。

方法2

此種方法適用於:系統角色數量較多,每個角色的菜單權限均為動態的配置出來,以及每個角色的角色權限並非一成不變的情況
此種方法下,前端不再在路由表中為每個路由賦與權限,而通過由接口返回的路由數據動態生成菜單顯示,具體步驟為:

1 用戶登錄成功后,再單獨訪問一個拉取用戶權限的接口userPermission

2 該接口會根據用戶的信息返回該用的有訪問權限的路由表信息。

3 在前端通過比對全量的菜單數據得到用戶的有效路由值,
通過router.addRoutes動態掛載路由

4 使用vuex(redux)管理路由表,根據vuex中可訪問的路由渲染側邊欄組件。做到只顯示用戶有權限訪問的菜單。

注:

router.addRoutes為vue-router2.2之后新增加的api,能夠動態的添加路由。之前的版本無法做到。

值得說明的是,這些頁面級別的權限控制,雖然已經在前端做了,但是只在前端做是不夠的,服務端也依然要做接口層面的權限控制。

接口權限控制以及按鈕級別的權限控制

在頁面級權限控制的基礎上,即不同權限角色有權進入不同的代碼的基礎上,做到同一個頁面,不同的角色進入,顯示的按鈕不同,或者觸發點擊后,得到不同的結果呢?

我們采用了兩種方法相結合 :

1 : 特定按鈕的顯示與否
部分按鈕與接口邏輯的耦合度較深,比較容易在代碼層面做控制,通過服務端的接口中包含按鈕權限的數據,前端通過v-if手動控制其是否顯示

2 同樣顯示出的按鈕,點擊后觸發的權限結果不同
此種方法需要結合請求與響應攔截器一同實現
在此系統中,我們其實針對每一個請求都會驗證其使用者是否有操作權限,
在此系統服務端架構中,在業務邏輯操作層之上,有一層權限校驗層,當發現按鈕點擊人因為某種原因沒有權限使用此接口功能時,
(原因可能有一下:,比如通過地址進入了沒有權限使用的頁面,或者在別的電腦上登陸了相同的賬號,或者管理員在后台中把一個已經登陸的用戶權限刪除等等情況),此時,權限系統會在接口中返回異常的錯誤碼,其錯誤碼體現在接口數據的最外層 respCode中,除了登陸接口外的所有接口,我們統一使用返回值的respCode值作為權限驗證的結果,我們給項目中使用的ajax請求插件axios封裝了一個respone的攔截器,當respCode值為約定的異常代碼時,我們統一做跳轉等處理,否則做正確的業務邏輯。

權限設計架構示意圖:

接口格式:
{
"respCode": "0",     //使用此key作為權限控制的標識位
"respData": {        // 此key作為真正展示所需數據
  "status": "0",
  "msg": "操作提示信息"
},
"respMsg": "請求異常信息"
}
axios攔截器
util.ajax.interceptors.response.use(
res => {
  console.log(res.data)
  //對響應數據做些事
  //統一判斷后端返回的錯誤碼
  // const { respCode, respMsg = '' } = res.data
  // if (+respCode === 0) {
  //   const resData = res.data.respData
  //     return { ...resData, respCode, respMsg };
  // }
  if (res.data.respCode === '0') {
    return res.data.respData
  }
  if (res.data.respCode === -2 || res.data.respCode === -5) {
    vm.$Message.error(res.data.errMsg);
  }
  if (res.data.respCode === -8) {
    vm.$store.commit('logout', this);
  }
},
error => {
  alert("網絡異常~")
}
)

最后用一張腦圖來總結一下本文所闡述的內容


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM