详解VueJS应用中管理用户权限


Posted in Javascript onFebruary 02, 2018

在需要身份验证的前端应用里,我们经常想通过用户角色来决定哪些内容可见。比如,游客身份可以阅读文章,但注册用户或管理员才能看到编辑按钮。

在前端中管理权限可能会有点麻烦。你之前可能写过这样的代码:

if (user.type === ADMIN || user.auth && post.owner === user.id ) {
 ...
}

作为代替方案,一个简洁轻量的库——CASL——可以让管理用户权限变得非常简单。只要你用CASL定义了权限,并设置了当前用户,就可以把上面的代码改为这样:

if (abilities.can('update', 'Post')) {
 ...
}

在这篇文章里,我会展示如何在前端应用里使用Vue.js和CASL来管理权限。

详解VueJS应用中管理用户权限

CASL 速成课程

CASL可以让你定义一系列规则来限制哪些资源对用户可见。

比如,CASL规则能够标明用户可以对给定的资源和实例(帖子、文章、评论等)进行哪些CRUD(Create, Read, Update和Delete)操作。

假设我们有分类广告网站。最显而易见的规则就是:

游客可以浏览所有帖子

管理员可以浏览所有帖子,并且可以更新或删除

使用CASL,我们用AbilityBuilder来定义规则。调用can来定义一条新规则。例如:

onst { AbilityBuilder } = require('casl');

export function(type) {
 AbilityBuilder.define(can => {
  switch(type) {
   case 'guest':
    can('read', 'Post');
    break;
   case 'admin':
    can('read', 'Post');
    can(['update', 'delete'], 'Post');
    break;
   // Add more roles here
  }
 }
};

现在,就可以用定义的规则来检查应用权限了。

import defineAbilitiesFor from './abilities';

let currentUser = {
 id: 999,
 name: "Julie"
 type: "registered",
};

let abilities = defineAbilitiesFor(currentUser.type);

Vue.component({
 template: `<div><div>
       <div>Please log in</div>
      `,
 props: [ 'post' ],
 computed: {
  showPost() {
   return abilities.can('read', 'Post');
  }
 }
});

Demo 课程

作为演示,我做了一个用来展示分类广告帖子的服务器/客户端应用。这个应用的规则是:用户能够阅读帖子或发帖,但是只能更新或删除自己的帖子。

我用Vue.js和CASL来方便地运行和扩展这些规则,即使以后添加新的操作或实例也将很方便。

现在我就带你一步步搭建这个应用。如果你想一睹为快,请戳这个Github repo。

定义用户权限

我们在 resources/ability.js中定义用户权限。CASL的一个优点是与环境无关,也就是说它既能在Node中运行,也能在浏览器中运行。

我们会把权限定义写到一个CommonJS模块里来保证Node的兼容性(Webpack能让这个模块用在客户端)。

resources/ability.js

const casl = require('casl');

module.exports = function defineAbilitiesFor(user) {
 return casl.AbilityBuilder.define(
  { subjectName: item => item.type },
  can => {
   can(['read', 'create'], 'Post');
   can(['update', 'delete'], 'Post', { user: user });
  }
 );
};

下面我们来剖析这段代码。

define方法的第二个参数,我们通过调用can来定义了权限规则。这个方法的第一个参数是你要允许的CRUD操作,第二个是资源或实例,在这个例子中是Post。

注意第二个can的调用,我们传了一个对象作为第三个参数。这个对象是用来测试user属性是否匹配我们提供的user对象。如果我们不这么做,那不光创建者可以删帖,谁都可以随便删了。

resources/ability.js

...
casl.AbilityBuilder.define(
 ...
 can => {
  can(['read', 'create'], 'Post');
  can(['update', 'delete'], 'Post', { user: user });
 }
);

CASL检查实例来分配权限时,需要知道实例的type。一种解决方式是把具有subjectName方法的对象,作为define方法的第一个参数,subjectName方法会返回实例的类型。

我们通过在实例中返回type来达成目的。我们需要保证,在定义Post对象时,这个属性是存在的。

resources/ability.js

...
casl.AbilityBuilder.define(
 { subjectName: item => item.type },
 ...
);

最后,我们把我们的权限定义封装到一个函数里,这样我们就可以在需要测试权限的时候直接传进一个user对象。在下面的函数中会更易理解。

resources/ability.js

const casl = require('casl');

module.exports = function defineAbilitiesFor(user) {
 ...
};

Vue 中的访问权限规则

现在我们想在前端应用中检查一个对象中,用户具有哪些CRUD权限。我们需要在Vue组件中访问CASL规则。这是方法:

引入Vue和 abilities plugin。这个插件会把CASL加到Vue的原型上,这样我们就能在组件内调用了。

在Vue 应用内引入我们的规则(例: resources/abilities.js)。

定义当前用户。实战中,我们是通过服务器来获取用户数据的,在这个例子中,我们简单地硬编码到到项目里。

牢记,abilities模块export一个函数,我们把它称为defineAbilitiesFor。我们会向这个函数传入用户对象。现在,无论何时,我们可以通过检测一个对象来得出当前用户拥有何种权限。

添加abilities插件,这样我们就可以在组件中像这样来进行测试了:this.$can(...)。

src/main.js

import Vue from 'vue';
import abilitiesPlugin from './ability-plugin';

const defineAbilitiesFor = require('../resources/ability');
let user = { id: 1, name: 'George' };
let ability = defineAbilitiesFor(user.id);
Vue.use(abilitiesPlugin, ability);

Post 实例

我们的应用会使用分类广告的帖子。这些表述帖子的对象会从数据库中检索,然后被服务器传给前端。比如:

我们的Post实例中有两个属性是必须的:

type属性。CASL会使用 abilities.js中的subjectName回调来检查正在测试的是哪种实例。

user属性。这是发帖者。记住,用户只能更新和删除他们发布的帖子。在 main.js中我们通过defineAbilitiesFor(user.id)已经告诉了CASL当前用户是谁。CASL要做的就是检查用户的ID和user属性是否匹配。

let posts = [
 {
  type: 'Post',
  user: 1,
  content: '1 used cat, good condition'
 },
 {
  type: 'Post',
  user: 2,
  content: 'Second-hand bathroom wallpaper'
 }
];

这两个post对象中,ID为1的George,拥有第一个帖子的更新删除权限,但没有第二个的。

在对象中测试用户权限

帖子通过Post组件在应用中展示。先看一下代码,下面我会讲解:

src/components/Post.vue

<template>
 <div>
  <div>

   <br /><small>posted by </small>
  </div>
  <button @click="del">Delete</button>
 </div>
</template>
<script> import axios from 'axios';

 export default {
  props: ['post', 'username'],
  methods: {
   del() {
    if (this.$can('delete', this.post)) {
     ...
    } else {
     this.$emit('err', 'Only the owner of a post can delete it!');
    }
   }
  }
 } </script>
<style lang="scss">...</style>

点击Delete按钮,捕获到点击事件,会调用del处理函数。

我们通过this.$can('delete', post)来使用CASL检查当前用户是否具有操作权限。如果有权限,就进一步操作,如果没有,就给出错误提示“只有发布者可以删除!”

服务器端测试

在真实项目里,用户在前端删除后,我们会通过 Ajax发送删除指令到接口,比如:

src/components/Post.vue

if (this.$can('delete', post)) {
 axios.get(`/delete/${post.id}`, ).then(res => {
  ...
 });
}

服务器不应信任客户端的CRUD操作,那我们把CASL测试逻辑放到服务器:

server.js

app.get("/delete/:id", (req, res) => {
 let postId = parseInt(req.params.id);
 let post = posts.find(post => post.id === postId);
 if (ability.can('delete', post)) {
  posts = posts.filter(cur => cur !== post);
  res.json({ success: true });
 } else {
  res.json({ success: false });
 }
});

CASL是同构(isomorphic)的,服务器上的ability对象就可以从abilities.js中引入,这样我们就不必复制任何代码了!

封装

此时,在简单的Vue应用里,我们就有非常好的方式管理用户权限了。

我认为this.$can('delete', post) 比下面这样优雅得多:

if (user.id === post.user && post.type === 'Post') {
 ...
}
Javascript 相关文章推荐
类似框架的js代码
Nov 09 Javascript
关于JavaScript的一些看法
May 27 Javascript
javascript中var的重要性分析
Feb 11 Javascript
AngularJS中的过滤器使用详解
Jun 16 Javascript
JavaScript函数的调用以及参数传递
Oct 21 Javascript
不同js异步函数同步的实现方法
May 28 Javascript
JS原生带缩略图的图片切换效果
Oct 10 Javascript
微信小程序template模板与component组件的区别和使用详解
May 22 Javascript
vue 返回上一页,页面样式错乱的解决
Nov 14 Javascript
VUE 实现动态给对象增加属性,并触发视图更新操作示例
Nov 29 Javascript
uin-app+mockjs实现本地数据模拟
Aug 26 Javascript
ES6的循环与可迭代对象示例详解
Jan 31 Javascript
原生JS实现网页手机音乐播放器 歌词同步播放的示例
Feb 02 #Javascript
使用express+multer实现node中的图片上传功能
Feb 02 #Javascript
Vue多种方法实现表头和首列固定的示例代码
Feb 02 #Javascript
jquery.picsign图片标注组件实例详解
Feb 02 #jQuery
使用webpack打包koa2 框架app
Feb 02 #Javascript
Vue组件化开发思考
Feb 02 #Javascript
微信小程序实现导航栏选项卡效果
Jun 19 #Javascript
You might like
PHP 魔术函数使用说明
2010/05/14 PHP
ThinkPHP3.1新特性之字段合法性检测详解
2014/06/19 PHP
php在数组中查找指定值的方法
2015/03/17 PHP
解决FLASH需要点击激活的代码
2006/12/20 Javascript
dojo 之基础篇
2007/03/24 Javascript
js左侧多级菜单动态的解决方案
2010/02/01 Javascript
页面载入结束自动调用js函数示例
2013/09/23 Javascript
Javascript写入txt和读取txt文件示例
2014/02/12 Javascript
零基础搭建Node.js、Express、Ejs、Mongodb服务器及应用开发入门
2014/12/20 Javascript
js实现数字每三位加逗号的方法
2015/02/05 Javascript
jQuery时间插件jquery.clock.js用法实例(5个示例)
2016/01/14 Javascript
Javascript数组Array基础介绍
2016/03/13 Javascript
BootStrap tab选项卡使用小结
2020/08/09 Javascript
jQuery实现6位数字密码输入框
2016/12/29 Javascript
vue.js实现数据动态响应 Vue.set的简单应用
2017/06/15 Javascript
es6中的解构赋值、扩展运算符和rest参数使用详解
2017/09/28 Javascript
js+html5生成自动排列对话框实例
2017/10/09 Javascript
Swiper自定义分页器使用详解
2017/12/28 Javascript
微信小程序实现YDUI的ScrollNav组件
2018/02/02 Javascript
angular6的响应式表单的实现
2018/10/10 Javascript
微信小程序 Storage更新详解
2019/07/16 Javascript
layer页面跳转,获取html子节点元素的值方法
2019/09/27 Javascript
Vue前端项目部署IIS的实现
2020/01/06 Javascript
vue中的.$mount('#app')手动挂载操作
2020/09/02 Javascript
Python中的字符串查找操作方法总结
2016/06/27 Python
Python中列表与元组的乘法操作示例
2018/02/10 Python
Python实现按中文排序的方法示例
2018/04/25 Python
pytorch进行上采样的种类实例
2020/02/18 Python
python脚本实现mp4中的音频提取并保存在原目录
2020/02/27 Python
爱尔兰电子产品购物网站:Komplett.ie
2018/04/04 全球购物
会计师事务所审计实习自我鉴定
2013/09/20 职场文书
入团者的自我评价分享
2013/12/02 职场文书
《找不到快乐的波斯猫》教学反思
2014/02/24 职场文书
入党群众意见范文
2015/06/02 职场文书
在人间读书笔记
2015/06/30 职场文书
解析Redis Cluster原理
2021/06/21 Redis