【问题标题】:How should I implement a token based authorization API in Node.js and Redis?我应该如何在 Node.js 和 Redis 中实现基于令牌的授权 API?
【发布时间】:2014-11-13 10:10:30
【问题描述】:

我正在开发一个处理来自 Mongo 数据库的资源的网络应用程序,对于这些资源,我想提供一个 API,以便未来的移动应用程序可以从原始客户端获取或使用它。

但是,我希望 Web 应用使用相同的 API,在这里我对如何正确实现这一点感到有些困惑。

这是我到目前为止所做的:

API 认证:

app.route('/api/auth/')
    .post(function (request,response) {

        var email = request.body.email;
        var password = request.body.password;
        var login = new Account({"local.email":email,"local.password":password});

        Account.findOne({"local.email":email}, function (err,user) {
            if (err) {
                response.send(500);
            }

            if (!user) {
                response.send(404);
            }

            else {

                user.validPassword(password, function (err,matched) {
                    if (err) {
                        response.send(500);
                    }

                    if (matched) {
                        var uuidToken = uuid.v4();
                        redisClient.set(uuidToken,user._id,redis.print);
                        redisClient.expire(user._id,100);
                        response.send(uuid);
                    }

                    else {
                        response.send(403);
                    }
                });                    
            }
        });         
    });

所以基本上我会收到消费者的用户名和密码,然后根据数据库对其进行身份验证,如果匹配,我会回复一个令牌(实际上是一个UUID)。该令牌与数据库中的用户 ID 配对存储在 Redis 中。未来对任何 API 路由的每个请求都将验证此类令牌是否存在。

我想知道:

  • 我应该如何管理令牌 TTL,以及在未来请求时续订?
  • 如何控制每个时间窗口的请求限制?
  • 我采取的方法是否有任何安全警告?

网站验证:

基本上我对数据库执行相同的用户名-密码身份验证,然后:

1.启动一个新的服务器会话。

2。当然,提供一个带有会话 ID 的 cookie。

3.然后我创建了 Redis UUID 和用户 ID 记录,API 将检查该记录。我想这没关系,因为请求POST /api/auth 再次进行身份验证是有任何意义的

我想知道:

  • 这是最好的方法吗?
  • 我是否应该包含任何令牌盐来区分纯 API 消费请求和来自 Web 应用程序的请求?
  • 我采取的方法是否有任何安全警告?
  • 我应该包含更多令牌吗?

这是POST /login的例子:

app.route('/login')

    .post(function (request,response,next) {

        var email = request.body.email;
        var password = request.body.password;

        var login = new Account({"local.email":email,"local.password":password});

        Account.findOne({"local.email":email}, function (err,user) {

            if (err) {
                                response.redirect('/error');
            }

            if (!user) {

                                var cookie = request.cookies.userAttempts;

                                if (cookie === undefined) {

                                    response.cookie('userAttempts',1);
                                }

                                else {

                                    response.cookie('userAttempts',(++cookie));
                                }

                                response.redirect('/');
            }

            else {

                user.validPassword(password, function (err,matched) {

                    if (err) {

                                    // Redirect error site or show err message.
                                    response.redirect('/error');
                    }

                    if (matched) {

                                    var session = request.session;
                                    session.userid = user._id;
                                    var uuidToken = uuid.v4();
                                    redisClient.set(uuidToken,user._id,redis.print);
                                    redisClient.expire(uuidToken,900);                                        
                                    response.cookie('email',email);
                                    response.redirect('/start');
                    }

                    else {

                            var cookie = request.cookies.passwordAttemps;

                            if (cookie === undefined)

                                response.cookie('passwordAttemps',1);

                            else {
                                var attemps = ++request.cookies.attemps
                                response.cookie('passwordAttemps', attemps)
                            }

                            response.redirect('/');                                    
                    }

                });                    
            }


        });
    })

我认为我可以摆脱使用和编写典型的会话实现,并以某种方式依赖 API 所具有的类似的基于令牌的身份验证。

【问题讨论】:

    标签: node.js api authentication


    【解决方案1】:

    您所拥有的一切都在正确的轨道上,并且基本上取代了 cookie 的一些功能。不过,有一些事情需要考虑,并且您已经触及了其中的一些。

    1. 虽然使用 UUID(我猜是 v4?)的好处在于它具有不确定性和“随机性”,但令牌本身毫无价值。如果 redis 丢失数据,则令牌不再具有任何上下文。如果没有 redis 的帮助,你也不能强制过期。将此与 JWT 进行比较,后者可以自己携带上下文,任何人都可以使用正确的密钥解密,可以处理过期,并且可以强制执行进一步的常见应用程序级别约束(发行者、受众等)。

    2. 速率限制。 There are a number of ways to handle this,除了您可能会使用令牌作为在速率限制器中的请求中识别用户的关键之外,其中很少有直接与您选择的令牌方案相关联。

    3. 在 Web 应用程序和其他客户端(移动应用程序、桌面应用程序等)中透明地传递令牌可能是一个巨大的痛苦。为了访问私有资源,用户需要在请求中的某个地方传递令牌,可能是标头,对于 Web 应用程序,这意味着您需要手动干预以在每个请求中包含令牌。这意味着对所有经过身份验证的请求进行手动编码的 ajax 请求。虽然这可能很烦人,但至少可以这样做,而且如果您正在编写单页应用程序,那么您很可能还是会这样做。任何移动或桌面客户端都可以这样说。既然您已经必须直接在代码中发出 HTTP 请求,那么这有什么关系呢?现在想象一个返回 html 页面的 HTTP GET 端点只能通过适当的身份验证访问的场景。对于网络应用程序,用户很可能会通过浏览器重定向或直接在 URL 栏中输入来访问它。 How is the token added to the request? 除了使用 cookie(由于移动和桌面客户端未实现它们而明确不使用它们)之外,这实际上是不可能的。但是,如果您的 API 客户端始终可以修改 HTTP 请求结构,这并不是真正的问题。

    现在是一个无耻的插件,our team has a library we use for this。它主要在内部使用,因此对其依赖项(express,redis)非常固执己见,但希望它可以在这里对您有所帮助。事实上,这个库几乎只是一个 JWT 包装器,围绕着你已有的东西。如果您决定使用它并发现任何问题或缺陷,请随时在 github 上提交任何问题。否则,npm 上还有一大堆其他基于 JWT 的会话管理模块看起来很有希望。我会检查这些,因为那里很可能有比我们更好的模块。同样,我们在内部使用并且来自一组非常特定的用例,因此它捕获您所有的机会非常渺茫。另一方面,听起来您正在使用类似的堆栈,所以也许鞋子合适。

    如果您确实使用我们的,那么在该模块的 API 表面中存在一个拆分可能看起来很奇怪,因为您可以选择将数据直接存储在 JWT 声明中或 redis 中。这是经过深思熟虑的,我认为您的示例说明了双方的良好用例。通常我们所做的是将用户的电子邮件和姓名存储在 JWT 声明中,然后将更多动态会话数据存储在他们会话的 redis 中。例如,在登录时,您需要将颁发者、受众和用户的电子邮件添加到 JWT 声明中,但保留与“userAttempts”相关的任何内容。然后,在尝试失败时,您将在与该 JWT 相关的 redis 中存储的会话数据上添加或修改“userAttempts”。一旦设置了 JWT,就无法在不生成新内容的情况下修改其内容,因此请注意,如果您决定在 JWT 中保留相对动态的数据,您将在服务器和客户端之间不断交换新旧 JWT .

    【讨论】:

    • 非常感谢您提出的扩展问题。我想我会考虑使用 JWT。到目前为止,网站身份验证所需的页面是在会话存在验证时提供的(我知道这是多余的方式,但我避免这种方式在路由中包含任何令牌)。从网站是我向 API 提出请求的地方。我认为 JWT 基本上可能会更好地完成一项以上的工作。
    • 也可以同时使用基于 cookie 的方法和基于 JWT 的方法。您会注意到,我们使用的 JWT 模块的初始化参数允许与 express-session(使用 cookie)一起使用。这个想法是您可以在 jwt-redis-session 之前将 express-session 推送到中间件堆栈上,然后将它们一起使用。
    猜你喜欢
    • 2021-01-29
    • 1970-01-01
    • 1970-01-01
    • 2014-11-08
    • 2016-03-23
    • 1970-01-01
    • 2016-11-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多