护照JS管理员用户验证

3
我有一个使用Node、Express和EJS作为前端的程序。在创建、编辑、删除的所有路由之前,我使用了包含Passport的中间件函数。
function isLoggedIn(req, res, next) {
    if (req.isAuthenticated()) {
        if (req.user._id != "12345") {
            res.redirect("/error");
        }
        return next();
    }
    res.redirect("/error");
}

我能想到的最好的验证我的管理员用户正在访问路由的方法是通过在MongoDB中使用req.user._id检查用户ID。

是否有更好的方式来处理管理员用户访问路由和HTML组件?

1个回答

5

我认为这份内容基本正确。需要注意的是,有两个层面:身份验证(authentication)和授权(authorization)。

身份验证实际上是一个布尔值:用户是否经过身份验证?你在这里有你的函数 req.isAuthenticated()。它可能逻辑上返回一个布尔值,true或false,表示用户是否已经经过身份验证(即登录)。

授权可能采用多种形式,但实质上仍然是一个布尔值:该用户是否符合访问此资源的条件。

身份验证通常在中间件中很好处理,在“终点”之前运行,但授权并不像身份验证那样简单,因为任何终点都可以允许或拒绝操作,或者根据用户的特权以不同方式响应。

整个对话或许在角色和权限讨论中是非常深入的。

我认为答案取决于应用程序。您的应用程序有两个要求:一是用户必须通过身份验证,二是用户可能需要成为管理员。

答案大概会是:最简单的方法是什么?

我认为,您应该考虑SOLID原则并注意到您只有一个中间件,因此它应该具有一个职责:检查用户是否已通过身份验证。接下来,也许您应该有另一个名为 isAdmin 的中间件,它适用于每个需要此额外条件的终点。那就是这个中间件所做的所有事情——进行额外检查。您不应该在 isLoggedIn 中间件中添加这些额外内容,因为这会使该中间件的重用性和可组合性降低。

isAdmin 中间件可能是一个好主意,但也有可能只是将其作为需要管理员检查的每个终点内部的函数。哪种方式更好?首先,哪种方式更简单。哪种方式更少代码,但仍然简单易懂。

由于这涉及到角色和权限,是否存在更加强大的方法来跟踪哪些用户是管理员?如果你运行的代码类似于 if (req.user._id === 12345) {},那么就需要特殊知识来记住代码中的这个位置,因此它有点脆弱且“更容易”出错。也许向用户表添加一个 is_admin 列会是个好主意,它可以对每个用户设置为 null0,而你的用户可以设置为 1。然后你可以检查 if (req.user.is_admin) {}

这可能会带领我们到一个中间件函数的样式:

function isAdmin(req, res, next) {
    if (req.isAuthenticated() && (req.user.is_admin === 1)) {
        return next();
    }
    return res.redirect(403, "/error");
}

你还可以尝试像这样更改 is_admin 数据库列,将其改为类似于 role 的形式,除了管理员用户外,每个用户都可以使用 1,而管理员用户可以设置为 2。这样可以实现以下操作:
function hasAuthorization(req, res, next) {
    if (req.isAuthenticated() && (req.user.role >= 2)) {
        return next();
    }
    return res.redirect(403, "/error");
}

这样的逻辑可以让您拥有越来越高级别的角色:也许1是普通用户,2是经理,3是管理员,4是超级管理员。如果用户的角色小于4,则他们无权访问。

在我看来,这种增加权限的想法非常好,但它可能存在关键缺陷,当您重构路由或角色时可能会出问题。你需要记住到处都有“> 3”,并将其更改为“> 4”。 如果您遗忘任何一个地方,那就是安全漏洞了,所以我相信您能理解我的论点。

与其看到像<>这样的运算符,我宁愿看到对特定角色的检查,例如:

if ((req.user.role === 'ADMIN') || (req.user.role === 'MANAGER')) {}

我们必须始终围绕这个想法:什么是最简单的?创建一个isAdmin中间件,然后将所有管理员路由组合到该中间件下面是否更简单?还是将授权检查放在每个路由内部更简单?

请参考以下示例:

import isAdmin from '../auth/isAdmin.js'

app.get('/admin', (req, res) => {
    if (!isAdmin(req.user)) {
        return res.redirect(403, '/error')
    }

    return res.render('admin')
})

这可能需要更多的工作,但也有潜在的更精细化的控制,因此你会拥有更多的控制权。

app.get('/foobars', (req, res) => {
    if (isAdmin(req.user)) {
        return res.json(/* all foobar records from all accounts */)
    }

    if (isManager(req.user)) {
        return res.json(/* all foobar records from the user's account */)
    }

    return res.json({ error: 'Insufficient privileges for this operation' })
})

我的最终想法是,你应该有两个功能:一个检查用户是否已经认证,另一个检查用户是否有权限。然后你可以将它们堆叠在一起,无论是在中间件中还是在两个中间件中,或者在一个路由中。
我还认为你应该找到一种更强大的方法来检查用户是否是你自己。如果你把你的应用从一台电脑移动到另一台电脑,下次填充用户表时用户ID可能会更改,所以"id"不是锁定你的用户的强有力方式。

2
非常感谢您提供这么详细的解释。这让我很有感觉,实际上我已经在做类似的事情了,我创建了第二个中间件函数isAdmin。听到我想得没错,我真的很高兴。 - Ian
当您使用类似connect-redis的东西与express-session一起使用,并且您正在执行req.user.isAdmin=true时,这是安全的吗?客户端实际上可以修改吗?如果他们能够将此设置为true,那么他们不会立即成为管理员吗? - PirateApp
1
这是一个好问题,通常的答案是,是安全的。答案取决于如何填充req.user。我建议搜索类似“req.user是否安全”的内容。但一般来说,用户无法访问request对象。那是服务器创建的一个不可变对象(除非你因某种原因改变了它,哈哈)。req(请求)和res(响应)通常是每个HTTP服务器中存在的两个对象,无论语言或框架如何。 - agm1984

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接