Node.js基于资源的ACL

问题描述 投票:2回答:1

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

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

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

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

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

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

node.js express design-patterns acl
1个回答
0
投票

这是一个非常广泛的问题,所以我会尝试给你一些提示作为我的答案,但是

javascript中是否存在ACL模式?

有很多解决方案,但我不会称之为任何一种模式。我现在会非常主观,但passport.js和类似模块的方式至少可以说是不透明的 - 而且它并不是真正的ACL ......

有人可能会说 - 嘿,这是node.js,必须有模块才能做到这一点并使你的node_modules更重但是在npm中搜索一个好的acl模块,我只发现了一些过时的并且与express紧密绑定。由于你的问题不是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保存在一个地方并且除非必要,否则不要按路径定义它更安全。这样您就不会省略一条路径,也不会在请求中间的某处留下安全漏洞。为此,我们需要将ACL拆分为多个部分:

  • URL访问检查(如果所有用户的路径是公共的/开放的)
  • 用户和会话有效性检查(用户已登录,会话未过期)
  • 管理员/用户检查(所以权限级别)
  • 否则我们不允许任何事情。 app.all('*',(req,res,next)=> {if(path.isPublic)next(); //如果(user.valid && user.expires> Date.now( ))next(); // session和user必须是有效的if if(user.admin)next(); // admin可以去任何地方if(path.isOpen && user.valid)next(); // path for登录用户也可以通过else抛出新的错误(“Forbidden”);});

这项检查不是很严格,但我们不需要重复。还要注意底部的throw错误 - 我们将在错误处理程序中处理:

app.use(function (err, req, res, next) {
    if (err.message === "Forbidden") res.status(403).send("Forbidden");
    else res.status(500).send("Something broke");
})

具有4个参数的任何处理程序将被Express.js视为错误处理程序。

在特定的路径级别,如果需要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代码状态之一作为响应是一个好习惯 - 这对客户端来说是有意义的。本质上,当用户未登录(或会话已过期)时回复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);
})

Phew ......我不认为这个列表是详尽无遗的,但在我看来,这是一个明智的开端。

© www.soinside.com 2019 - 2024. All rights reserved.