在前端应用程序的认证过程中,我们常常想根据自己的角色指派来做些改变,使得一些内容是用户可见,另外一些不可见。例如,guest用户可以看到一个帖子,但只有注册用户或管理员才可以看到一个按钮来编辑该邮件。
在前端应用程序中权限管理可能十分的凌乱。你可能以前也写过这样的代码:
if (user.type === ADMIN || user.auth && post.owner === user.id ) {
...
}
作为替代,有一个整洁的小类库CASL可以非常简单的帮助你来管理用户权限。一旦你用CASL定义了你的权限管理,并且激活的一个用户,你就可以把上述的例子改变成这样:
if (abilities.can('update', 'Post')) {
...
}
在这篇文章中,我将演示如何在一个基于Vue.js和CASL的前端应用程序中管理权限。
基于Vue.js开发的应用程序如何管理用户权限?

CASL语言快速入门
它允许您定义一组规则,可以限制资源到给定的用户,即确定是否允许他们访问这些资源。
例如,使用规则可以是用户可以在一个给定的资源或实体(例如,一个帖子,评论,文章等)进行CRUD操作(创建、读取、更新和删除)。
假设我们有一个分类广告网站,有简单的“销售”职位。这个应用程序的一套显而易见的规则是:
guest用户可以查看任何帖子。
管理用户可以查看任何帖子,并可以更新或删除一个帖子。
在CASL,我们使用AbilityBuilder来定义规则。一个新规则是用一个可以调用的方法来创建的,例如:
const { 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 v-if="showPost"><div>
<div v-else>Please log in</div>
`,
props: [ 'post' ],
computed: {
showPost() {
return abilities.can('read', 'Post');
}
}
});
你可以找到更多关于CASL的官方文档(https://stalniy.github.io/casl/)。
演示项目
作为演示,我制作了一个简单的服务器/客户端应用程序,其中显示了分类广告帖子。此应用程序的权限规则是:用户可以阅读任何文章或创建新的帖子,但只能更新或删除由他们自己创建的职位的帖子。
我用vue.js与CASL使这些规则易于实现并可以支持一定规模的访问,以防未来增加其他实体或操作。
现在我将介绍这个应用程序的设置步骤。如果你想看到完整的代码,那么你可以访问一下这个GitHub的repo看看(https://github.com/anthonygore/vue-casl-demo)。
定义用户权限
让我们在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 });
}
);
};
让我们把代码细分一点:
我们可以看到第一个函数调用的方法中有二个参数,我们通过这个调用就可以定义权限规则。这种方法的第一个参数是你需要的CRUD操作,第二个是Post。
注意,在第二个函数调用中,我们传递了第三个参数是一个对象。这用于检测实体的用户属性是否匹配我们提供的用户对象。如果我们没有这样做,任何帖子就都可以被任何用户更新或删除,而不仅仅是帖子的所有者。
resources/ability.js
...
casl.AbilityBuilder.define(
...
can => {
can(['read', 'create'], 'Post');
can(['update', 'delete'], 'Post', { user: user });
}
);
当CASL检查确定一个实体的权限时,它需要知道实体的类型。通常的一种方法是通过函数的属性subjectname作为定义方法的一个参数来传递一个对象。这个函数将返回实体的类型。
我们将通过返回实体上的type属性来实现这一点。我们需要确保当我们在某一个时刻定义我们的Post对象时,这个属性是存在的。
resources/ability.js
...
casl.AbilityBuilder.define(
{ subjectName: item => item.type },
...
);
最后,我们在一个函数中封装我们的定义,这个函数允许我们在检测权限时传递用户对象。这样当我们在主应用程序中使用它时,就更好理解了,如下所示。
resources/ability.js
const casl = require('casl');
module.exports = function defineAbilitiesFor(user) {
...
};
在Vue中使用访问权限规则
我们现在希望能够检测一个对象在我们的前端应用程序看到的CRUD操作允许用户执行它。我们需要我们的Vue组件内提供的使用规则。以下是如何实现:
import Vue和abilities plugin。这个插件添加CASL的Vue的原型,让我们从内部组件可以调用它。
Import 我们的规则集到Vue(如resources/abilities.js)
定义一个当前用户。在一个真正的应用程序中,我们可以从服务器获取这个用户数据。对于我们的示例,我们只需硬编码一个即可。
记住,abilities 模块的exports一个函数,我们称之为defineAbilitiesFor。我们要将用户对象传递给这个函数。现在,每当检测对象时,我们都可以看到当前用户可用的权限。
添加abilities插件,允许我们在组件内进行测试如 likethis.$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实体必须有两个属性:
类型属性。CASL将使用定义在abilities.js的实体,通过subjectname回调来确定正在检测什么样的用户。
用户属性。这是Post的所有者。记住,用户只有拥有了更新和删除权限。在main.js我们已经告诉CASL他目前的用户是defineabilitiesfor(用户ID)。CASL现在所需要做的是检查用户的ID与用户属性。
let posts = [
{
type: 'Post',
user: 1,
content: '1 used cat, good condition'
},
{
type: 'Post',
user: 2,
content: 'Second-hand bathroom wallpaper'
}
];
鉴于这两个Post对象,我们当前的用户乔治拥有ID 1,它将在第一个帖子上有更新/删除权限,但第二个帖子没有这个权限。
检测用户权限
在我们的应用程序中通过一个名为“帖子”的组件显示帖子。先看一下代码,然后我们把它分解到下面:
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>...</style>
当用户点击删除按钮的时候,删除方法是捕获一个被称为delete的处理程序。
然后,我们使用CASL检查,用户是否有权限操作this.$can('delete', post)。如果他们有这个权限,我们可以采取相应一些操作。如果没有一个则提示一个错误的消息,“只有这个职位的发布者可以删除它!”。
服务器端检测
在实际应用中,用户在前端deletes后,我们将删除的功能使用Ajax发送一个指令到一个API(应用程序接口),例如:
src/components/Post.vue
if (this.$can('delete', post)) {
axios.get(`/delete/${post.id}`, ).then(res => {
...
});
}
我们会把CASL检测的逻辑放在服务器上,因为服务器来确认来自客户端的CRUD操作:
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是同构的,服务器上的对象可以从abilities.js中importability,我们不需要做任何重复的编码!
总结
这样,我们就有一个很好的方式,可以在一个Vue应用中很简单的管理用户权限了。
我相信this.$can('delete', post)比下面的代码好的多:
if (user.id === post.user && post.type === 'Post') {
...
}
这不仅是更难阅读,而且,这里有一个隐含的规则,即用户可以删除一个帖子。毫无疑问,这条规则将在我们应用程序的其他地方使用,并且应该真正被抽象出来。这正是CASL可以为我们做。
感谢Sergii Stotskyi,CASL的创造者,支持这篇文章。
分享一个 Vue.js 2 的入门级全家桶系列教程:
1.vue.js 2 入门与提高: xc.hubwiz.com/course/vue.…
2.vuex 2 入门与提高: xc.hubwiz.com/course/vuex
3.vue-router 2 入门与提高: xc.hubwiz.com/course/vuer…
4.vue.js 2 工程化实践: xc.hubwiz.com/course/vueg…