详解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入门问答 整理的几个常见的初学者问题
Feb 22 Javascript
jquery中对表单的基本操作代码
Jul 29 Javascript
js中查找最近的共有祖先元素的实现代码
Dec 30 Javascript
jquery中交替点击事件的实现代码
Feb 14 Javascript
jQuery unbind 删除绑定事件详解
May 24 Javascript
基于Bootstrap的后台管理面板 Bootstrap Metro Dashboard
Jun 17 Javascript
微信小程序前端源码逻辑和工作流
Sep 25 Javascript
js中split()方法得到的数组长度问题
Jul 19 Javascript
AngularJS ui-router刷新子页面路由的方法
Jul 23 Javascript
详解Webpack多环境代码打包的方法
Aug 03 Javascript
微信小程序购物车、父子组件传值及calc的注意事项总结
Nov 14 Javascript
p5.js临摹旋转爱心
Oct 23 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中mkdir函数用法实例分析
2014/11/15 PHP
PHP实现限制IP访问的方法
2017/04/20 PHP
PHP实现的mysql主从数据库状态检测功能示例
2017/07/20 PHP
YII框架关联查询操作示例
2019/04/29 PHP
PHP时间相关常用函数用法示例
2020/06/03 PHP
jQuery UI 实现email输入提示实例
2013/08/15 Javascript
js计算两个时间之间天数差的实例代码
2013/11/19 Javascript
解析JavaScript中的不可见数据类型
2013/12/02 Javascript
原生javaScript做得动态表格(注释写的很清楚)
2013/12/29 Javascript
javascript常见操作汇总
2014/09/03 Javascript
JavaScript学习笔记之数组随机排序
2016/03/23 Javascript
Vue.js计算属性computed与watch(5)
2016/12/09 Javascript
使用Bootstrap + Vue.js实现添加删除数据示例
2017/02/27 Javascript
微信小程序 密码输入(源码下载)
2017/06/27 Javascript
JavaScript选取(picking)和反选(rejecting)对象的属性方法
2017/08/16 Javascript
JS动态插入脚本和插入引用外部链接脚本的方法
2018/05/21 Javascript
ES6 Promise对象的应用实例分析
2019/06/27 Javascript
vue路由结构可设一层方便动态添加路由操作
2020/08/31 Javascript
使用Python的urllib和urllib2模块制作爬虫的实例教程
2016/01/20 Python
利用python解决mysql视图导入导出依赖的问题
2017/12/17 Python
PyQt5每天必学之事件与信号
2018/04/20 Python
Python 实现取多维数组第n维的前几位
2019/11/26 Python
Python读写操作csv和excle文件代码实例
2020/03/16 Python
.net开发工程师面试题
2014/02/25 面试题
新锐科技Java程序员面试题
2016/07/25 面试题
文明礼仪事迹材料
2014/01/09 职场文书
2014年师德师风学习材料
2014/05/16 职场文书
社区四风存在问题及整改措施
2014/10/26 职场文书
毕业实习证明(4篇)
2014/10/28 职场文书
入党培养人考察意见
2015/06/08 职场文书
致接力运动员加油稿
2015/07/21 职场文书
2016年学校禁毒宣传活动工作总结
2016/04/05 职场文书
如何计划开一家便利店?
2019/07/31 职场文书
Python基础之元类详解
2021/04/29 Python
如何利用opencv判断两张图片是否相同详解
2021/07/07 Python
Java多线程并发FutureTask使用详解
2022/06/28 Java/Android