vue+router+axios 实现后台管理系统登录拦截(权限控制)

最近学习vue-cli3搭建后台管理项目,关于系统登录拦截和获取用户权限控制这一块是卡了挺久的一个难点,后台项目权限验证与安全性是非常重要的,可以说是一个后台项目一开始就必须考虑和搭建的基础核心功能。这篇文章写一下前后端分离下的登录解决方案,目前大多数都采用请求头携带 Token 的形式。

一、整体思路

  1. 首次登录时,后端服务器判断用户账号密码正确之后,根据用户id、用户名、定义好的秘钥、过期时间生成 token ,返回给前端;
  2. 前端拿到后端返回的 token ,存储在 localStroage 和 Vuex 里;
  3. 前端每次路由跳转,判断 localStroage 有无 token ,没有则跳转到登录页,有则请求获取用户信息,改变登录状态;
  4. 每次请求接口,在 Axios 请求头里携带 token;
  5. 后端接口判断请求头有无 token,没有或者 token 过期,返回401;
  6. 前端得到 401 状态码,重定向到登录页面,回到第一步。

二、封装axios

登录成功后,把后台返回的 Token 存在localStroage,检查有无 Token ,每次请求在 Axios 请求头上进行携带

如果状态码是401,则有可能是 Token 过期,需要跳转到登录页进行登录重新获取Token。

axios封装需根据后台返回的数据格式封装,例如我司后台返回数据格式如下:

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失效)跳转到登录页
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;
})

//返回值解构
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 ||
      "网络错误"
    );
  } 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 ||
    "网络错误"
  );
})

//切换页面取消请求
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,用于判断用户访问该路由时是否需要登录。例如:在用户直接跳转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信息后我们再去调取接口重新获取存入store

2.登陆拦截

根据当前路由的requireAuth字段判断当前页面是否需要登录验证,若需要登录验证,查看localStroage里有无Token ,有就继续执行,如果没有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里面的信息,因此每次刷新我们先调用获取用户信息接口获取用户信息存入vuex   
    let { data } = await getLoginInfo();
    store.commit("setLoginInfo", data);
    //判断是否需要登录验证
    let token = window.localStorage.getItem("token");
    if (to.meta.requiresAuth) {
      if (token) {
        next();
      } else {
        // 调取退出接口,将页面路由重定向到登录页进行登录
        logOut()
        next({
          path: "/login",
          query: { redirect: to.fullPath }
        });
      }
    } else {
      next();
    }
  }
};

四、Vuex配置

上面我们调用用户信息接口时用到了store进行存储,因此需要配置

// 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里头,跳转到首页之前重定向过来的页面。

    async handleSubmit() {
      // 先校验表单输入
      let flag = await this.$refs.loginForm.validate();
      if (!flag) return;
      try {
        this.loading = true;
        // 调用登录接口
        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;
      }
    }
  }

整体流程跑完了,实现的主要功能就是:

  1. 访问登录注册之外的路由,都需要登录权限,比如首页,判断有无token,有则访问成功,没有则跳转到登录页面;
  2. 成功登录之后,保存后端返回的token,跳转到之前重定向过来的页面;
  3. token 过期后,请求接口时,身份过期,跳转到登录页,继续第二步;

六、菜单权限

我们在首页调用getLoginInfo接口返回了用户信息、菜单列表和权限按钮列表的JSON数据,存入Vuex里头,这时我们就可以在左侧导航组件里根据后台返回的菜单列表数据动态显示左侧导航菜单,从而实现了页面权限

七、按钮权限

【7.1】自定义auth指令

我们定义一个auth指令来显示隐藏页面元素,从而实现按钮权限

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

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

【7.2】全局注册

【7.3】在页面使用

如下所示,通过自定义指令v-auth绑定字段,当绑定的字段包含在后端返回的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

文章每周持续更新,可以微信搜索「 前端大集锦 」第一时间阅读,回复【视频】【书籍】领取200G视频资料和30本PDF书籍资料

 

©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页