node.js - 基于Node.js资源的ACL

标签 node.js express design-patterns acl

我正在Node中实现一个简单的访问控制系统,我想知道什么是我正在做的最好的方法。

我正在使用Node ACL,但我不清楚如何在每个资源的基础上进行阻止。

让我们来看下面的例子:USER ->* PROJECT ->* ENTRY。用户可以有多个项目,其中包含许多条目。用户可以是ADMINUSER

我创建了一个端点/entry/{ID},用户可以在其中访问条目详细信息。每个人都可以访问该端点,ADMIN可以看到所有条目,但是对于User我需要做类似的事情:

app.get('/entry/{id}', (req, res) => {
    if (user.admin) {
        // Return eveything
    }
    else {
       if (entry.project == user.project) {
           // return it
       }
       else {
           // Unathorized
       }
    }
})


是否有更好的方法/模式来实现对资源所有权的检查?

最佳答案

这是一个非常广泛的问题,所以我将尝试给您一些提示作为我的答案,但是
javascript中是否有ACL模式?
有很多解决方案,但我不会将其中任何一种称为模式。我现在会非常主观,但是至少可以说passport.js和类似模块的方式是不透明的-而且它实际上不是ACL ...
有人可能会说-嘿,这是node.js,必须有模块才能执行该操作,并使您的node_modules变重,但在 npm 中搜索一个好的acl模块,我只发现了一些过时的模块,并与express紧密绑定(bind)。由于您的问题不是which is the best npm module for acl,因此我放弃了在第3页上查找此类问题,这并不意味着还没有准备就绪,因此您可能需要更仔细地研究。
我认为您的实现可以被接受,如我提到的那样,需要进行一些小的更正或提示:
将您的请求逻辑与访问控制逻辑分开
在您的代码中,所有事情都发生在一个回调中-这绝对是非常有效的,但从长远来看也很难支持。您会发现,如果所有回调中都包含以上代码,它将最终以相同的代码结束。分离逻辑非常简单-只需在两个回调中实现相同的路径(它们将按照定义的顺序运行),因此:

app.all('/entry/{id}', (req, res, next) => {
    const {user, entry} = extractFromRequest(req);
    if (user.admin || entry.project === user.project) {
        next();
    } else {
        res.status(403).send("Forbidden");
    }
});

app.get('/entry/{id}', (req, res) => {
    // simply respond here
})
这样,第一个回调将检查用户是否具有访问权限,并且这不会影响响应的逻辑。 next()的用法特定于类似表达的框架,我假设您使用它们查看代码-调用它时,将执行下一个处理程序,否则将不运行其他处理程序。
参见Express.js app.all documentation for an acl example
使用广泛的服务范围
将基本ACL放在一个地方,除非有必要,否则不要按路径定义它,这样更加安全。这样,您将不会省略一条路径,也不会在请求中间的某个地方留下安全漏洞。为此,我们需要将ACL分成几部分:
  • URL访问检查(如果路径对所有用户都是公共(public)/开放)
  • 用户和 session 有效性检查(用户已登录, session 未过期)
  • 管理员/用户检查(因此权限级别)
  • 否则我们不允许任何操作。
  •     app.all('*', (req, res, next) => {
            if (path.isPublic) next(); // public paths can be unlogged
            else if (user.valid && user.expires > Date.now()) next(); // session and user must be valid
            else if (user.admin) next(); // admin can go anywhere
            else if (path.isOpen && user.valid) next(); // paths for logged in users may also pass
            else throw new Error("Forbidden");
        });
    
    此检查不是很严格,但是我们不需要重复自己。还要注意底部的throw Error-我们将在错误处理程序中进行处理:
    app.use(function (err, req, res, next) {
        if (err.message === "Forbidden") res.status(403).send("Forbidden");
        else res.status(500).send("Something broke");
    })
    
    Express.js会将具有4个参数的任何处理程序视为错误处理程序。
    在特定路径级别上,如果需要ACL,只需向处理程序抛出错误:
    app.all('/entry/{id}', (req, res, next) => {
        if (!user.admin && user.project !== entry.project) throw new Error("Forbidden");
        // then respond...
    });
    
    这让我想起了另一个提示...
    不要使用user.admin
    好的,好的,如果您愿意,可以使用它。我不。破解代码的第一个尝试是尝试对具有属性的任何对象设置admin。这是常见安全检查中的通用名称,因此就像将您的WiFI AP登录名保留为出厂默认值一样。
    我建议使用角色和权限。一个角色包含一组权限,一个用户具有一些角色(或者一个角色比较简单,但给您的选择较少)。角色也可以分配给项目。
    这很容易是整篇文章,所以这里是further reading on Role-based ACL
    使用标准HTTP响应
    上面提到的一些方法,但是简单地使用标准4xx HTTP代码状态之一作为响应是一个好习惯-这对客户端来说将是有意义的。本质上,当用户未登录(或 session 已过期)时,回复401;在没有足够权限的情况下回复403;在超出使用限制时回复429more codes and what to do when the request is a teapot in Wikipedia
    至于实现本身,我确实喜欢创建一个简单的AuthError类,并使用它来引发应用程序中的错误。
    class AuthError extends Error {
        constructor(status, message = "Access denied") {
            super(message);
            this.status = status;
        }
    }
    
    处理并在代码中抛出这样的错误真的很容易,就像这样:
    app.all('*', (req, res, next) => {
        // check if all good, but be more talkative otherwise
        if (!path.isOpen && !user.valid) throw new AuthError(401, "Unauthenticated");
        throw new AuthError(403);
    });
    
    function checkRoles(user, entry) {
        // do some checks or...
        throw new AuthError(403, "Insufficient Priviledges");
    }
    
    app.get('/entry/{id}', (req, res) => {
        checkRoles(user, entry); // throws AuthError
        // or respond...
    })
    
    在错误处理程序中,您发送从代码中捕获的状态/消息:
    app.use(function (err, req, res, next) {
        if (err instanceof AuthError) res.send(err.status).send(err.message);
        else res.status(500).send('Something broke!')
    })
    
    不要立即回复
    最后-这更多的是同时具有安全性和安全性。每次您收到一条错误消息时,为什么不睡几秒钟呢?这会在内存方面伤害您,但只会造成一点伤害,并且可能会严重伤害潜在的攻击者,因为他们等待结果的时间更长。而且,仅需在一个地方实现,就非常简单:
    app.use(function (err, req, res, next) {
        // some errors from the app can be handled here - you can respond immediately if
        // you think it's better.
        if (err instanceof AppError) return res.send(err.status).send(err.message);
        setTimeout(() => {
            if (err instanceof AuthError) res.send(err.status).send(err.message);
            else res.status(500).send('Something broke!')
        }, 3000);
    })
    
    ew ...我认为这份 list 并不详尽,但我认为这是明智的开端。

    关于node.js - 基于Node.js资源的ACL,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55375020/

    相关文章:

    c++ - 让多线程程序的两个 "deep"部分相互通信的模式是什么?

    c++ - 哪种设计模式便于维护数据文件的向后兼容性?

    javascript - 使用 redux 进行服务端渲染

    node.js - 如何开发 https Nodejs Web 应用程序以便不显示安全警告

    node.js - Sails.js:res.attachment + res.redirect(附加文件并重定向)

    javascript - Node.js-koa : generator don't go next

    c# - 我创建的这个单例有什么问题

    javascript - jade 的语法支持 switch 语句吗?

    node.js - 简单错误 : Express 3 -> 4. 移动 Controller 并请求索引 "Route.get() requires callback functions but got a [object Undefined]"

    javascript - Express.js 如何使用 response.render 在客户端呈现 html?