Vue前端鑑權方案,先後端分離

Vue前端鑑權方案,先後端分離

技術棧

前端Vue全家桶,後臺.net。css

需求分析

  1. 前端路由鑑權,屏蔽地址欄入侵
  2. 路由數據由後臺管理,前端只按固定規則異步加載路由
  3. 權限控制精確到每個按鈕
  4. 自動更新token
  5. 同一個瀏覽器只能登陸一個帳號

前端方案

對於需求一、二、3,採用異步加載路由方案前端

  1. 首先編寫vue全局路由守衛
  2. 排除登陸路由和無需鑑權路由
  3. 登陸後請求拉取用戶菜單數據
  4. 在vuex裏處理菜單和路由匹配數據
  5. 將在vuex裏處理好的路由數據經過addRoutes異步推入路由
router.beforeEach((to, from, next) => {
    // 判斷當前用戶是否已拉取權限菜單
    if (store.state.sidebar.userRouter.length === 0) {
      // 無菜單時拉取
      getMenuRouter()
        .then(res => {
          let _menu = res.data.Data.ColumnDataList || [];
          // if (res.data.Data.ColumnDataList.length > 0) {
          // 整理菜單&路由數據
          store.commit("setMenuRouter", _menu);
          // 推入權限路由列表
          router.addRoutes(store.state.sidebar.userRouter);
          next({...to, replace: true });
          // }
        })
        .catch(err => {
          // console.log(err);
          // Message.error("服務器鏈接失敗");
        });
    } else {
      //當有用戶權限的時候,說明全部可訪問路由已生成 如訪問沒權限的菜單會自動進入404頁面
      if (to.path == "/login") {
        next({
          name: "index"
        });
      } else {
        next();
      }
    }
  } else {
    // 無登陸狀態時重定向至登陸 或可進入無需登陸狀態路徑
    if (to.path == "/login" || to.meta.auth === 0) {
      next();
    } else {
      next({
        path: "/login"
      });
    }
  }
});
複製代碼
注意

我這裏無需鑑權的路由直接寫在router文件夾下的index.js,經過路由元信息meta攜帶指定標識vue

{
    path: "/err-404",
    name: "err404",
    meta: {
       authentication: false
    },
    component: resolve => require(["../views/error/404.vue"], resolve)
  },
複製代碼

上面說到路由是根據後臺返回菜單數據根據必定規則生成,所以一些不是菜單,又須要登陸狀態的路由,我寫在router文件夾下的router.js裏,在上面步驟4裏處理後臺返回菜單數據時,和處理好的菜單路由數據合併一同經過addRoutes推入。 這樣作會有必定的被地址欄入侵的風險,可是筆者這裏大可能是不過重要的路由,若是你要求咳咳,能夠定一份字典來和後臺接口配合精確加載每個路由。ios

// 加入企業
{
  path: "/join-company",
  name: "join-company",
  component: resolve => require([`@/views/index/join-company.vue`], resolve) 
},
複製代碼

在vuex中將分配的菜單數據轉化爲前端可用的路由數據,我是這樣作的: 管理系統在新增菜單時須要填寫一個頁面地址字段Url,前端獲得後臺菜單數據後根據Url字段來匹配路由加載的文件路徑,每一個菜單一個文件夾的好處是:你能夠在這裏拆分js、css和此菜單私有組件等vuex

menu.forEach(item => {
          let routerItem = {
            path: item.Url,
            name: item.Id,
            meta: {
              auth: item.Children,
            }, // 路由元信息 定義路由時便可攜帶的參數,可用來管理每一個路由的按鈕操做權限
            component: resolve =>
              require([`@/views${item.Url}/index.vue`], resolve) // 路由映射真實視圖路徑
          };
          routerBox.push(routerItem);
      });
複製代碼

關於如何精確控制每個按鈕我是這樣作的,將按鈕編碼放在路由元信息裏,在當前路由下匹配來控制頁面上的按鈕是否建立。 菜單數據返回的都是多級結構,每一個菜單下的子集就是當前菜單下的按鈕權限碼數組,我把每一個菜單下的按鈕放在此菜單的路由元信息meta.auth中。這樣做的好處是:按鈕權限校驗只需匹配每一個菜單路由元信息下的數據,這樣校驗池長度一般不會超過5個。axios

created() {
  this.owner = this.$route.meta.auth.map(item => item.Code);
}
methods: {
    matchingOwner(auth) {
      return this.owner.some(item => item === auth);
    }
}
    
複製代碼

需求4自動更新token,就是簡單的時間判斷,並在請求頭添加字段來通知後臺更新token並在頭部返回,前端接受到帶token的請求就直接更新token後端

// 在axios的請求攔截器中
    let token = getSession(auth_code);
    if (token) config.headers.auth = token;
    if (tokenIsExpire(token)) {
      // 判斷是否須要刷新jwt
      config.headers.refreshtoken = true;
    }
// 在axios的響應攔截器中
  if (res.headers.auth) {
    setSession(auth_code, res.headers.auth);
  }
複製代碼

對於需求5的處理比較麻煩,要跨tab頁只能經過cookielocal,筆者這裏不容許使用cookie所以採用的localstorage。經過打開的新頁面讀取localstorage內的token數據來同步多個頁面的帳號信息。token使用的jwt並前端md5加密。 這裏須要注意一點是頁面切換要當即同步帳號信息。數組

通過需求5改造後的全局路由守衛是這樣的:瀏覽器

function _AUTH_() {
// 切換窗口時校驗帳號是否發生變化
window.addEventListener("visibilitychange", function() {
  let Local_auth = getLocal(auth_code, true);
  let Session_auth = getSession(auth_code);
  if (document.hidden == false && Local_auth && Local_auth != Session_auth) {
    setSession(auth_code, Local_auth, true);
    router.go(0)
  }
})

router.beforeEach((to, from, next) => {
    // 判斷當前用戶是否已拉取權限菜單
    if (store.state.sidebar.userRouter.length === 0) {
      // 無菜單時拉取
      getMenuRouter()
        .then(res => {
          let _menu = res.data.Data.ColumnDataList || [];
          // if (res.data.Data.ColumnDataList.length > 0) {
          // 整理菜單&路由數據
          store.commit("setMenuRouter", _menu);
          // 推入權限路由列表
          router.addRoutes(store.state.sidebar.userRouter);
          next({...to, replace: true });
          // }
        })
        .catch(err => {
          // console.log(err);
          // Message.error("服務器鏈接失敗");
        });
    } else {
      //當有用戶權限的時候,說明全部可訪問路由已生成 如訪問沒權限的菜單會自動進入404頁面
      if (to.path == "/login") {
        next({
          name: "index"
        });
      } else {
        next();
      }
    }
  } else {
    // 無登陸狀態時重定向至登陸 或可進入無需登陸狀態路徑
    if (to.path == "/login" || to.meta.auth === 0) {
      next();
    } else {
      next({
        path: "/login"
      });
    }
  }
});
}
複製代碼

通過需求5改造後的axios的請求攔截器是這樣的,由於ie沒法使用visibilitychange,而且嘗試百度其餘屬性無效,所以在請求發出前作了粗暴處理:bash

if (ie瀏覽器) { 
    setLocal('_ie', Math.random())
    let Local_auth = getLocal(auth_code, true);
    let Session_auth = getSession(auth_code);
    if (Local_auth && Local_auth != Session_auth) {
      setSession(auth_code, Local_auth, true);
      router.go(0)
      return false
    }
  }
複製代碼

這裏有一個小問題須要注意:由於用的local所以首次打開瀏覽器可能會有登陸已過時的提示,這裏相信你們都能找到適合本身的處理方案

結語

通過這些簡單又好用的處理,一個基本知足需求的先後端分離前端鑑權方案就誕生啦

相關文章
相關標籤/搜索