版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://ligang.blog.csdn.net/article/details/82686892
前段时间,撰写过“ 单页应用优化–懒加载”的问题,这篇我们描述一下单页应用的另外一个问题权限。提起权限,一般会涉及如下几种情况:
前端的权限控制实质上就是用于展示,让操作变得更加友好,真正的安全实际上是由后端控制的!
下述所有示例,都使用Vue编写,会重点描述页面级别权限和模块级别权限
这里的使用权限是指用户登录,其实就是简单的判断登录状态而已。通常我们会使用Session进行控制,前端请求携带Cookie(Cookie中保存sessionID),服务端依此进行用户身份识别。然而,使用Session进行管理用户登录状态,在当下后台无状态化盛行的情况下,以及多台节点部署Session同步或者横向扩展(Scale-out,把 session 实现基于中心化的 Redis 服务)等问题(有成熟的解决方法,这里不赘述),已不是最佳方案。
前端后分离的项目中,往往采用Restful风格进行前后端约束,我们通常会在请求头中携带Authorization/Token来解决用户身份识别。
上述流程参考自:https://www.cnblogs.com/qianduantuanzhang/archive/2018/01/05/8204692.html
思路:
第一步:点击登录按钮,触发Vuex中的登录事件,成功返回Token,存储Token到sessionStorage/localStorage中(前后端可以约定相应的编码机制);
// 登录成功 store.commit(types.LOGIN) /* * 1. vuex中存储用户和token信息 * 2. 同步信息到localStorage中 * 3. 调整到相关页面 */
第二步:拦截处理
// 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); })
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,可动态挂载路由,无需在实例化之前就挂载上去的!
参考地址:https://juejin.im/post/591aa14f570c35006961acac
// 登录成功,触发 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
第一部分是域,第二部分是操作,第三部分是正在执行的实例。
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句