使用 JWT 作为 Session 系统!问题重重且很危险。
温馨提示:这篇文章已超过601天没有更新,请注意相关的内容是否还可用!
资料来源:learnku.com/articles/22616
JWT 结论的缺点 那么... JWT 有什么用?
JSON Web 令牌,也称为 JWT。 本文将详细讲解:为什么JWT不适合存储Session,以及JWT带来的安全隐患。 希望大家对JWT有更深入的了解!
❝
原文地址:~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions
❞
不幸的是,我发现越来越多的人开始推荐使用JWT来管理网站的用户会话(Session)。 在这篇文章中,我将解释为什么这是一个非常非常不成熟的想法。
为了避免混淆和歧义,首先定义了一些术语:
需要澄清的是:这篇文章并没有挑起“永远不要使用JWT”的争论——它只是想表明JWT不适合作为Session机制,是非常危险的。 JWT 在其他方面确实占有一席之地。 在本文的最后,我将简要描述一些合理的用途。
图片需要先解释
很多人错误地试图将 Cookies 和 JWT 进行比较。 这种比较是没有意义的,就像比较内存和硬盘一样。 Cookie 是一种存储机制,而 JWT Tokens 是经过加密和签名的令牌。
它们不是对立的——相反,它们可以单独使用或组合使用。 正确的比较应该是:Session vs. JWT,以及 Cookies vs. Local Storage。
在本文中,我会将 JWT Tokens 与 Session 进行比较,偶尔还会将 Cookie 与 Local Storage 进行比较。 这样的比较是有道理的。
JWT 的轶事优势
人们在安利智威汤逊时,经常宣扬以下好处:
我会一一解释为什么以上几点是错误的或误导的,有些解释可能有些含糊,主要是这些“好处”的表述本身就比较模糊。
横向扩展容易吗?
这是列表中唯一在技术层面上部分正确的“好处”,但前提是您使用的是无状态 JWT 令牌。 然而在现实中,几乎没有人需要这种横向扩展能力。 有许多更简单的扩展方法。 除非你是在运维淘宝这么大的系统,否则根本不需要无状态会话。
扩展有状态会话的一些示例:
《在一台服务器上运行多个后端进程》:只需要在这台服务器上安装Redis服务来存储Session。 “运行多个服务器”:只需要一个专用的 Redis 服务器来存储会话。 “在多个集群中运行多个服务器”:会话持久性(又名:粘性会话)。
以上场景在现有的软件系统中都得到了很好的支持,您的应用需要特殊处理的可能性基本为零。
也许你在想,你应该在你的应用程序中留出更多的调整空间,以防将来需要进行一些特殊的操作。 但是实践告诉我们,以后更换Session机制并不困难。 唯一的代价是迁移后所有用户将被强制注销一次。 我们不需要预先实现 JWT,尤其是考虑到它带来的负面影响。
使用方便?
这真的不是。 你要自己处理Session管理机制,不管是客户端还是服务端。 虽然标准会话 cookie 开箱即用,但 JWT 并不简单。
说白了,目前各种开箱即用的框架并没有自动集成JWT,需要开发者自行处理。
更灵活?
我还没有看到有人成功地解释了“JWT 如何更灵活”。 几乎每一个主流的 Session 实现都允许你将数据直接存储到 Session 中,这与 JWT 机制没有区别。 据我所知,这只是一个流行语。
更安全?
由于使用了加密技术,很多人认为 JWT Tokens “更安全”。 事实上,签名的 cookie 也比未签名的 cookie 更安全,但这绝不是 JWT 独有的。 优秀的 Session 实现使用签名 cookies(译者注:如 Laravel)。
“使用密码学”并不能神奇地使某些东西更安全,它必须服务于特定目的并且是该目的的有效解决方案。 不正确地使用加密实际上会降低安全性。
我多次听到的另一个“更安全”的论点是“JWT 不使用 Cookies 来传输令牌”。 这很可笑,cookie 只不过是一个 HTTP 标头,使用 cookie 并没有什么不安全的地方。 事实上,cookie 可以很好地防止恶意客户端代码。
如果您担心有人拦截您的会话 cookie,那么您应该考虑使用 TLS。 如果不使用 TLS,任何类型的会话机制都可能被拦截,包括 JWT。
内置过期时间功能?
无意义和无用的功能。 过期控制也可以在服务器端实现,很多Session的实现都是这样做的。 其实服务端的过期控制更合理,让你的应用可以清除不再需要的Session数据; 如果您使用无状态 JWT 令牌并依赖其过期机制,则无法执行此操作。
这个过期时间实际上在某些场景中增加了复杂性。
不用问用户“这个网站使用cookies”?
完全错了。 不存在所谓的“cookie 法”——关于 cookie 的各种法律实际上涵盖了任何类型的“对于服务的正常运行并非绝对必要的持久性 ID”,任何你能想到的 Session 机制都包括在内。
❝
译者注:然而,鹅中国没有。
❞
简单的说:
防止 CSRF 攻击?
这真的不是。 JWT Token的存储方式大致有两种:
防止 CSRF 攻击的唯一正确方法是使用 CSRF Tokens。 Session机制与此无关。
更适合移动端?
毫无根据。 几乎所有当前可用的浏览器都支持 Cookie,因此也支持会话。 主流的移动开发框架和严肃的 HTTP 客户端库也是如此。 这根本不是问题。
对于阻止 cookie 的用户?
不太可能。 用户通常会阻止任何意义上的持久数据,而不仅仅是 cookie。 例如,Local Storage 和任何可以持久化 Session 的存储机制(无论是否使用 JWT)。 使用 JWT 有多简单并不重要,那完全是另一个问题。 此外,试图在没有 cookie 的情况下使身份验证过程正常工作基本上是没有用的。
最重要的是,大多数禁用所有 cookie 的用户都明白这将使身份验证无法使用,并且他们将单独取消阻止他们关心的站点。 这不是您作为 Web 开发人员应该解决的问题。 更好的解决方案是向您的用户详细解释为什么您的网站需要使用 cookie。
JWT 的缺点
上面,我已经解释了常见的误解及其错误的原因。 你可能会想:“这看起来没什么大不了的,即使 JWT 没有带来任何好处,也不会有什么不同”,那你就大错特错了。
使用JWT作为Session机制有很多缺点,其中一些会造成严重的安全问题。
更多空间
JWT 令牌实际上并不“小”。 尤其是使用stateless JWT的时候,所有的数据都会直接编码成Token,很快就会超过Cookies或者URL的长度限制。 您可能会考虑将它们存储在本地存储中,但是......
更不安全
如果 JWT Token 存储在 Cookies 中,安全性与其他 Session 机制相同。 但如果将 JWT 存储在其他地方,则会导致新的漏洞,详情请参阅,尤其是“Storing sessions”部分。
❝
Local Storage 是 HTML5 中的一个很棒的特性,它使浏览器能够支持 Key/Value 存储。 那么我们应该将 JWT Tokens 存储在 Local Storage 中吗? 考虑这些令牌可能会变得越来越大可能是有用的。 Cookie 通常在 4k 左右的存储上有优势。 对于较大的 Token,Cookies 可能无法胜任,Local Storage 可能成为一个明确的解决方案。 但是,Local Storage 不提供任何类似 Cookies 的安全措施。 LocalStorage 与 Cookies 不同,它不会随每个请求发送存储的数据。 访问数据的唯一方法是使用 JavaScript,这意味着攻击者注入的任何 JavaScript 脚本都可以通过内容安全策略检查随意访问或泄露数据。 不仅如此,JavaScript 不关心或跟踪数据是否通过 HTTPS 发送。 就 JavaScript 而言,它只是数据,浏览器将其视为任何其他数据。 在几代工程师为确保没有人可以恶意访问我们的 cookie 而经历了所有麻烦之后,我们试图忽略这种经历。 这对我来说似乎是倒退了一步。
❞
简单的说就是“Using Cookies is not optional”,不管你用不JWT。
不能单独摧毁
还有更多的安全问题。 不像Session,可以随时单独在服务器端销毁。 无状态 JWT 令牌不能单独销毁。 根据 JWT 的设计,Token 无论如何都会一直有效直到过期。 例如,这意味着当检测到攻击时,您无法破坏攻击者的会话。 同样,用户修改密码后,旧的Session也无法销毁。
我们对此无能为力,除非我们重建一个复杂的有状态(Stateful)基础设施来显式检测或拒绝特定的 Session,否则我们将无法结束会话。 但这完全违背了使用无状态 JWT 令牌的初衷。
数据延迟
与上述安全问题类似,还有另一个潜在的安全问题。 就像缓存一样,存储在无状态令牌中的数据最终会变得“陈旧”,不再反映数据库中的最新数据。
这意味着过期的信息可能会保留在 Tokens 中,例如:用户在个人信息页面修改的旧 URL。 更严重的是,它可能是一个具有管理员权限的 Token,即使你已经撤销了管理员权限。 因为这些Token是无法销毁的,所以面对需要取消的管理员权限,除了关闭整个系统之外别无他法。
实施库缺乏生产验证或根本不存在
您可能认为以上所有问题都围绕着“无状态 JWT”展开,这在很大程度上是正确的。 然而,使用有状态令牌基本上等同于传统的会话 cookie……但缺乏生产环境的广泛验证。
现有的Session实现(比如Express的express-session)已经在生产环境中使用了很多很多年,安全性有了很大的提高。 如果使用JWT作为Session cookies的临时替代品,将无法享受到这些好处,还得不断完善自己的实现(过程中容易引入bug),或者使用第三方实现,虽然还没有在现实世界中。 有很多应用。
❝
译者注:实际上,Laravel Passport 使用了一种类似于“stateful JWT”的方法来存储 OAuth Access Token。 幸运的是,Passport 已经有很多实际应用无法验证服务器身份,并不完全依赖于 JWT。
❞
综上所述
Stateless JWT Tokens 不能单独销毁或更新,这取决于你如何存储它们,它也可能导致长度问题和安全风险。 Stateful JWT Tokens 在功能上和 Session cookies 没有区别,但是缺少生产环境的验证,大量 Reviews 的执行,以及良好的客户端支持。
除非你在 BAT 这种规模的公司工作,否则没有理由使用 JWT 作为 Session 机制。 或者直接使用Session。
那么... JWT 有什么用?
在这篇文章的开头,我提到了JWT虽然不适合作为Session机制,但是在其他方面确实有它的一席之地。 虽然声明仍然成立,但 JWT 特别有效的一个例子通常是作为一次性授权令牌。
引用 JSON Web Token 规范():
❝
JSON Web Token (JWT) 是一种紧凑的 URL 安全方式,用于表示要在两方之间传输的声明。 [...] 使声明能够通过消息身份验证代码 (MAC) 进行数字签名或完整性保护和/或加密。
❞
在这种情况下,“声明”可以是“命令”、一次性身份验证或基本上可以用以下句子描述的任何内容:
❝
你好服务器 B,服务器 A 告诉我我可以 <...Claim...>,这是我的证明:<...Key...>。
❞
例如,你有一个文件服务,用户必须经过身份验证才能下载文件,但文件本身存储在一个完全独立且无状态的“下载服务器”中。 在这种情况下,你可能希望“应用服务器(服务器A)”发出一次性的“下载令牌”,用户可以使用它去“下载服务器(服务器B)”获取所需的文件。
以这种方式使用 JWT 有几个明显的特性:
正如您在上面看到的无法验证服务器身份,结合会话和 JWT 令牌是有意义的。 它们各有各的用途,有时您需要将两者结合使用。 只是不要将 JWT 用于“持久的、长期的”数据。