通过 CSRF 保护和刷新令牌安全地使用 JWT

信息安全 验证 饼干 csrf jwt
2021-09-05 11:34:15

我正在我的应用程序中实现 JWT,我想让它们尽可能安全。我将列出我正在计划的所有内容,非常感谢有关此实施安全性的任何建议。这是我的网站,我可以完全访问它的各个方面,包括前端和后端。

这将是一个 SPA,使用 API 访问后端。我使用 JWT 来保存每个 API 命中的数据库调用。

智威汤逊

JWT 存储在access_tokencookie 中。它们首先被签名,然后使用jose-jwt加密。签名算法是HS256,加密是DIR。它们包括用户 ID、过期exp声明和其他几个自定义声明。JWT 在 30 分钟后过期,JWT cookie 在 7 天后过期。

(简洁版本):

  • 存储在 cookie 中的 JWT
  • JWT 包含用户 ID
  • JWT 签名然后加密
  • JWTexp声明将在未来设置为 30 分钟
  • JWT cookie 设置为在未来 7 天后过期

CSRF 保护

JWT 包含一个cst存储随机生成的 CSRF 令牌的声明。CSRF 令牌在登录时和发出新 JWT 时在响应正文中发送。CSRF 令牌存储在浏览器的 localStorage 中。它随每个请求一起发送,并根据 JWT 中的值进行验证。

(简洁版本):

  • JWT 包含一个随机生成的 CSRF 令牌
  • 登录时发送并存储在 localStorage 中的 CSRF 令牌
  • 在所有请求的请求头中发送的 CSRF 令牌
  • 标头 CSRF 令牌与 JWT 中的 CSRF 令牌相比

刷新令牌

由于 JWT 在 30 分钟后到期,因此有必要对其进行刷新。JWT 包含一个rfs存储随机刷新令牌的声明。此刷新令牌也存储在数据库中(与用户分开的表以允许多个会话)。如果 JWT 已过期(基于其exp声明),则检查数据库以确保用户仍然有效(例如,未删除帐户、未更改密码等)。如果用户有效,则验证刷新令牌并生成新的 JWT/CSRF 令牌并在响应中传回。如果用户无效,access_token则返回一个任意值,如0,并将其过期设置为过去,以便浏览器将其清除。CSRF 令牌传回为空,因此将从 localStorage 中清除。用户的所有刷新令牌都从数据库中清除。

(简洁版本):

  • 如果 JWT 过期,检查用户数据库以验证用户是否仍然有效
  • 如果有效:
    • 将刷新令牌与 DB 进行比较(假设它与其余部分匹配)
    • 生成新的刷新令牌,覆盖数据库中的先前值
    • exp用新日期重新签发 JWT
    • 将刷新令牌传回并存储在 localStorage
  • 如果无效:
    • 通过 a) 设置为无效值,b) 将过期设置为过去,清除 JWT cookie
    • 告诉浏览器清除 localStorage CSRF 令牌
    • 从数据库中清除用户的所有刷新令牌

快速而肮脏的 TL;DR

  • 登录后,将随机 CSRF 令牌添加到 JWT。
  • 在响应正文中将相同的 CSRF 令牌发送回客户端。
  • 将 CSRF 令牌存储在 localStorage 中。
  • 在 JWT 中包含刷新令牌。
  • 将 JWT cookie 设置为 1 周后过期。
  • 将 JWT exp 声明设置为 30 分钟。
  • 如果 JWT 声明已过期,请针对 DB 验证刷新令牌以确保用户仍然有效。
  • 如果用户有效:
    • 使用新的 CSRF 令牌和新的刷新令牌发出更新的 JWT。
    • 将 JWT cookie 的到期时间设置为未来一周。(基本上是重新发行cookie)
    • 在响应正文中发送新的 CSRF 令牌,覆盖现有的 localStorage 值。
  • 如果用户无效:
    • 返回具有相同名称但没有内容的 JWT cookie。
    • 将 cookie 过期时间设置为过去的任意日期。
    • 告诉浏览器清除 localStorage 值。
3个回答

您似乎混合了几种不同的对立技术,并没有说明您选择这些技术的原因以及它们控制您试图保护的威胁的原因。

JWT 存储在 access_token cookie 中。它们首先被签名,然后使用 jose-jwt 加密。

它们被加密有什么原因吗?当您想要验证数据的完整性时使用签名 - 即它没有被没有密钥的任何人更改。当您想要保护数据的机密性时使用加密 - 即没有密钥的任何人都无法读取它。如果令牌中没有最终用户无法读取的内容,则没有理由加密。

JWT 包含一个存储随机生成的 CSRF 令牌的 cst 声明。CSRF 令牌在登录时和发出新 JWT 时在响应正文中发送。CSRF 令牌存储在浏览器的 localStorage 中。它随每个请求一起发送,并根据 JWT 中的值进行验证。

这听起来像是Double Submit Cookie CSRF 控件的实现。localStorage 受同源策略保护,防止用户浏览器中的另一个 Web 会话访问令牌。

确保 CSRF 令牌具有至少 128 位的熵。

由于 JWT 在 30 分钟后到期,因此有必要对其进行刷新。JWT 包含一个存储随机刷新令牌的 rfs 声明。此刷新令牌也存储在数据库中(与用户分开的表以允许多个会话)。如果 JWT 已过期(基于其 exp 声明),则检查数据库以确保用户仍然有效(例如,未删除帐户、未更改密码等)。如果用户有效,则验证刷新令牌并生成新的 JWT/CSRF 令牌并在响应中传回。

这就是事情变得复杂的地方。您的安全模型应决定客户端会话或服务器端会话。JWT 通常用于前者。也就是说,如果您在客户端中有一个由仅对服务器可用的密钥签名的令牌,那么您的应用程序应该相信这个令牌的存在表示一个有效的会话,如果它没有过期并且签名是有效的。

这种方法的缺点是难以撤销令牌。如果用户帐户被盗,并且用户更改了密码,则任何攻击者会话仍将处于活动状态 30 分钟,因为它们仍然具有有效、未过期且已签名的令牌。

解决此问题的方法是改为实现服务器端会话。例如,您跟踪数据库表中的会话,这意味着可以通过删除数据库中的行来立即注销它们。会话令牌可以是 128 位熵随机字符串,作为 cookie 客户端提供,并使用 SHA-256 服务器端进行散列存储,以减少数据库中的任何数据泄漏。您始终可以使用 cookie 发送纯文本到期日期,以便客户端知道何时需要刷新令牌。例如,一个用于令牌的 HttpOnly cookie,以及一个包含过期时间的非 HttpOnly cookie,以便客户端 JavaScript 可以读取它。HttpOnly cookie 可以帮助减轻 XSS 漏洞的影响。

因此,如果您在服务器端跟踪会话,那么拥有已签名的 JWT 客户端几乎没有什么优势。额外的代码意味着更多的攻击面和更多的漏洞被引入剩余代码的机会。应用程序的复杂性通常与安全性相反。

如果您将 Double Submit Cookie 与用于会话状态的服务器端会话结合使用,那么明智的做法是为 CSRF 令牌使用不同的 cookie。这将使您能够使用客户端代码将 CSRF 令牌添加为标头,而不会冒会话标识符令牌的风险。请注意,如果您正在设置自定义标头而不是实现 CORS,那么这可以在一定程度上缓解 CSRF不过建议也使用令牌,因为像 Flash 这样的技术往往会破坏浏览器的安全性(Flash 意味着更多的代码运行,更多的代码提供更多的攻击面,更多的代码意味着更多的漏洞机会)。

在 Refresh Token 部分之前,这对我来说听起来相当合理(CSRF 解决方案也很可爱)。

我在基于 JWT 的系统中看到的优势之一是访问令牌会定期过期。这意味着受损的 JWT 访问令牌只能让攻击者访问几分钟或几小时。

如果您在 JWT 中包含 Refresh Token,则破坏 JWT 的攻击者可以轻松地使用它来刷新自己,使其没有有效的到期日期。

访问令牌不应该能够授予新的访问令牌。只有刷新令牌(或完整身份验证)才能授予新的访问令牌。

从评论汇总:

  • JWT 作为单页应用程序中的承载凭证,通过 ajax 与服务器端 API 对话是有意义的。
  • 在单页应用程序中使用 cookie 没有意义。如果通过 ajax 函数对服务器 API 的所有请求都成为瓶颈,则该函数可以在标头中包含 JWT。如果没有cookies,那么就没有CSRF,因为来自攻击帧的请求不会经过ajax函数
  • 第三方脚本的使用当然是普遍的,但它仍然是一个很大的安全风险。第三方脚本在相同的 javascript 和 DOM 上下文中运行,并且可以看到整个 DOM。如果它们是恶意的,则有很多很多的攻击选项。如果可能,消除第三方脚本或确保它们经过验证,使用子资源完整性或其他方式(例如监控它们的变化)。
  • localStorage 遵循同源策略,可以被视为 DOM 或授权数据的客户端缓存。在 SPA 中,JWT 可以保存在 localStorage 中,因为它遵循与 DOM 相同的安全模型。
  • 刷新令牌可以保存在 JWT 中
  • JWT 过期和刷新生命周期很难在摘要中评论。重要的是要在安全性和可用性之间找到适当的平衡,除非有充分的理由,否则不要要求用户跳过障碍,并在用户有疑虑时为他们提供正确的保证。

其他一些事情要做:

  • 强制执行 https 并使用相关的资源协议标头,例如 x-frame-options、严格的传输安全和内容安全策略
  • 在 JWT 中保留特定版本的单页应用程序的一些元数据,并在应用程序更新后需要刷新
  • 将特定客户端的元数据保存在 JWT 中。不允许从移动浏览器使用桌面浏览器 JWT。
  • 有能力从服务器撤销所有未完成的 JWT 并刷新与用户关联的令牌。如果用户将 JWT 和刷新令牌绑定到他们的手机和桌面,并告诉您他们丢失了手机,您可能希望使他们的手机和桌面 JWT 和刷新令牌无效。
  • 在服务器 API 上拥有强大的对象/操作级别授权 - 意思是,验证 JWT 的完整性,确认它打包的授权没有被撤销,然后检查它包含的声明是否提供了对 API 的适当访问,然后再执行任何操作API 操作。