vue+router+axios 實現(xiàn)后臺管理系統(tǒng)登錄攔截(權(quán)限控制)

最近學(xué)習(xí)vue-cli3搭建后臺管理項目,關(guān)于系統(tǒng)登錄攔截和獲取用戶權(quán)限控制這一塊是卡了挺久的一個難點,后臺項目權(quán)限驗證與安全性是非常重要的,可以說是一個后臺項目一開始就必須考慮和搭建的基礎(chǔ)核心功能。這篇文章寫一下前后端分離下的登錄解決方案,目前大多數(shù)都采用請求頭攜帶 Token 的形式。

一、整體思路

  1. 首次登錄時,后端服務(wù)器判斷用戶賬號密碼正確之后,根據(jù)用戶id、用戶名、定義好的秘鑰、過期時間生成 token ,返回給前端;
  2. 前端拿到后端返回的 token ,存儲在 localStroage 和 Vuex 里;
  3. 前端每次路由跳轉(zhuǎn),判斷 localStroage 有無 token ,沒有則跳轉(zhuǎn)到登錄頁,有則請求獲取用戶信息,改變登錄狀態(tài);
  4. 每次請求接口,在 Axios 請求頭里攜帶 token;
  5. 后端接口判斷請求頭有無 token,沒有或者 token 過期,返回401;
  6. 前端得到 401 狀態(tài)碼,重定向到登錄頁面,回到第一步。

二、封裝axios

登錄成功后,把后臺返回的 Token 存在localStroage,檢查有無 Token ,每次請求在 Axios 請求頭上進(jìn)行攜帶

如果狀態(tài)碼是401,則有可能是 Token 過期,需要跳轉(zhuǎn)到登錄頁進(jìn)行登錄重新獲取Token。

axios封裝需根據(jù)后臺返回的數(shù)據(jù)格式封裝,例如我司后臺返回數(shù)據(jù)格式如下:

import axios from "axios";
import { Message } from "iview";
let router = import("@/router");

axios.defaults.baseURL = "/api";
axios.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8";
axios.defaults.headers["X-Requested-With"] = "XMLHttpRequest";
axios.defaults.headers["Cache-Control"] = "no-cache";
axios.defaults.headers["pragma"] = "no-cache";

let source = axios.CancelToken.source();

//請求添加token
axios.interceptors.request.use(request => {
  request.headers["Authorization"] = window.localStorage.getItem('token') ? window.localStorage.getItem('token') : "";
  return request;
});

//登錄過期(token失效)跳轉(zhuǎn)到登錄頁
axios.interceptors.response.use(response => {
  let data = response.data;
  if (
    data.state && [401].includes(data.state.code)
  ) {
    router.then(lib => {
      if (lib.default.currentRoute.name === "login") return;
      lib.default.push({
        name: "login"
      })
      Message.warning(data.state.msg);
    });
  }
  return response;
})

//返回值解構(gòu)
axios.interceptors.response.use(response => {
  let data = response.data;
  let isJson = (response.headers["content-type"] || "").includes("json");
  if (isJson) {
    if (data.state && data.state.code === 200) {
      return Promise.resolve({
        data: data.data,
        msg: data.state.msg,
        code: data.state.code,
        page: data.page
      });
    }
    return Promise.reject(
      data.state &&
      data.state.msg ||
      "網(wǎng)絡(luò)錯誤"
    );
  } else {
    return data;
  }
}, err => {
  let isCancel = axios.isCancel(err);
  if (isCancel) {
    return new Promise(() => {});
  }
  return Promise.reject(
    err.response.data &&
    err.response.data.state &&
    err.response.data.state.msg ||
    "網(wǎng)絡(luò)錯誤"
  );
})

//切換頁面取消請求
axios.interceptors.request.use(request => {
  request.cancelToken = source.token;
  return request;
});
router.then(lib => {
  lib.default.beforeEach((to, from, next) => {
    source.cancel()
    source = axios.CancelToken.source();
    next()
  })
})

export function post(url, data, otherConfig) {
  return axios.post(url, data, otherConfig);
}

export function get(url, data, otherConfig) {
  return axios.get(url, {
    params: data,
    ...otherConfig
  });
}

三、路由攔截

首先在定義路由的時候就需要多添加一個自定義字段requireAuth,用于判斷用戶訪問該路由時是否需要登錄。例如:在用戶直接跳轉(zhuǎn)mainPage頁面的時候,需要判斷用戶是否登錄,那么我們在該路由下添加meta字段。

const router = new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes: [{
            path: '/',
            name: 'MainPage',
            component: mainPage,
            children: pages,
            meta: {
                requiresAuth: true  // 訪問該路由時需要判斷是否登錄
            }
        },
        {
            path: '/login',
            name: 'login',
            component: Login
        },
    ]
})

給mainPage首頁路由增加了 requiresAuth,所以我們需要在mainPage頁面里面使用路由鉤子beforeRouteEnter來攔截路由,

路由攔截兩個作用:

1.獲取用戶信息

由于每次刷新頁面store里面的信息會清空,因此我們可以在beforeRouteEnter增加獲取用戶信息接口,這樣每次清空store信息后我們再去調(diào)取接口重新獲取存入store

2.登陸攔截

根據(jù)當(dāng)前路由的requireAuth字段判斷當(dāng)前頁面是否需要登錄驗證,若需要登錄驗證,查看localStroage里有無Token ,有就繼續(xù)執(zhí)行,如果沒有Token ,重定向到登錄頁。

// mainPage頁面

import LeftNav from "@/components/leftNav";
import { getLoginInfo, logout } from "@/api/getData";
import store from "@/store";
export default {
  components: { LeftNav },
  // 路由攔截
  async beforeRouteEnter(to, from, next) {
    // 因為刷新頁面每次都會清空strore里面的信息,因此每次刷新我們先調(diào)用獲取用戶信息接口獲取用戶信息存入vuex   
    let { data } = await getLoginInfo();
    store.commit("setLoginInfo", data);
    //判斷是否需要登錄驗證
    let token = window.localStorage.getItem("token");
    if (to.meta.requiresAuth) {
      if (token) {
        next();
      } else {
        // 調(diào)取退出接口,將頁面路由重定向到登錄頁進(jìn)行登錄
        logOut()
        next({
          path: "/login",
          query: { redirect: to.fullPath }
        });
      }
    } else {
      next();
    }
  }
};

四、Vuex配置

上面我們調(diào)用用戶信息接口時用到了store進(jìn)行存儲,因此需要配置

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    loginInfo: {},
  },
  mutations: {
    setLoginInfo(state, data) {
      state.loginInfo = data;
    },
  },
  actions: {},
  modules: {}
})

五、login頁面登錄

這里我寫了個登錄組件,下面是點擊登錄時的 handleSubmit 方法,登錄成功后我們需要把后端返回的token存入localStrage里頭,跳轉(zhuǎn)到首頁之前重定向過來的頁面。

    async handleSubmit() {
      // 先校驗表單輸入
      let flag = await this.$refs.loginForm.validate();
      if (!flag) return;
      try {
        this.loading = true;
        // 調(diào)用登錄接口
        let { msg } = await login({
          userName: this.user,
          password: this.password
        });
        this.$Message.success(msg);
        window.localStorage.setItem('token', res.data.token) // 登錄成功后將后臺返回的token存到localStorage
        // 跳回指定路由
        let redirectUrl = decodeURIComponent(this.$route.query.redirect || "/");
        this.$router.push({ path: redirectUrl });
      } catch (e) {
        this.$Message.warning(e);
      } finally {
        this.loading = false;
      }
    }
  }

整體流程跑完了,實現(xiàn)的主要功能就是:

  1. 訪問登錄注冊之外的路由,都需要登錄權(quán)限,比如首頁,判斷有無token,有則訪問成功,沒有則跳轉(zhuǎn)到登錄頁面;
  2. 成功登錄之后,保存后端返回的token,跳轉(zhuǎn)到之前重定向過來的頁面;
  3. token 過期后,請求接口時,身份過期,跳轉(zhuǎn)到登錄頁,繼續(xù)第二步;

六、菜單權(quán)限

我們在首頁調(diào)用getLoginInfo接口返回了用戶信息、菜單列表和權(quán)限按鈕列表的JSON數(shù)據(jù),存入Vuex里頭,這時我們就可以在左側(cè)導(dǎo)航組件里根據(jù)后臺返回的菜單列表數(shù)據(jù)動態(tài)顯示左側(cè)導(dǎo)航菜單,從而實現(xiàn)了頁面權(quán)限

七、按鈕權(quán)限

【7.1】自定義auth指令

我們定義一個auth指令來顯示隱藏頁面元素,從而實現(xiàn)按鈕權(quán)限

// util/auth.js
import store from "@/store";

export default {
  bind(el, binding, vnode) {
    let auths = store.state.loginInfo.roleBtns;  // 后臺返回的按鈕權(quán)限列表
    let hasAuth = auths.includes(binding.value);
    if (hasAuth) {
      delete el.style.display;
    } else {
      el.style.display = "none";
    }
  }
}

【7.2】全局注冊

【7.3】在頁面使用

如下所示,通過自定義指令v-auth綁定字段,當(dāng)綁定的字段包含在后端返回的roleBtns列表里,那么該按鈕會顯示,否則隱藏

<div class="header-right">
    <el-button v-auth="'editProduct'" type="primary" size="mini" @click="addProductRelation">新增
    </el-button>
    <el-button v-auth="'deleteProduct'" type="primary" size="mini" @click="deleteSelectProduct">刪除</el-button>
</div>

八、項目GitHub地址

vue-iview3-admin

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容