JWT令牌和CSRF

13
我仍然不清楚JWT和CSRF如何一起工作。我理解JWT的基本原理(它是什么以及如何工作)。我也了解与会话一起使用时的CSRF。同样,我了解在localStorage中存储JWT存在风险,这就是为什么你需要CSRF令牌的原因。所以我的问题是,我如何同时使用它们。为了简单起见,假设我有一个登录页面。
1)用户登录后,一旦邮箱和密码被使用,如果用户经过身份验证,则服务器将发送一个CSRF并使用JWT存储httpOnly cookie(如何使用PHP设置cookie)。我理解的是,您可以使用header('Set-Cookie: X-Auth-Token=token_value; Secure; HttpOnly;');请确认是否是这样做的方法。
2)一旦我使用JWT设置了cookie,我如何在随后的请求中发送CSRF令牌?从我所了解的,您可以将它们设置在标头中。因此,如果我发出Ajax请求,我将把它们放在标头中。
3)一旦请求已完成并且CSRF令牌随请求发送。如何进行验证。我应该比较什么?
最后,这种实现是否安全!
我非常感激如果您能够包含尽可能多的细节。

SameSite Cookie属性可用于解决此问题,并且在浏览器上得到广泛支持已有几年时间。https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie/SameSite#browser_compatibility - Stephen Rauch
2个回答

27
我见过并且自己也使用的一种方法是将CSRF令牌作为声明包含在JWT中。因此,当用户发送用户名和密码时,您可以执行以下操作:
  1. 如果用户名和密码正确,请按照以下列表继续。
  2. 创建一个新的JWT,并在有效载荷中作为声明包含生成的CSRF令牌,然后对JWT进行签名。
  3. 通过设置包含JWT的HTTPOnly cookie来响应客户端的身份验证请求。这样确保只有浏览器(而不是客户端应用程序和可能存在恶意脚本)可以访问JWT。还将cookie设置为secure是个好主意。这可以防止浏览器在使用未安全的通信渠道(即非https)时发送cookie。
  4. 在设置JWT cookie时,您还应设置一个HTTP头,其中还包含您生成的CSRF令牌。请注意,现在您将在两个位置拥有CSRF令牌——在JWT cookie内部和在HTTP头中。
  5. 在客户端应用程序中,将标头中的CSRF令牌存储到localstorage中。
  6. 对于每个请求,请从localstorage中获取CSRF令牌,并将其作为请求头包含在内(包含JWT的cookie会自动传递给浏览器)。
  7. 服务器应从cookie中读取JWT,验证其签名并从JWT的有效载荷中读取CSRF令牌。然后,它应将其与请求头中的CSRF令牌进行比较。如果它们匹配,服务器可以继续处理请求。

我建议你观看这个关于JWT的讲座。它更详细地介绍了相同的方法(还有漂亮的图表)。随意观看整个讲座,或者如果你特别关注CSRF,请从36:29开始。

以下是一张幻灯片(来自上述演示文稿),展示了JWT和CSRF令牌如何一起使用。我用红色数字进行了注释,与上面的列表对应。 Sequence diagram of JWT and CSRF working together


你能澄清一下第四点吗?当你说在同一个响应中时,是指回显到客户端还是设置在头部? - Gacci
@Gacci 把它设置在头部。 - Indrek Ots
非常抱歉,我还是很困惑。#4中的头部是否属于头部#3?两者相同吗?我的理解是您设置了两个cookie,一个带有JWT(安全且仅限HTTP)和另一个cookie(仅限安全)。然后将仅限安全的cookie存储在localStorage中,然后与JWT中的那个进行比较。这是正确的吗? - Gacci
1
编辑了我的回答。在#3中,您设置了一个包含JWT的HTTPOnly安全cookie。 JWT本身还应包含您的CSRF令牌。在#4中,您设置了一个HTTP标头(而不是cookie),其中还包含您的CSRF令牌。在您的Web应用程序中,您只能读取包含CSRF令牌的HTTP标头。因此,当您从服务器接收到对您的身份验证请求的响应时,请阅读包含CSRF令牌的HTTP标头并将其存储在浏览器的本地存储中。稍后,当您向服务器发出另一个请求时,请从localstorage中读取CSRF令牌并将其设置为请求标头。 - Indrek Ots
JWT.CSRF是在每个登录请求上随机生成的,还是像全局秘密密钥一样由每个用户共享的? - Jerry
将csrf令牌保存在浏览器本地存储中是危险的。 - JeffMinsungKim

-1

防范 CSRF 的常见范式是为每个“表单”生成一个令牌,并验证为特定表单设置的令牌是否正确。

这样,攻击者无法猜测令牌,因此无法创建发送到您的服务器的外部表单。

通常,设置该令牌的方法是通过 Cookie,客户端应用程序将其附加到某些自定义标头中,这将为您提供额外的保护,然后,服务器应在 cookie 值和标头值之间进行比较。

重要的是,令牌只在一个会话中有效,否则,攻击者可以重复使用在攻击向量中获得的相同令牌。

JWT 是一种用于将用户身份验证到系统而不是验证请求是否有效的方法。

我将尝试通过示例来解释,假设您没有实施任何防止CSRF的保护措施,但是您确实有一种机制来验证用户(使用JWT),因此用户使用正确的用户名和密码登录,您的服务器将发送一个JWT,客户端将其保存到localStorage / memory中。 现在,攻击者可以在他的网站上创建一个指向您的服务器的表单,假设他知道用户的JWT令牌(他使用其他方法获得了它,如MITM),他所需要做的就是欺骗用户进入他的带有表单的页面,表单将被发送给您而不会出现任何问题,您的服务器将接受请求并成功攻击。
如果您实施了任何CSRF机制,则示例中的攻击者将无法猜测当前令牌,因此您的服务器将忽略他的请求。

我理解认证和授权之间的区别。我的问题是如何实现它,换句话说,我要比较什么。我对这些概念非常清楚! - Gacci
为了添加CSRF保护,如我所写的那样,您应该通过Cookie传递一个令牌,客户端应用程序将其作为隐藏字段附加到表单或通过标头附加到ajax请求。然后,您可以在服务器端比较Cookie中的令牌与附加字段(或标头)中的值是否相同。 - felixmosh
如果您将令牌存储在本地存储中,则不会受到 CSRF 的影响,这是反向的。如果您将令牌存储在 cookie 中并将其用作身份验证,则会受到影响,因为它会自动添加到 req 中。您应该将 CSRF 保护作为标头添加,因为攻击者无法自动添加自定义标头。 - glasspill
你是正确的,基本上,每当你只使用cookie来验证用户时,你就容易受到CSRF攻击。如果将JWT作为头部发送,则可以防止CSRF攻击。 - felixmosh
也许你应该专注于使用 web workers 安全存储你的 JWT Token。 - Musculaa

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