回答问题1
从我读到的内容来看,如果令牌存储在浏览器的 localStorage/sessionStorage 而不是 cookie 中,那么使用基于令牌的身份验证的 RESTful API 的应用程序很容易受到 XSS 而不是 CSRF 的攻击。这是因为,要使 CSRF 发挥作用,应用程序必须使用 cookie。我对么?
或多或少是正确的。仅使用 cookie 进行身份验证且没有任何 CSRF 保护的应用程序将容易受到 CSRF 的攻击,因为 cookie 会自动包含在所有请求中。
但现在令牌存储在 localStorage/sessionStorage 中,应用程序变得容易受到 XSS 攻击。
这并不是说应用程序容易受到 XSS 攻击,而是身份验证令牌容易受到 XSS 攻击。如果您的身份验证令牌发送为http-only
,那么 JavaScript(以及 XSS 攻击)就无法读取该值。
回答问题2
我认为这篇文章很好地解释了如何使用会话 cookie 和 CSRF 令牌。由于文章有点长,让我尝试总结一下:
- Use an
http-only
cookie(理想情况下也secure
— 作者没有提到 — 这会阻止浏览器在 http 请求(仅 https 请求)上发送 cookie验证令牌。这样,cookie就无法被JavaScript读取,从而无法在XSS攻击中被窃取。
- 使用会话存储来存储跨站请求伪造保护令牌。这样做的好处是一些 JavaScript 代码必须读取该值并将其放入请求中。在某些第三方、受感染的站点上运行的恶意代码将无法从本地存储读取 CSRF 令牌,因此无法创建有效的请求。
本文还详细介绍了如果您的静态资源与 REST 端点位于不同的域中,如何设置跨域标头。
然而,你的做法与文章所说的背道而驰。您将身份验证令牌放入 localStorage 中,并将 CSRF 令牌放入 cookie 中。我建议将其翻转回来,因为身份验证令牌比 CSRF 令牌更重要,并且http-only secure
cookie 比 localStorage 更难被坏人获取。
对你的方法的评论
上面帖子中提到的方法需要我坚持CSRFProtectionCookie
在某种数据库中。我不想那样做。我不希望每次向 API 发出经过身份验证的请求时都会访问数据库。
在这里,你正在沿着一条好的道路思考。您所描述的将被称为“无状态 CSRF 令牌”。
您对“经过身份验证的请求”的方法看起来不错。我不完全确定您需要加密 CSRF 令牌值,但这并没有什么坏处。
这消除了将任何内容保存到数据库的需要(或者是吗?我错过了一些漏洞吗?)
你说得对,没有漏洞。
但是,在每个用户请求上解密和验证哈希值的开销是多少?是否比数据库 I/O 开销更大?
I/O 通常比 CPU 密集型计算慢得多,即使对于加密之类的计算也是如此。也就是说,如果您真的担心它,则必须进行衡量(当然,说起来容易做起来难)。
最后的想法...
请记住,通过将 CSRF 令牌存储在 JWT 声明中,CSRF 令牌将与 JWT 令牌一样有效。即使您向客户端发送新的 JWT 令牌,旧令牌和 CSRF 令牌仍然有效。对于短期令牌(例如 10-60 分钟)来说,这不是一个问题,但是将 CSRF 令牌存储在服务器端某处的好处之一意味着您可以拥有用于超级敏感操作的一次性令牌。 (此外,如果您需要撤销尚未过期的 JWT 令牌,您还需要将该状态存储在服务器端的某个地方。但现在我们陷入了另一个兔子洞......)
实际上没有任何特定的框架能够完全防止 XSS 漏洞,因为它们可以以多种不同的方式表现出来。也就是说,您的网络应用程序可以使用Content-Security-Policy标头有助于防止 XSS 攻击。 CSP 标头可用于告诉浏览器允许从哪些域加载资源(例如 JavaScript 文件)。它甚至可以用来阻止内联 JavaScript 运行(这是 XSS 的主要攻击媒介之一)。