如何使用 JWT 在 Express 中验证单个路由(无代码重复)的多种类型用户的身份验证令牌?

Posted

技术标签:

【中文标题】如何使用 JWT 在 Express 中验证单个路由(无代码重复)的多种类型用户的身份验证令牌?【英文标题】:How to verify authentication token of multiple type of users for a single route (without code duplication) in Express with JWT? 【发布时间】:2021-08-16 18:09:43 【问题描述】:

我有一个快速应用程序,其中一些路线是公开的(互联网上的每个人都可以访问),而一些路线受到保护。我使用 JWT 进行路由身份验证。我在系统中有不同类型的用户,例如:作者、订阅者等。有些路由是访问作者,有些是订阅者。但是有一些路线可供作者和订阅者访问。

为了确保当用户请求受保护的路由时,我使用中间件功能对每个类别的用户来验证他们的身份验证令牌。

仅作者可以访问的示例路线,如下所示,

router.get("/", verifyToken.author(), async (req, res) => 
    try 
        // doing stuff
     catch (err) 
        // doing stuff
    
);

订阅者可以访问的示例路线如下所示,

router.get("/", verifyToken.subscriber(), async (req, res) => 
    try 
        // doing stuff
     catch (err) 
        // doing stuff
    
);

PS:verifyToken.author() 或我为验证令牌而编写的任何其他中间件函数实际上都是闭包。因为有时我需要在函数中传递参数。所以,这些方法的一个例子是,

module.exposts.author = () => 
    return (req, res, next) => 
        const token = req.header("Authorization");
        if (!token) return res.status(401).send( message: "Access Denied!" );

        try 
            jwt.verify(token, process.env.AUTHOR_SECRET);

            next();
         catch (err) 
            res.status(401).send( message: "Access Denied!" );
            console.log(err);
        
    ;
;

当我需要编写一个函数来验证作者和订阅者的令牌时,我可以检查这样做并将该方法用作所需路由的中间件。但恐怕它并不像听起来那么简单,因为实际上我并没有这两种类型的用户。而且几乎所有的用户类型都具有复杂的路由访问权限,其中可能有 5 个特定类型的用户可以访问该路由。

正如我所说,假设作者和订阅者属于我上面所说的 5 种用户类型。所以为作者+订阅者编写一个函数,为作者、订阅者存在的5种用户编写另一个函数,看起来代码重复太多了。

我希望我能够解释为什么我只是不想在下面写这样的东西,

module.exports.authorSubscriber = () => 
    return (req, res, next) => 
        const token = req.header("Authorization");
        if (!token) return res.status(401).send( message: "Access Denied!" );

        try 
            jwt.verify(token, process.env.AUTHOR_SECRET);

            next();
         catch (err) 
            try 
                jwt.verify(token, process.env.SUBSCRIBER_SECRET);
    
                next();
             catch (err) 
                res.status(401).send( message: "Access Denied!" );
                console.log(err);
            
        
    ;
;

相反,我想要这样的东西,在那里我可以重复使用我之前为作者编写的函数,

module.exports.authorSubscriber = () => 
    return (req, res, next) => 
        const token = req.header("Authorization");
        if (!token) return res.status(401).send( message: "Access Denied!" );

        try 
            jwt.verify(token, process.env.AUTHOR_SECRET);

            next();
         catch (err) 
            exports.author()
        
    ;
;

但是,author() 在这里不起作用。它在从路由调用时起作用,因为它会得到req, res, next。但在这种情况下,它是从另一个函数调用的,并且没有传递 req、res、next。如果我在authorSubscriber 中执行exports.author(req, res, next) 并定义作者以接受这些参数。它仍然不起作用。我认为路由中间件自动获取'req,res,next`,这些东西不能手动传递,可以吗?这就是我关闭这些函数的原因,因为在类似的验证器上我需要传递某个令牌并且中间件不需要额外的参数。

我很困惑。您可能已经清楚地意识到我存在知识空白,这就是为什么我在这里遗漏了一些东西。 请帮忙。

【问题讨论】:

您必须在您的节点应用程序中实现基于角色的访问控制 (RBAC) 和基于属性的访问控制 (ABAC),或者如果您已经实现了基于角色权限的身份验证和授权,那么您必须检查用户在中间件中的角色和权限 中间件函数不是“特殊”的,因为它们获取参数的方式与任何其他函数相同。可能让您感到困惑的一件事是,当您指定要在路由中表达的中间件函数时,您给它一个对稍后将使用 (req, res, next) 参数调用的函数的引用。即,您不会在将中间件函数分配给路由时调用它。您当然可以自己手动调用中间件,但如果您使用“闭包”模式,它可能看起来像 exports.author()(req, res, next)(注意“额外”括号)。 @Shailendra,我想我对这些术语不熟悉,我将通过在谷歌上搜索来阅读 RBAC、ABAC。但是,如果您知道任何好的资源,如果您推荐我,我会很高兴。 @MykWillis,我不知道路由中的中间件实际上是分配。是的,应用您建议的更改后,代码按预期工作。除此之外,您能否向我推荐有关 Express 中的中间件的任何好读物? 这似乎是一个不错的起点:expressjs.com/en/guide/writing-middleware.html 【参考方案1】:

假设您在闭包中定义“作者”中间件,如上面的示例所示:

module.exposts.author = () => 
    return (req, res, next) => 
        // implementation here
    ;
;

那么您确实可以在您描述的“作者+订阅者”场景中重新使用它。但请记住,上面定义的中间件不是名为author 的函数;相反,中间件是从名为author 的函数返回的匿名函数。所以如果你想在authorSubscriber 中重复使用它,它看起来像:

module.exports.authorSubscriber = () => 
    return (req, res, next) => 
        // ...
        try 
            jwt.verify(token, process.env.AUTHOR_SECRET);

            next();
         catch (err) 
            exports.author()(req, res, next)
        
    ;
;

请注意,您首先调用exports.author(),这将返回对您实际要调用的中间件的内部函数的引用。

正如其他 cmets 所指出的,这种类型的访问控制可能会有更好的模式供您在未来使用,但如果您的目标只是重用您已经在不同上下文中定义的中间件,我认为这似乎可行。

【讨论】:

【参考方案2】:

我认为您的解决方案中没有使用 JWT 的一项出色功能。您可以根据用于签署令牌的密钥来区分用户的角色。相反,您可以使用一个秘密签署所有令牌(理想情况下使用非对称签名),并在令牌中包含一个声明,该声明告诉您用户(或类型)的类型。然后,当您收到令牌时,您使用相同的密钥对其进行解码,然后只需检查令牌内的声明以检查用户的类型。无需尝试捕获。

【讨论】:

以上是关于如何使用 JWT 在 Express 中验证单个路由(无代码重复)的多种类型用户的身份验证令牌?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用基于 JWT 令牌的 express 获取经过身份验证的用户信息

如何使用 jwt 保护 express 中的静态文件夹

如何使用 express-Jwt 解码来自 idToken 的信息?

如何从标头中正确获取 jwt 令牌并将其与 express 一起使用

如何查看存储在 JWT 中的数据?使用 auth0 和 express-jwt

jsonwebtoken和express-jwt的使用