首页 > 其他分享 >单页应用优化--权限

单页应用优化--权限

时间:2023-03-13 12:32:18浏览次数:55  
标签:登录 单页 -- 后台 router 权限 路由 页面


前段时间,撰写过“​​ 单页应用优化–懒加载​​”的问题,这篇我们描述一下单页应用的另外一个问题权限。提起权限,一般会涉及如下几种情况:

  • 应用使用权【登录】
  • 页面级别权限【菜单访问权限】
  • 模块级别权限【区域<组件、按钮>是否显示】
  • 数据级别权限【数据权限】

前端的权限控制实质上就是用于展示,让操作变得更加友好,真正的安全实际上是由后端控制的!

下述所有示例,都使用Vue编写,会重点描述页面级别权限模块级别权限

应用使用权限

这里的使用权限是指用户登录,其实就是简单的判断登录状态而已。通常我们会使用Session进行控制,前端请求携带Cookie(Cookie中保存sessionID),服务端依此进行用户身份识别。然而,使用Session进行管理用户登录状态,在当下后台无状态化盛行的情况下,以及多台节点部署Session同步或者横向扩展(Scale-out,把 session 实现基于中心化的 Redis 服务)等问题(有成熟的解决方法,这里不赘述),已不是最佳方案。

前端后分离的项目中,往往采用​​Restful​​风格进行前后端约束,我们通常会在请求头中携带Authorization/Token来解决用户身份识别。

单页应用优化--权限_权限

思路:

第一步:点击登录按钮,触发Vuex中的登录事件,成功返回Token,存储Token到sessionStorage/localStorage中(前后端可以约定相应的编码机制);

// 登录成功
store.commit(types.LOGIN)
/*
* 1. vuex中存储用户和token信息
* 2. 同步信息到localStorage中
* 3. 调整到相关页面
*/

第二步:拦截处理

  • 【请求后台API】Axios Request钩子中,添加Authorization头,服务端获取进行校验;如果存在伪造情况,返回401,前端在Axios Response钩子中,进行捕获处理

单页应用优化--权限_单页应用_02

// Axios Request钩子
axios.interceptors.request.use(req => {
req.headers.Authorization = store.state.token
return req;
}, error => {
return Promise.reject(error);
})

// Axios Response钩子
axios.interceptors.response.use(res => {
return res;
}, error => {
switch(error.response.status) {
case 401:
// 触发退出操作 并跳转到登录页面
store.commit(types.LOGOUT)
router.replace({path: '/login'})
}
return Promise.reject(error);
})
  • 【页面跳转】路由beforeEach钩子进行token信息校验(这里只能校验是否存在,具体准确性无法校验)
router.beforeEach((to, from, next) => {
// 注销 或者 没有用户信息
if(to.path === '/login' || store.state.[info]) {
store.commit(types.LOGOUT)
next({path: '/login'})
} else {
next()
}
})

页面级别权限

需要​​router.addRoutes​​动态挂载路由。vue2.2.0以后新增了router.addRoutes​,可动态挂载路由,无需在实例化之前就挂载上去的!

  • 创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页面;
  • 当用户登录后,获取用户权限列表,生成最终用户可访问的路由表;
  • 调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由;
  • 使用vuex管理路由表,根据vuex中可访问的路由渲染header、侧边栏组件。


// 登录成功,触发
this.updateRouter(data.routes)

// ...
methods: {
// routes是后台返回来的路由信息 routes里应包含404情况
async updateRouter (routes) {
// routers默认
const routers = [{
path: '/login',
name: 'login',
component: login
}, {
path: '/',
component: App,
redirect: 'index',
children: []
}]
routes.forEach(r => {
routers[1].children.push({
name: r.name,
path: r.path,
component: () => routesMap[r.component]
})
})
this.$router.addRoutes(routers)
this.$router.push('/')
}
}

这样就实现了根据后端的返回动态扩展路由,当然也可以根据后端的返回生成侧栏或顶栏的导航菜单,这样就不需要再在前端处理页面权限了。需要注意的是,上面有待处理问题:

  • 登录成功后默认跳转到’/’,并非后台指定

注意事项:这里有一个需要非常注意的地方就是 ​​404​​​ 页面一定要最后加载,如果放在​​routers​​​一同声明了​​404​​​,后面的所以页面都会被拦截到​​404​​,详细的问题见​​addRoutes when you’ve got a wildcard route for 404s does not work​​

对于后台返回的routes的说明:

方式一:后台完整返回整个路由,这里后台需要返回component的加载信息,然后前端直接addRoutes指定路由下(无权限的路由不会挂载,但后台需要指定component地址,前端强制依赖后台);

方式二:后台返回相关路由权限标识,前端将完整路由进行标识展示(所有路由会被挂载

我们采用二者结合方式,使用后台路由标识name(这里需要保证name的唯一性),然后前端根据后台返回的标识对路由进行剔除,动态添加路由。

模块级别权限

某些按钮是否可以点击;某些区域是否可以查看~

组件形式

这里使用render函数,它比template更接近编译器。

// Auth.vue
<script>
export default {
name: 'Auth-Comp',
functional: true,
// 增加了context来弥补缺少的实例
render: function(createElement, context) {
let {props, children, data} = context
if(props.auth) {
// return children
// 完全透明的传入任何特性、事件监听器、子节点等。
return createElement('div', data, children)
} else {
return null
}
}
}
</script>

// 使用
<Auth-Comp :auth="true"><Hello></Hello></Auth-Comp>

缺点,多添加了一层div,因为不允许存在多个根节点;注意,这里不能使用context.slots().default,因为如果存在具名slots会展示不全

指令形式

Vue.directive('auth', {
inserted(el, binding, vnode) {
let {value} = binding
if(value && !hasPermission(value)) {
el.parentNode && el.parentNode.removeChild(el)
}
}
})

// 使用
<Hello v-auth="true"></Hello>

一个指令定义对象可以提供几个钩子(均可选):

  • ​bind​​:指令第一次绑定到元素时调用,只调用一次
  • ​inserted​​:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中);
  • ​update​​:所有组件VNode更新时调用,可能发生在其子VNode更新前,可以比较更新前后的值来忽略不必要的模板更新;
  • ​componentUpdate​​:指令所在组件的VNode**及其子VNode**全部更新后调用;
  • ​unbind​​:只调用一次,指令与元素解绑时调用。

缺点,不能在<template>标签上使用!

数据级别权限

这通常需要服务端根据用户权限对数据进行控制,来确保是否返回给前端,前端根据返回结果进行展示~~~

补充

公司采用的权限标识为8421,即

delete

put

post

get

8

4

2

1

关于权限规则,也可以采用Apache Shiro的规则。​​printer:query:lp7200​​ 第一部分是域,第二部分是操作,第三部分是正在执行的实例。

参考地址:​​http://shiro.apache.org/permissions.html​


标签:登录,单页,--,后台,router,权限,路由,页面
From: https://blog.51cto.com/u_15998238/6117528

相关文章

  • 深入理解ES6--用模块封装代码
    用模块封装代码在模块顶部创建的变量不会自动被添加到全局共享作用域(模块顶部this的值为undefined),必须导出后,外部代码才可访问。浏览器中使用模块​​<script>​​的type属......
  • 深入理解ES6--迭代器、生成器、代理、反射、Promise
    迭代器(Iterator)和生成器(Generator)for-of循环及展开运算符…都是针对迭代器的!!!不能使用箭头函数来创建生成器;ES6函数的简写方式可以(只需在函数名前加星号)可迭代对象具有Symbol......
  • 深入理解ES6--Set、Map及Symbol
    Set集合和Map集合Set集合是一种无重复元素的列表,通常用来检测给定的值在某个集合中是否存在;Map集合内含多组键值对,通常用来缓存频繁取用的数据。ES5中的问题varmap=Objec......
  • 2_Servlet初识
    ​ Servlet开发流程在后台随机生成一个整数当浏览器请求一个Servlet时如果生成的是奇数,返回"happynewyear"如果生成的是偶数,返回"happybirthday"1创建一个JAVA......
  • 脑瘫A+B问题(官方的题目-_-)
    1importjava.util.*;23publicclassMain4{publicstaticvoidmain(Stringargs[]){5Scannerscanner=newScanner(System.in);6......
  • 2_Servlet初识
    ​ Servlet开发流程在后台随机生成一个整数当浏览器请求一个Servlet时如果生成的是奇数,返回"happynewyear"如果生成的是偶数,返回"happybirthday"1创建一个JAVA......
  • Docker设置JDK17的JVM启动参数踩坑
    背景需求中需要接入腾讯广告的SDK,在编写完代码进行自测时,直接报错,提示Unabletomakeprotectedfinaljava.lang.Classjava.lang.ClassLoader.defineClass堆栈信息如......
  • SpringBoot--过滤器/拦截器/AOP--区别/使用/顺序
    SpringBoot--过滤器/拦截器/AOP--区别/使用/顺序https://knife.blog.csdn.net/article/details/121387483?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant......
  • linux安装tomcat
    linux安装tomcat一、下载安装包链接:https://pan.baidu.com/s/1TibyDHGbwc3YIEX0ZDJ8jg提取码:183q需要用到其他版本的tomcat,请自行下载:下载地址:http://tom......
  • 特殊回文数
    1importjava.util.*;23publicclassMain{4publicstaticvoidmain(String[]args){5Scannerscanner=newScanner(System.in);6......