(十三)Vue3-huohuo-admin 验证与权限

413 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天

验证与权限

上一篇我们讲述了登录页面的设计与基本实现,这里我想再谈一谈一些验证与权限相关的信息。

登录验证

后台项目区别于其他项目的一大特点,就是权限验证及其安全性。我们开发时一般做的第一个页面,就是登录页面,它们是息息相关的。

用户输入用户名和密码后,首先通过前端的输入格式和验证工具进行验证,验证成功后,再login到后端端验证此用户是否存在及密码是否正确。后端验证成功后,一般会返回一个token和用户信息,它们会被存到sessionStorage中,作为与后端数据交互的权限标识。

前端验证

一般验证方式:

输入格式验证

image-20220530163227271

比如登录验证,我们主要验证的是账号、密码、验证码,对应的规则遵循EP的约定。

有一个问题是,我们怎么知道这些约定是什么,又怎么知道我们是否正确遵循了这些约定?TS 类型能帮助我们解决这个问题。我们需要使用到的是EP中自带的type,不幸的是,这些type需要我们自己去源码中寻找,应该使用哪一个,比如表单的规则typeFormRules,我们使用前需要找到他并引入,以配合 TS 给我们友好的代码提示(可能有更好的办法,欢迎评论)。

image-20220530173207024

工具验证

短信验证码、图形验证码(本项目使用)、滑块图、文案编辑等

image-20220530173840909

后端验证

  • 除了前端的格式验证,后端也应该对前端伴随着请求体传过来的数据进行格式校验
  • 一些重要信息一般通过数据库对比校验进行判断是否通过,比如登录信息
  • 一些页面访问权限、操作权限通过用户的角色信息加以控制

路由权限

项目中我们主要做的的权限控制有两种,登录权限(是否能成功登录)和角色权限(角色信息如菜单路由、操作按钮权限)。来看看具体是如何实现的吧!

上面我们已经详细讲过权限中有关验证与登录的内容,用户登录成功后,后端会返回某些信息info,来帮助我们控制更具体化的角色权限。

通常我们会以角色来做权限判定标准,执行可许的权限操作。

权限的路由控制

首先我们来分析,通过权限,有哪些方式可以控制路由,也就是我们的可访问页面。

思路一

纯后端动态控制路由表,前端写好页面及其路由

const routes = [
  {
    path: "/test",
    component: () => import("/@/views/test/test.vue"),
  },
];

登录后,根据用户的权限,路由表(含所有路由配置项)全部从后端一次性获取

{
    path: "/test",
    name: "test",
    redirect: "/test/test",
    meta: {
        title: "test",
        icon: "test_icon",
        showLink: true,
        rank: 100
	},
	children: {...}
}

思路二

前端控制页面级权限:拿到后端用户返回的角色role,映射出前端写好的路由表控制当前角色能够看到的页面(如菜单栏)

前端写好页面及所有路由表

const routes =[
	{
	path: '/test',
    name: "test",
    component: () => import('/@/views/test/test.vue')
    meta: {
        title: "test",
        icon: "test_icon",
        roles: ['admin', 'test'] // 用户角色符合该配置项,则将该路由添加进最终路由表
	},
	children: {...}
	}
]

后端控制请求权限:用户向服务端发送请求头携带token的请求,服务端根据用户角色来验证是否有该操作的权限

思路三(推荐)

通过info获取到role,再从后端动态地根据role拿到权限相关的信息(如动态路由表、按钮权限),将通过权限过滤出来的动态路由表,与前端写好的静态路由表结合,生成最终路由表。

本项目采用思路三的方式开发的原因:

  • 静态路由与动态路由区分开
  • 通过用户角色过滤动态路由表,菜单栏由路由表动态生成
  • 权限相对灵活,后端对角色权限的改动,前端不需要跟着改动

简单实现

登录的成功后,我们做了一个初始化路由操作,传入用户角色的参数,目的就是拿到一份对应角色的动态路由表(permissionRouter),也就是权限的过滤。

image-20220530180457848

这份后端传过来的动态路由表(permissionRouter),我们在mockmock/asyncRoutes.ts)中模拟出来,后端同样是根据用户角色参数来返回正确的动态路由信息。

如下,我们根据登录时调用的初始化路由方法(initRouter),并传入角色参数admin,该mock接口会根据传入的query参数,返回不同的info,也就是动态路由表。这样用户所能访问的涉及权限的路由就都包含在这份表上,达到路由权限的控制。

export default [
  {
    url: "/getAsyncRoutes",
    method: "get",
    response: ({ query }) => {
      if (query.name === "admin") {
        // 不同角色返回不同的路由信息
        return {
          code: 0,
          info: [setDifAuthority("v-admin", permissionRouter)],
        };
      } else {
        return {
          code: 0,
          info: [setDifAuthority("v-test", permissionRouter)],
        };
      }
    },
  },
] as MockMethod[];

其他权限控制

除了比较核心的路由权限外,还有一些细粒化的权限控制,比如按钮权限中一些涉及角色操作与角色切换的行为。

直接使用用户信息

这里我们以权限界面的一个简单角色切换功能为例分析(src/views/permisson/page/index):

<script setup lang="ts">
import { ref, unref } from "vue";
import { storageSession } from "/@/utils/storage";
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";

defineOptions({
  name: "PermissionPage",
});

let purview = ref<string>(storageSession.getItem("info").username);

function changRole() {
  if (unref(purview) === "admin") {
    storageSession.setItem("info", {
      username: "test",
      accessToken: "eyJhbGciOiJIUzUxMiJ9.test",
    });
    window.location.reload();
  } else {
    storageSession.setItem("info", {
      username: "admin",
      accessToken: "eyJhbGciOiJIUzUxMiJ9.admin",
    });
    window.location.reload();
  }
}
</script>

<template>
  <el-card>
    <template #header>
      <div class="card-header">
        <span>
          当前角色:
          <span style="font-size: 26px">{{ purview }}</span>
          <p style="color: #ffa500">
            查看左侧菜单变化(系统管理),模拟后台根据不同角色返回对应路由
          </p>
        </span>
      </div>
    </template>
    <el-button
      type="primary"
      @click="changRole"
      :icon="useRenderIcon('user', { color: '#fff' })"
    >
      切换角色
    </el-button>
  </el-card>
</template>

使用角色指令

auth指令。有时候指令能够让我们更方便地控制界面上一些比较小的按钮操作,比如列表中某一项或者某个按钮是否对当前角色可见。

这里我们简单看看角色指令v-adminv-test的使用:

<!-- src/views/permisson/button.index.vue -->
<script setup lang="ts">
  import { ref } from "vue";
  import { storageSession } from "/@/utils/storage";

  defineOptions({
    name: "PermissionButton",
  });

  const auth = ref<boolean>(storageSession.getItem("info").username || "admin");

  function changRole(value) {
    storageSession.setItem("info", {
      username: value,
      accessToken: `eyJhbGciOiJIUzUxMiJ9.${value}`,
    });
    window.location.reload();
  }
</script>

<template>
  <el-card>
    <template #header>
      <div class="card-header">
        <el-radio-group v-model="auth" @change="changRole">
          <el-radio-button label="admin" />
          <el-radio-button label="test" />
        </el-radio-group>
      </div>
    </template>
    <p v-auth="'v-admin'">只有admin可看</p>
    <p v-auth="'v-test'">只有test可看</p>
  </el-card>
</template>