casl库控制用户权限_使用CASL在Vue中管理用户权限

casl库控制用户权限

There is one thing we can all agree on, no matter what language or platform we prefer for building applications — there has to be some form of control and access levels in our applications to ensure it runs smoothly. This is why the concept of user permission will quickly become commonplace for you once you build your first application.

无论我们希望使用哪种语言或平台来构建应用程序,我们都可以达成共识-我们的应用程序必须具有某种形式的控制和访问级别,以确保其平稳运行。 这就是为什么在您构建第一个应用程序后,用户权限的概念很快就会变得司空见惯的原因。

In server-side languages, user permissions can be done with little or no fuss. You can use sessions to hold a user’s information and there would be over a hundred libraries begging for the opportunity to help you manage what the user sees and when the user sees it. You can manage complex permission logic with the aid of a robust database.

在服务器端语言中,用户权限可以花很少的功夫就可以做到。 您可以使用会话来保存用户的信息,届时将有一百多家图书馆寻求帮助您管理用户看到的内容以及何时看到它的机会。 您可以借助强大的数据库来管理复杂的权限逻辑。

For JavaScript, this becomes a little tricky, given that all you may have to achieve this localStorage. In this tutorial, we will explore how we can manage user permission for our JavaScript application using CASL.

对于JavaScript,鉴于您可能需要实现此localStorage,所以这变得有些棘手。 在本教程中,我们将探索如何使用CASL管理JavaScript应用程序的用户权限。

什么是CASL ( What is CASL )

CASL is an authorization JavaScript library which lets us define what resources a given type of user can access. CASL forces us to think about permissions in terms of abilities — what a user can or cannot do vs roles — who is this user. In defining the abilities of a user, the user role can be composed.

CASL是一个授权JavaScript库,通过它我们可以定义给定类型的用户可以访问哪些资源。 CASL强迫我们根据权限(用户可以做什么或不可以做什么vs角色)来考虑权限,即该用户是谁。 在定义用户的能力时,可以组成用户角色。

入门 ( Getting Started )

We will use an authenticated Vue application we previously created so we can speed things up. In Vue Authentication And Route Handling Using Vue Router, we had created an application with different user types. For this tutorial, we will extend the application to add a page with blog posts that can only be edited by the creator.

我们将使用先前创建的经过身份验证的Vue应用程序,以便加快处理速度。 在使用Vue Router进行Vue身份验证和路由处理中 ,我们创建了具有不同用户类型的应用程序。 对于本教程,我们将扩展该应用程序以添加一个页面,其中包含只能由创建者编辑的博客文章。

Clone The Repository For The Project

克隆项目存储库

$git clone https://github.com/christiannwamba/vue-auth-handling

Install Dependencies

安装依赖项

$npm install

Install CASL

安装CASL

$npm install @casl/vue @casl/ability

We have all the basics we need setup now. Let’s proceed to make the components for our application. We are working off an existing project, so this will save us a lot of time. We need to add 2 new components to the project to enable us to create blog posts and view blog posts.

我们拥有了现在需要设置的所有基础知识。 让我们继续为我们的应用程序制作组件。 我们正在处理一个现有项目,因此这将节省大量时间。 我们需要向项目中添加2个新组件,以使我们能够创建博客文章和查看博客文章。

BlogManager ( The BlogManager )

First, create a file BlogManager.vue in the ./scr/components and add the following to it:

首先,创建一个文件BlogManager.vue./scr/components及以下添加到它:

<template>
    <div class="hello">
        <h1>Create New Blog</h1>
        <form @submit="create">
            <input class="form-input" type="text" placeholder="Blog Title..." v-model="blog_title">
            <textarea class="form-input" v-model="blog_body" placeholder="Type content here"></textarea>
            <button>Create</button>
            <br/>
        </form>
    </div>
</template>

This creates a simple HTML page with a form for our application. This is the form for creating a new blog post.

这将为我们的应用程序创建一个带有表单的简单HTML页面。 这是用于创建新博客帖子的表格。

We need to create the data attributes to bind the form fields:

我们需要创建数据属性来绑定表单字段:

[...]
<script>
  export default {
      data () {
          return {
              blog_title: null,
              blog_body: null
          }
      },
  }
</script>

Let’s create the method that handles form submission:

让我们创建处理表单提交的方法:

<script>
  export default {
      [...]
      methods : {
          create(e){
              e.preventDefault()
              let user = JSON.parse(localStorage.getItem('user'))
              this.$http.defaults.headers.common['x-access-token'] = localStorage.jwt
          }
      }
  }
</script>

We have created the method and parsed the user string we stored in localStorage. This user string will come in handy when we are sending our form data to the server. We also setup the default headers for our http request handler — axios. Some of our endpoints require an access token to work, which is why we need to set it.

我们已经创建了方法并解析了存储在localStorage中的用户字符串。 当我们将表单数据发送到服务器时,该用户字符串将派上用场。 我们还为http请求处理程序axios设置了默认标头。 我们的某些端点需要访问令牌才能工作,这就是为什么我们需要设置它的原因。

In the Vue Authentication … tutorial, we had explained how we made axios globally accessible by all our Vue components.

Vue Authentication…教程中,我们解释了如何使axios可以被所有Vue组件全局访问。

Now, let’s send the blog post data to our server:

现在,让我们将博客文章数据发送到我们的服务器:

<script>
  export default {
      [...]
      methods : {
          create(e){
              [...]
              this.$http.post('http://localhost:3000/blog', {
                  blog_title: this.blog_title,
                  blog_body: this.blog_body,
                  created_by : user.id
              })
              .then(response => {
                  alert(response.data.message)
                  this.blog_title = null
                  this.blog_body  = null
              })
              .catch(function (error) {
                  console.error(error.response);
              });
          }
      }
  }
</script>

After we get a successful response, we set the form fields to null so that the user can create a new blog right away, if they wanted.

成功获得响应后,我们将表单字段设置为null,以便用户可以根据需要立即创建一个新博客。

Let’s add some styles to make the page look pretty ?

让我们添加一些样式使页面看起来漂亮吗?

<style scoped>
h1, h2 {
    font-weight: normal;
}
button {
    border-radius: 2px;
    font-size: 14px;
    padding: 5px 20px;
    border: none;
    background: #43bbe6;
    color : #ffffff;
    font-weight: 600;
    cursor: pointer;
    transition: 0.2s all;
}
button:hover {
    background: #239be6;
    transition: 0.2s all;
}
.form-input {
    min-width: 50%;
    border: 1px #eee solid;
    padding: 10px 10px;
    margin-bottom: 10px;
}
textarea {
    resize: none;
    height: 6em;
}
</style>

博客 ( The Blog )

We need to make a simple component for displaying the blog posts we create. Create a file Blog.vue in the ./scr/components and add the following to it:

我们需要制作一个简单的组件来显示我们创建的博客文章。 创建文件Blog.vue./scr/components及以下添加到它:

<template>
    <div class="hello">
        <h1>Welcome to blog page</h1>
        <div class="blog" v-for="blog,index in blogs" @key="index">
            <h2>{{blog.title}}</h2>
            <p>{{blog.body}}</p>
        </div>
    </div>
</template>

In the code block above, we loop through the blogs we retrieved from the server and display them using Vue’s v-for loop construct.

在上面的代码块中,我们循环浏览从服务器检索到的blogs ,并使用Vue的v-for循环构造显示它们。

Let’s add the script to fetch the data:

让我们添加脚本来获取数据:

[...]
<script>
    export default {
        data () {
            return {
                blogs: []
            }
        },
        mounted(){
            this.$http.get('http://localhost:3000/blog')
            .then(response => {
                this.blogs = response.data.blogs
            })
            .catch(function (error) {
                console.error(error.response)
            });
        }
    }
</script>

It’s important we defined the blogs attribute as an empty array. This is to prevent the page from throwing errors when it loads.

重要的是,我们将blogs属性定义为一个空数组。 这是为了防止页面在加载时引发错误。

Also, we used mounted() as against beforeMount() so that our users can see the blog page even before the content is loaded. If for any reason a network error causes a delay in the content being loaded, our users would not be starring at a blank page loading forever.

另外,我们使用了mounted()beforeMount()这样我们的用户即使在加载内容之前也可以看到博客页面。 如果由于任何原因网络错误导致加载内容出现延迟,我们的用户将不会永远盯着空白页面加载。

Now, let’s add some styles to spice things up ?

现在,让我们添加一些样式来使事情变得有趣吗?

<style scoped>
h1, h2 {
    font-weight: normal;
}
.blog {
    width: 60%;
    border: 1px #eee solid;
    padding: 20px;
    padding-top: 0px;
    display: table;
    margin: 0 auto;
    margin-bottom: 20px;
    text-align: left;
}
.blog h2 {
    text-decoration: underline;
}
.delete {
    border-radius: 2px;
    background: #aaa;
    height: 24px;
    min-width: 50px;
    padding: 4px 7px;
    color: #ffffff;
    font-size: 14px;
    font-weight: 900;
    border: none;
    cursor: pointer;
    transition: 0.2s all;
}
.delete:hover {
    background: #ff0000;
    transition: 0.2s all;
}
</style>

更新服务器脚本 ( Updating Server Scripts )

We have made some significant changes to the frontend of our application. We need to make corresponding changes to the server to support it.

我们对应用程序的前端进行了一些重大更改。 我们需要对服务器进行相应的更改以支持它。

Db经理 (Db Manager)

From the ./server directory, open the db.js file and add the following:

./server目录中,打开db.js文件并添加以下内容:

[...]
class Db {
    constructor(file) {
        [...]
        this.createBlogTable()
    }
    [...]
}

You would notice we have a this.createTable() method in the class constructor that creates the user table. We also want to create the blog table if it does not exist whenever or wherever our Db class is called.

您会注意到,我们在创建用户表的类构造函数中有一个this.createTable()方法。 如果在任何时候或任何地方调用我们的Db类都不存在博客表,我们也想创建它。

Let’s add the createBlogTable method:

让我们添加createBlogTable方法:

[...]
class Db {
    [...]
    createBlogTable() {
        const sql = `
            CREATE TABLE IF NOT EXISTS blog (
            id integer PRIMARY KEY, 
            title text NOT NULL, 
            body text NOT NULL,
            created_by integer NOT NULL)
        `
        return this.db.run(sql);
    }
}

Let’s add the method for selecting all blogs:

让我们添加选择所有博客的方法:

[...]
class Db {
    [...]
    selectAllBlog(callback) {
        return this.db.all(`SELECT * FROM blog`, function(err,rows){
            callback(err,rows)
        })
    }
}

Let’s also add the method for adding a new blog:

我们还添加添加新博客的方法:

[...]
class Db {
    [...]
    insertBlog(blog, callback) {
        return this.db.run(
            'INSERT INTO blog (title,body,created_by) VALUES (?,?,?)',
            blog, (err) => {
                callback(err)
            }
        )
    }
}

Then the method for updating a blog:

然后是更新博客的方法:

[...]
class Db {
    [...]
    updateBlog(blog_id, data, callback) {
        return this.db.run(
            `UPDATE blog SET title = ?, body = ?) where id = ${blog_id}`,
            data, (err) => {
                    callback(err)
            }
        )
    }
}

Finally, the method for deleting a blog:

最后,删除博客的方法:

[...]
class Db {
    [...]
    deleteBlog(blog_id, callback) {
        return this.db.run(
            'DELETE FROM blog where id = ?', blog_id, (err) => {
                callback(err)
            }
        )
    }
}

Our database manager script is now up-to-date. Let’s update the app server script.

我们的数据库管理器脚本现在是最新的。 让我们更新应用服务器脚本。

App Server脚本 (The App Server Script)

The changes we will make here will expose endpoints for our server that our application can plug into. First, the endpoint for viewing all blogs.

我们将在此处进行的更改将公开应用程序可以插入的服务器的端点。 首先,查看所有博客的端点。

Open the app.js file and add the following:

打开app.js文件并添加以下内容:

[...]
router.get('/blog', function(req, res) {
    db.selectAllBlog((err, blogs) => {
        if (err) return res.status(500).send("There was a problem getting blogs")

        res.status(200).send({ blogs: blogs });
    }); 
});
[...]

As you can see, we use the selectAllBlog method we created in the database manager script and pass a callback method that should handle what happens when the data is retrieved.

如您所见,我们使用在数据库管理器脚本中创建的selectAllBlog方法,并传递一个回调方法,该方法应处理检索数据时发生的情况。

Next, let’s add the method for creating a new blog:

接下来,让我们添加创建新博客的方法:

[...]
router.post('/blog', function(req, res) {
    let token = req.headers['x-access-token'];
    if (!token) return res.status(401).send({ auth: false, message: 'No token provided.' });

    jwt.verify(token, config.secret, function(err) {
        if (err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' });

        db.insertBlog(
            [
                req.body.blog_title,
                req.body.blog_body,
                req.body.created_by,
            ],
            function (err) {
                if (err) return res.status(500).send("Blog could not be created.")

                res.status(201).send({ message : "Blog created successfully" });
            }
        ); 
    });
});
[...]

We first check if the access token is available. If it is missing, we deny the request instantly before anything else. If however, the access token is not empty, we try and verify it’s validity. If it is expired, we deny the request. If it does not exist, we also deny the request. If it is valid, we proceed to add the blog post information to the database and return a success message.

我们首先检查访问令牌是否可用。 如果丢失,我们将立即拒绝该请求。 但是,如果访问令牌不为空,我们将尝试验证其有效性。 如果已过期,我们将拒绝该请求。 如果不存在,我们也会拒绝该请求。 如果有效,我们将继续将博客文章信息添加到数据库中并返回成功消息。

We can do more here in the way of validating the data but that would make the code block too long and complex, defeating the aim of this tutorial.

我们在这里可以通过验证数据的方式做更多的事情,但这会使代码块太长和太复杂,从而违背了本教程的目的。

Finally, let’s add the method for deleting a blog post:

最后,让我们添加删除博客文章的方法:

[...]
router.delete('/blog/:id', function(req, res) {
    let token = req.headers['x-access-token'];
    if (!token) return res.status(401).send({ auth: false, message: 'No token provided.' });

    jwt.verify(token, config.secret, function(err) {
        if (err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' });

        db.deleteBlog(
            req.params.id,
            function (err) {
                if (err) return res.status(500).send("There was a problem.")

                res.status(200).send({ message : "Blog deleted successfully" });
            }
        ); 
    });
});
[...]

And that concludes the updates we have to do on the server.

到此,我们必须在服务器上进行更新。

设置CASL ( Setting Up CASL )

We have everything ready to use CASL in our application. Let’s start by defining the ability users can have. In the ./src directory, create another directory config which would hold our ability configurations. Inside the config directory you just created, create the file ability.js and add the following:

我们已经准备就绪,可以在我们的应用程序中使用CASL。 让我们从定义用户可以拥有的能力开始。 在./src目录中,创建另一个目录config ,该目录将保存我们的能力配置。 在刚创建的config目录中,创建文件ability.js并添加以下内容:

import { AbilityBuilder } from '@casl/ability'

var user = JSON.parse(localStorage.getItem('user'))
function subjectName(item) {
    if (!item || typeof item === 'string' || !user) {
            return item
    }
    else if(item.created_by === user.id || user.is_admin === 1){
            return 'Blog'
    }
}

Before you say “What is user doing here again”, just be a little patient as I explain. Now, the function — subjectName will be used by CASL’s AbilityBuilder to determine who would have the ability to perform certain actions.

在您说“用户再次在这里做什么”之前,请耐心等待我解释。 现在,CASL的subjectName将使用函数AbilityBuilder来确定谁有能力执行某些操作。

The first thing we check is that whenever we want to check the user’s ability to carry out an action on anything, two things must be true:

我们检查的第一件事是,每当我们要检查用户对任何事物执行操作的能力时,必须满足以下两个条件:

  1. The thing to perform an action on is not empty

    执行动作的东西不为空
  2. The user exists

    用户存在

In the case of our blog, we confirm that the above conditions are true, then we move on to check if the blog was created by the user or if we have an admin user at hand. If that is true, we return “Blog”, which is the subjectName we will use to assign abilities.

对于我们的博客,我们确认上述条件成立,然后继续检查该博客是否由用户创建,或者我们是否有管理员用户。 如果是这样,我们将返回“博客”,这是我们将用来分配能力的subjectName。

Now, let’s assign some abilities ? :

现在,让我们分配一些功能? :

[...]
export default AbilityBuilder.define({ subjectName }, can => {
  can(['read'], 'all')
  if(user) can(['create'], 'all')
  can(['delete'], 'Blog')
})

The can(['read'], 'all') method basically means the following;

can(['read'], 'all')方法的基本含义是:

The user who meets this requirement can read all things that exists inside this application. Be it blogs, comments, whitepapers — anything at all.

满足此要求的用户可以阅读此应用程序中存在的所有内容。 无论是博客,评论,白皮书还是其他任何东西。

With is explanation in mind, you will observe that can(['delete'], 'Blog') means this user can delete this blog.

考虑到是的解释,您将观察到can(['delete'], 'Blog')意味着该用户可以删除此博客。

If it all doesn’t make sense to you, wait till we make use of it.

如果这一切对您都没有意义,请等我们使用它。

将CASL插件添加到Vue设置 (Add CASL Plugin To Vue Setup)

To make our ability configurations globally accessible, we need to add it to our Vue setup. Open the ./src/main.js file and edit as follows:

为了使我们的能力配置可全局访问,我们需要将其添加到Vue设置中。 打开./src/main.js文件,然后进行如下编辑:

[...]
import ability from './config/ability'
import { abilitiesPlugin } from '@casl/vue'

Vue.prototype.$http = Axios;

Vue.config.productionTip = false
Vue.use(abilitiesPlugin, ability)
[...]

We have imported the CASL’s abilitiesPlugin and the ability configurations we made above. By telling Vue to use it, we have effectively made the ability configurations globally accessible using the $can method the abilitiesPlugin will expose.

我们已经导入了CASL的abilitiesPlugin和我们在上面所做的能力配置。 通过告诉Vue使用它,我们已经使用abilitiesPlugin将公开的$can方法有效地使了能力配置可全局访问。

在我们的博客组件中使用这些功能 ( Use The Abilities In Our Blog Component )

Now that we have defined abilities, let’s try them out with our blog component. Open the Blog.vue file and add the following:

现在我们已经定义了功能,让我们通过博客组件尝试一下。 打开Blog.vue文件并添加以下内容:

<template>
    <div class="hello">
        [...]
        <div class="blog" v-for="blog,index in blogs" @key="index">
            [...]
            <button class="delete" v-if="$can('delete',blog)" @click="destroy(blog)">Delete</button>
        </div>
    </div>
</template>
[...]

We have added a delete button to each blog and will only display it if the user has the permission to delete a particular blog post. Recall that we will only allow a blog creator or an admin user to delete a blog post, and no other type of user.

我们为每个博客添加了一个删除按钮,并且仅在用户有权删除特定博客帖子时才显示。 回想一下,我们仅允许博客创建者或管理员用户删除博客文章,而不允许其他类型的用户。

Now, let’s add the method to handle delete when a user clicks it:

现在,让我们添加一种在用户单击时处理删除的方法:

[...]
<script>
  export default {
      [...]
      methods : {
          destroy(blog){
              this.$http.defaults.headers.common['x-access-token'] = localStorage.jwt
              this.$http.delete(`http://localhost:3000/blog/${blog.id}`)
              .then(response => {
                  console.log(response)
              })
              .catch(function (error) {
                  console.error(error.response);
              });
          }
      }
  }
</script>
[...]

Now, when our users are browsing our blog page, their view will be something like this

现在,当我们的用户浏览我们的博客页面时,他们的视图将如下所示

A user who is not authenticated

An authenticated user who has some blog posts

Admin sees all as he has the power to do and undo

This is the last thing we need to do for our application to be ready. Open the ./src/router/index.js file and add the following to it:

这是我们准备好应用程序所要做的最后一件事。 打开./src/router/index.js文件,然后添加以下内容:

[...]
let router = new Router({
  mode: 'history',
  routes: [
    [...]
    {
        path: '/blog',
        name: 'blog',
        component: Blog
    },
    {
        path: '/blog/create',
        name: 'blogmanager',
        component: BlogManager,
        meta: { 
            requiresAuth: true
        }
    },
  ]
})
[...]

And that’s it.

就是这样。

运行应用程序 ( Run The Application )

To run the application, you need to open the directory where it is located on your terminal. Then run the following command:

要运行该应用程序,您需要打开终端上的目录。 然后运行以下命令:

$npm run dev

Also, open the directory on another instance of your terminal so we can start the server as well:

另外,在终端的另一个实例上打开目录,以便我们也可以启动服务器:

$npm run server

结论 ( Conclusion )

In this tutorial, we have seen how to manage user permissions in our Vue application using CASL. How CASL abilities work was explained in details, to enable us to compose the unique permissions our applications will likely have.

在本教程中,我们了解了如何使用CASL在Vue应用程序中管理用户权限。 详细说明了CASL功能如何工作,以使我们能够组合应用程序可能具有的唯一权限。

So, try and create something awesome with it and share with me.

因此,尝试创建一些很棒的东西并与我分享。

翻译自: https://scotch.io/tutorials/managing-user-permissions-in-vue-using-casl

casl库控制用户权限

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值