详解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 相关文章推荐
jquery中this的使用说明
Sep 06 Javascript
面向对象的Javascript之一(初识Javascript)
Jan 20 Javascript
jQuery插件slides实现无缝轮播图特效
Apr 17 Javascript
JQuery插入DOM节点的方法
Jun 11 Javascript
Windows系统下Node.js的简单入门教程
Jun 23 Javascript
js实现圆盘记速表
Aug 03 Javascript
HTML页面定时跳转方法解析(2种任选)
Dec 22 Javascript
基于JavaScript实现复选框的全选和取消全选
Feb 09 Javascript
vue动态组件实现选项卡切换效果
Mar 08 Javascript
vue父组件向子组件传递多个数据的实例
Mar 01 Javascript
vue和webpack项目构建过程常用的npm命令详解
Jun 15 Javascript
基于Vue实现图片在指定区域内移动的思路详解
Nov 11 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 checkdate、getdate等日期时间函数操作详解
2010/03/11 PHP
PHP header()函数常用方法总结
2014/04/11 PHP
PHP第三方登录―QQ登录实现方法
2017/02/06 PHP
PHP封装的多文件上传类实例与用法详解
2017/02/07 PHP
PHP面向对象五大原则之里氏替换原则(LSP)详解
2018/04/08 PHP
JavaScript中Math对象方法使用概述
2014/01/02 Javascript
textarea焦点的用法实现获取焦点清空失去焦点提示效果
2014/05/19 Javascript
JS+CSS实现TreeMenu二级树形菜单完整实例
2015/09/18 Javascript
jQuery通过ajax请求php遍历json数组到table中的代码(推荐)
2016/06/12 Javascript
jquery实现图片放大点击切换
2017/06/06 jQuery
js实现本地时间同步功能
2017/08/26 Javascript
React Native中TabBarIOS的简单使用方法示例
2017/10/13 Javascript
js实现倒计时器自定义时间和暂停
2019/02/25 Javascript
wx-charts 微信小程序图表插件的具体使用
2019/08/18 Javascript
jQuery 动画与停止动画效果实例详解
2020/05/19 jQuery
vue + node如何通过一个Txt文件批量生成MP3并压缩成Zip
2020/06/02 Javascript
[00:58]PWL开团时刻DAY5——十人开雾0换5
2020/11/04 DOTA
Python通过PIL获取图片主要颜色并和颜色库进行对比的方法
2015/03/19 Python
Python实现识别手写数字 Python图片读入与处理
2020/03/23 Python
python 实现对文件夹内的文件排序编号
2018/04/12 Python
详解Python 解压缩文件
2019/04/09 Python
Pycharm 文件更改目录后,执行路径未更新的解决方法
2019/07/19 Python
详解Python中打乱列表顺序random.shuffle()的使用方法
2019/11/11 Python
基于python 等频分箱qcut问题的解决
2020/03/03 Python
CSS3中Transform动画属性用法详解
2016/07/04 HTML / CSS
Sasa莎莎海外旗舰店:香港莎莎美妆平台
2018/03/21 全球购物
River Island美国官网:英国高街时尚品牌
2018/09/04 全球购物
如何高效率的查找一个月以内的数据
2012/04/15 面试题
送给程序员的20个Java集合面试问题
2014/08/06 面试题
会计系毕业个人自荐信格式
2013/09/23 职场文书
区优秀教师事迹材料
2014/02/10 职场文书
优良学风班申请材料
2014/02/13 职场文书
志愿者爱心公益活动策划方案
2014/09/15 职场文书
2014年重阳节敬老活动方案
2014/09/16 职场文书
2016年中学法制宣传日活动总结
2016/04/01 职场文书
关于nginx 实现jira反向代理的问题
2021/09/25 Servers