坚持“记住我”饼干的竞争条件

时间:2011-05-10 10:53:15

标签: authentication cookies login race-condition

根据Persistent Login Cookie Best Practice,你绝不允许多次使用“记住我”的标记:

  

持久性cookie适用于单次登录。确认身份验证后,用于登录的随机数将失效,并分配一个全新的cookie。标准会话管理处理会话生命周期的凭证,因此在下一个会话之前不会检查新分配的cookie(此时它也会在使用后失效)。

然后,您如何处理用户同时访问您网站上多个网址的竞争条件?我现在实际上遇到了这个问题。

假设有两个请求同时从浏览器发送到服务器。请求不包含会话cookie,但包含相同的“记住我”cookie。其中一个请求将在另一个之前处理,并将通过经过身份验证的会话cookie和重新生成的“记住我”cookie获得响应。

第二个请求中的“记住我”令牌现已失效,并在服务器上生成另一个会话ID。此请求失败,因为无法对用户进行身份验证。

我想出了一些可能的解决方案,但它们似乎都不是很好。我错过了什么吗?

2 个答案:

答案 0 :(得分:2)

老问题,但我没有在任何地方找到答案。 我有同样的问题。我的解决方案是将旧令牌存储在数据库中,如果找不到主令牌,则将其用作后备。但是我确保旧令牌仅在短时间内有效,比如令牌更改后的几秒钟。然后我只在前一次更新后经过一段时间才更改令牌,否则会出现令牌连续多次更改的情况。

答案 1 :(得分:1)

要详细说明vangoz的答案,我想补充一点就是必须采用某种锁定机制。在这里考虑这个PHP-ish伪代码:

if (isFallback($token)) {
  // Log the user in
} else {
  // Usual processing
  // If token has to be updated, save old token as fallback for a few seconds
}

当存在并发请求时,它们可能都会在else分支中结束,一个请求将导致更新,另一个请求将导致令牌失效。我解决这个问题的方法是使用一个以$token命名的命名锁来包装else分支。此外,所有并发请求,但一个将无法获取锁定,在这种情况下,我们睡了一会儿并重试(重试时我们会发现令牌已成为后备令牌)。

if (isFallback($token)) {
  // Log the user in
} else {
  $couldLock = lock($token);
  if (!$couldLock) {
    usleep(10000);
    // Retry, possibly a recursive call
  } else {
    // Usual processing
    // If token has to be updated, save old token as fallback for a few seconds
    unlock($token);
  }
}

我希望这些考虑因素有用。