Pab*_*ect 3 acl design-patterns node.js express
我正在 Node 中实现一个简单的访问控制系统,我想知道什么是我正在做的事情的最佳方法。
我正在使用Node ACL,我不清楚如何在每个资源的基础上进行阻塞。
让我们看下面的例子:
USER ->* PROJECT ->* ENTRY
. 用户可以有多个项目,其中包含许多条目。用户可以是ADMIN
或USER
。
我创建了一个端点/entry/{ID}
,用户可以在其中访问条目详细信息。每个人都ADMIN
可以访问端点,s 可以看到所有条目,但User
我需要做类似的事情:
app.get('/entry/{id}', (req, res) => {
if (user.admin) {
// Return eveything
}
else {
if (entry.project == user.project) {
// return it
}
else {
// Unathorized
}
}
})
Run Code Online (Sandbox Code Playgroud)
是否有更好的方法/模式来实现对资源所有权的检查?
这是一个非常广泛的问题,所以我会尽量给你一些提示作为我的答案,但是
javascript 中有 ACL 模式吗?
有许多解决方案,但我不会将其中任何一个称为模式。我现在会非常主观,但passport.js
至少可以说类似模块的方式是不透明的 - 而且它不是真正的 ACL ...
有人可能会说 - 嘿,它是 node.js,必须有模块来做到这一点并使你的 node_modules 更重,但在npm 中寻找一个好的 acl 模块,我只找到了一些过时的模块,并且与 express 紧密结合。由于您的问题不是which is the best npm module for acl
我放弃在第 3 页寻找这样的问题,这并不意味着没有准备好的东西,因此您可能需要更仔细地查看。
我认为你的实现可以被认为是可以接受的,正如我提到的,有一些小的更正或提示:
将请求逻辑与访问控制逻辑分开
在您的代码中,一切都发生在一个回调中——这绝对是非常有效的,但从长远来看也很难支持。你看,在所有回调中,它会在很多 if's above 中以相同的代码结束。分离逻辑非常简单 - 只需在两个回调中实现相同的路径(它们将按照定义的顺序运行),因此:
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
})
Run Code Online (Sandbox Code Playgroud)
这样第一个回调会检查用户是否具有访问权限,这不会影响响应的逻辑。的用法next()
特定于类似 express 的框架,我假设您使用它查看您的代码 - 当您调用它时,将执行下一个处理程序,否则不会运行其他处理程序。
有关acl 示例,请参阅Express.js app.all 文档。
使用服务范围的 acl
将基本 ACL 保存在一个地方并且除非必要,否则不要按路径定义它要安全得多。这样你就不会遗漏一条路径,也不会在请求中间的某个地方留下安全漏洞。为此,我们需要将 ACL 分成几部分:
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");
});
Run Code Online (Sandbox Code Playgroud)
这个检查不是很严格,但我们不需要重复自己。还要注意底部的 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");
})
Run Code Online (Sandbox Code Playgroud)
Express.js 将任何带有 4 个参数的处理程序视为错误处理程序。
在特定路径级别,如果需要 ACL,只需向处理程序抛出错误:
app.all('/entry/{id}', (req, res, next) => {
if (!user.admin && user.project !== entry.project) throw new Error("Forbidden");
// then respond...
});
Run Code Online (Sandbox Code Playgroud)
这让我想起了另一个提示......
不要使用 user.admin
好吧,如果你喜欢,就用它。我不。第一次尝试破解您的代码是尝试在任何具有属性的对象上设置 admin。这是通用安全检查中的通用名称,因此就像将您的 WiFI AP 登录名保留为出厂默认设置一样。
我建议使用角色和权限。一个角色包含一组权限,一个用户有一些角色(或者一个更简单但给你的选项更少的角色)。角色也可以分配给项目。
这很容易成为一篇关于此的整篇文章,因此这里有一些关于基于角色的 ACL 的进一步阅读。
使用标准 HTTP 响应
上面提到了其中的一些内容,但简单地使用标准 4xx HTTP 代码状态之一作为响应是一种很好的做法 - 这对客户端来说是有意义的。本质上401
是在用户未登录(或会话过期)、403
没有足够的权限、429
超过使用限制时进行回复。更多代码以及当请求是维基百科中的茶壶时该怎么做。
至于实现本身,我喜欢创建一个简单的 AuthError 类并使用它从应用程序中抛出错误。
class AuthError extends Error {
constructor(status, message = "Access denied") {
super(message);
this.status = status;
}
}
Run Code Online (Sandbox Code Playgroud)
在代码中处理和抛出这样的错误真的很容易,就像这样:
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...
})
Run Code Online (Sandbox Code Playgroud)
在您的错误处理程序中,您发送从代码中捕获的状态/消息:
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!')
})
Run Code Online (Sandbox Code Playgroud)
不要立即回复
最后 - 这更像是一项安全功能,同时也是一项安全功能。每次你回复一条错误信息时,为什么不睡几秒钟呢?这会在记忆方面伤害你,但它只会伤害一点点,它会伤害可能的攻击者很多,因为他们等待结果的时间更长。此外,在一个地方实现它非常简单:
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);
})
Run Code Online (Sandbox Code Playgroud)
呼...我不认为这个列表是详尽无遗的,但在我看来这是一个明智的开始。
归档时间: |
|
查看次数: |
1083 次 |
最近记录: |