该应用程序是一个托管在静态存储上的 SPA(概念上类似于 S3,尽管它不是 S3(,并且根本没有后端服务器。假设这是https://static.example.com/app.html
当用户访问该页面时,他们可以使用外部提供程序(如 Auth0 和 Azure AD(进行身份验证。它们完成身份验证流,并通过 URL 片段上的id_token
发送回 SPA。例如,https://static.example.com/app.html#id_token=XX
.该id_token
用于调用在Bearer
授权标头中传递的外部 API 服务器。
问题是在客户端中存储 JWT 的位置。
- 众所周知,将 JWT 存储在
sessionStorage
中可能会导致令牌被 XSS 攻击(或在依赖项中添加的恶意代码等(窃取。 - 推荐的方法是将 JWT 存储在设置为
HttpOnly
或至少部分 cookie 中(请参阅"Cookie 拆分"部分(。但是,这在我的情况下是不可行的,因为没有后端服务器,并且在经过身份验证后,用户将直接重定向到 SPA,因此我无法创建HttpOnly
cookie。
这种方法的一个变体是OWASP推荐的:使用"指纹cookie"。这有同样的问题,因为我无法设置HttpOnly
的cookie。 - 另一种方法(例如 Auth0 文档建议(是将 JWT 保存在内存中。虽然这应该防止大多数(如果不是全部?XSS 攻击,这是不切实际的,因为会话将仅限于当前选项卡,并且无法在页面重新加载后幸存下来。
我看到两种不同的选择,都有严重或潜在的严重缺点:
- 无论如何,将令牌存储在
sessionStorage
中,假设在XSS攻击(或通过NPM注入的恶意依赖(可能导致会话被盗的情况下存在风险。这可以通过为令牌设置较短的生命周期(例如 1 小时(来缓解。虽然我正在开发的应用程序不存储关键信息(它不是银行或类似信息(,但代码中的错误让会话通过 XSS 被盗并不好。 - 实现后端服务器以将身份验证流移动到那里,甚至可以将 JWT 完全替换为会话令牌。但是,这将使应用程序不再是静态的,这是不可取的。
- (第三个选项,将 JWT 保留在内存中,由于用户体验不佳而被排除在外(
我错过了什么?
安全性是一种折衷方案 - 您可以选择接受XSS的风险,或者承担后端服务器的负担以提高安全性,或者牺牲用户体验以获得可能介于两者之间的安全级别。你不可能拥有一切。
一种常见的解决方案是在 sessionStorage 中使用生存期非常短的令牌,以及一个 httpOnly cookie,以便标识提供者在需要时获取新令牌。通过这种方式,窃取会话令牌为攻击者提供的价值较小,并且由于XSS需要用户交互,因此攻击者有时可能很难获得新的令牌(或容易,具体取决于XSS的位置(。此外,此解决方案需要更优雅的错误处理,并导致代码稍微复杂一些。