网络编程 发布日期:2024/11/16 浏览次数:1
在需要身份验证的前端应用里,我们经常想通过用户角色来决定哪些内容可见。比如,游客身份可以阅读文章,但注册用户或管理员才能看到编辑按钮。
在前端中管理权限可能会有点麻烦。你之前可能写过这样的代码:
if (user.type === ADMIN || user.auth && post.owner === user.id ) { ... }
作为代替方案,一个简洁轻量的库——CASL——可以让管理用户权限变得非常简单。只要你用CASL定义了权限,并设置了当前用户,就可以把上面的代码改为这样:
if (abilities.can('update', 'Post')) { ... }
在这篇文章里,我会展示如何在前端应用里使用Vue.js和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'); } } });
作为演示,我做了一个用来展示分类广告帖子的服务器/客户端应用。这个应用的规则是:用户能够阅读帖子或发帖,但是只能更新或删除自己的帖子。
我用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) { ... };
现在我们想在前端应用中检查一个对象中,用户具有哪些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实例中有两个属性是必须的:
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') { ... }