详解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 相关文章推荐
让getElementsByName适应IE和firefox的方法
Sep 24 Javascript
url地址自动加#号问题说明
Aug 21 Javascript
JavaScript 32位整型无符号操作示例
Dec 08 Javascript
Javscript删除数组中指定元素并返回新数组
Mar 06 Javascript
JS遍历ul下的li点击弹出li的索引的实现方法
Sep 19 Javascript
JS实现快速的导航下拉菜单动画效果附源码下载
Nov 01 Javascript
js入门之Function函数的使用方法【新手必看】
Nov 22 Javascript
基于JavaScript实现自定义滚动条
Jan 25 Javascript
Vue使用vue-cli创建项目
Sep 01 Javascript
关于预加载InstantClick的问题解决方法
Sep 12 Javascript
Angular5.0 子组件通过service传递值给父组件的方法
Jul 13 Javascript
原生js实现自定义滚动条
Jan 20 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 结果集的分页实现代码
2009/03/10 PHP
可缩放Reloaded-一个针对可缩放元素的复用组件
2007/03/10 Javascript
js使浏览器窗口最大化实现代码(适用于IE)
2013/08/07 Javascript
解析img图片没找到onerror事件 Stack overflow at line: 0
2013/12/23 Javascript
javascript中的事件代理初探
2014/03/08 Javascript
js如何判断用户是在PC端和还是移动端访问
2014/04/24 Javascript
jQuery中:has选择器用法实例
2014/12/30 Javascript
angularjs客户端实现压缩图片文件并上传实例
2015/07/06 Javascript
jquery基础知识第一讲之认识jquery
2016/03/17 Javascript
基于jQuery实现仿百度首页选项卡切换效果
2016/05/29 Javascript
JavaScript制作颜色反转小游戏
2016/09/25 Javascript
Vue form 表单提交+ajax异步请求+分页效果
2017/04/22 Javascript
解决vue attr取不到属性值的问题
2018/09/18 Javascript
微信小程序 导入图标实现过程详解
2019/10/11 Javascript
vue2.0 获取从http接口中获取数据,组件开发,路由配置方式
2019/11/04 Javascript
vue如何搭建多页面多系统应用
2020/06/17 Javascript
vue自定义组件(通过Vue.use()来使用)即install的用法说明
2020/08/11 Javascript
php使用递归与迭代实现快速排序示例
2014/01/23 Python
python连接mysql实例分享
2016/10/09 Python
Python编程实战之Oracle数据库操作示例
2017/06/21 Python
python利用OpenCV2实现人脸检测
2020/04/16 Python
Django Rest framework认证组件详细用法
2019/07/25 Python
Python中利用LSTM模型进行时间序列预测分析的实现
2019/07/26 Python
Python爬虫入门教程02之笔趣阁小说爬取
2021/01/24 Python
W3C公布最新的HTML5标准草案
2008/10/17 HTML / CSS
欧洲最大的美妆零售网站:Feelunique
2017/01/14 全球购物
Hotter Shoes美国官网:英国最受欢迎的舒适鞋
2018/08/02 全球购物
飞利信loadrunner和软件测试笔试题
2012/09/22 面试题
本科生的职业生涯规划范文
2014/01/09 职场文书
管理信息系学生的自我评价
2014/01/11 职场文书
教师党的群众路线教育实践活动学习笔记
2014/11/05 职场文书
2014年团支部年度工作总结
2014/12/24 职场文书
2015年全国科普日活动总结
2015/03/23 职场文书
医学生自荐信范文(2016精选篇)
2016/01/28 职场文书
带你彻底理解JavaScript中的原型对象
2021/04/14 Javascript
SQL Server中的逻辑函数介绍
2022/05/25 SQL Server