如何在服务器Node.js应用程序中管理身份验证令牌?

时间:2019-05-25 06:33:53

标签: javascript node.js token

我必须调用一些需要传递令牌的api,并且需要刷新此令牌,而主要问题是-如何以及在服务器中的何处存储令牌?互联网上的一些解决方案告诉我如何做那样(代码可能不起作用,但是想法很明显):

let myToken = { ... }
class MyService {
    myFunc() {
        makeHttpCall(myToken).catch(async err => {
            if (err.httpCode == 401) {
                let netToken = await refreshToken()
                myToken = netToken;
                return myFunc();
            }

        })
    }
 }
module.exports = MyService

他们告诉nodejs是单线程应用程序-因此调用之间的同步没有任何问题。我同意, 但是,如果令牌已过时并且随后有两个makeHttpCall函数调用怎么办?是的,他们将一一失败 401,并说这些呼叫有延迟,因此顺序可能如下:

    First invocation                    
           |                                 
          \|/                                
      makeHttpCall                      Second invocation
           |                                     | 
          \|/                                   \|/ 
          401                               makeHttpCall
           |                                     |    
          \|/                                   \|/  
        refresh (unexpected delay here)         401  
           |                                     |   
           |                                    \|/  
           |                                  refresh  
          \|/                                    |
     writeNewToken                               |
           |                                     |
          \|/                                    |
       makeHttpCall                              |
           |                                     |       
          \|/                                   \|/ 
          401                             writeNewToken
(because token refreshed twice)                    

因此,想法是-第二个makeHttpCall在第二次调用收到新令牌后立即发出请求。

如何同步呢?如何刷新令牌并确保其未被覆盖?

请注意,该问题与任何用户会话或oauth令牌无关。使用会话或对oauth签名的解决方案不是答案

1 个答案:

答案 0 :(得分:0)

我也遇到过类似的情况,而找到的最理想的解决方法是-

  1. 将所有内容包装到一个主函数中,该主函数负责刷新令牌并调度请求。

  2. 请求仅应执行获取数据的任务,并且如果被拒绝,则请求将优雅地失败,让主函数知道令牌不再有效,而不会尝试刷新令牌本身。

  3. 存储刷新令牌,说明当前是否正在刷新以及已调度的请求。在原型/小型系统中,您可以将其存储在内存中,但我强烈建议您使用持久性数据存储-sqlite,postgresql,mysql或redis启用持久性。

该请求永远不要尝试刷新令牌本身,因为在高吞吐量环境中,您将很快陷入每次请求分派都会刷新令牌的状态。而且,如果新令牌立即使之前的令牌无效,您会看到请求成功或被随机拒绝

以编程方式(伪代码)-

let refreshToken = 'asfasfjhskajfaskf',
       isTokenBeingRefreshed = 0, //1 if it is indeed being refreshed
       pendingRequests = {      
          "<request id>":"data"
       }

function dispatchRequest(params) {       
  if(isTokenBeingRefreshed) {
     //push requests into pendingRequests and wait for token to be refreshed

  if(!isTokenBeingRefreshed) {
    //dispatch request
  }

}

一旦收到发送请求,请检查令牌是否正在刷新。如果是,请保留该请求。如果不是,则调度请求。如果由于身份验证错误而导致请求失败,则将所有其他请求搁置(如果已经分派,则它们将失败,但由于一个请求已经在进行中,因此不请求另一个令牌,只需静默确认刷新令牌的请求即可),等待令牌刷新。请区分请求是否被拒绝,由于操作错误,由于网络错误等导致失败。

您可能必须使用setInterval之类的方法来分派保留的请求。

如果您将其作为项目的一部分进行编写(这将是长期的并且可能具有较高的吞吐量),那么您应该使用消息代理或队列进行评估。要考虑的选项是Kafka,ZeroMQ,RabbitMQ。即使是使用Redis的基本本地滚动队列也可以完成相当大的负载。检查节点是否为bull

注意-在未决请求中,最好存储所有未决,已完成和失败的请求及其数据。最好为每个请求分配一个唯一的ID,这样,如果由于身份验证错误或网络错误而导致请求失败,您可以重试。