持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天
验证与权限
上一篇我们讲述了登录页面的设计与基本实现,这里我想再谈一谈一些验证与权限相关的信息。
登录验证
后台项目区别于其他项目的一大特点,就是权限验证及其安全性。我们开发时一般做的第一个页面,就是登录页面,它们是息息相关的。
用户输入用户名和密码后,首先通过前端的输入格式和验证工具进行验证,验证成功后,再login到后端端验证此用户是否存在及密码是否正确。后端验证成功后,一般会返回一个token和用户信息,它们会被存到sessionStorage中,作为与后端数据交互的权限标识。
前端验证
一般验证方式:
输入格式验证
比如登录验证,我们主要验证的是账号、密码、验证码,对应的规则遵循EP的约定。
有一个问题是,我们怎么知道这些约定是什么,又怎么知道我们是否正确遵循了这些约定?TS 类型能帮助我们解决这个问题。我们需要使用到的是
EP中自带的type,不幸的是,这些type需要我们自己去源码中寻找,应该使用哪一个,比如表单的规则type是FormRules,我们使用前需要找到他并引入,以配合 TS 给我们友好的代码提示(可能有更好的办法,欢迎评论)。
工具验证
短信验证码、图形验证码(本项目使用)、滑块图、文案编辑等
后端验证
- 除了前端的格式验证,后端也应该对前端伴随着请求体传过来的数据进行格式校验
- 一些重要信息一般通过数据库对比校验进行判断是否通过,比如登录信息
- 一些页面访问权限、操作权限通过用户的角色信息加以控制
路由权限
项目中我们主要做的的权限控制有两种,登录权限(是否能成功登录)和角色权限(角色信息如菜单路由、操作按钮权限)。来看看具体是如何实现的吧!
上面我们已经详细讲过权限中有关验证与登录的内容,用户登录成功后,后端会返回某些信息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),也就是权限的过滤。
这份后端传过来的动态路由表(permissionRouter),我们在mock(mock/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-admin和v-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>