分布式系统

不要将 JWT 用于会话管理

2019-02-15  本文已影响16人  萧哈哈

原文链接: Stop using JWT for sessions
译者: dreamdevil00

最近我看到越来越多的人建议在 web 应用中使用 JWT 管理用户会话。这是个很烂的主意, 稍后我会解释原因。

为避免混淆, 首先定义几个术语:

澄清一点: 本文并没有声明你不应该使用 JWT, 仅仅是说明 JWT 不适用于会话机制, 将 JWT 用于会话管理是危险的。 JWT 自有其适用场景。 文末我会简短的谈下其他使用场景。

小注

很多人会错误地对 cookie 和 JWT 进行比较。 这种比较根本没有意义, 如同比较苹果和橘子。 cookie 是一种存储机制, 而 JWT 令牌是加密过地签名令牌。cookie 和 JWT 并不是对立的, 相反, 它们可以一起使用, 也可以单独使用。 恰当的比较是, "sessions vs. JWT" 以及 "cookies vs. Local Storage".

所谓的 JWT 优点

推荐 JWT 的人通常会说下述的一个或几个好处:

我将逐一阐述这些说法——以及它们为什么是错误的或具有误导性的。下面的一些解释可能会有点含糊不清,这主要是因为这些说法是含糊不清的的。

易于 (水平) 扩展

从技术上来讲, 这可能是上述列表中唯一稍微正确的表述了,但是仅对使用 无状态 的 JWT 而言。然而, 事实上, 几乎没有人真正需要这种扩展性, 有很多更容易的方法进行扩展, 除非操作的是 Reddit 这样的体量,你并不需要 无状态的会话

一些扩展 有状态 会话的例子:

  1. 单服务器上运行多个后端进程: 服务器上的一个 Redis daemon 用于会话存储
  2. 在多个服务器上运行: 一台专用于会话存储的 Redis 服务器。
  3. 在多个服务器上运行, 多个集群: 粘性会话.

这些都是现有软件能很好地支持的场景。你的应用程序极不可能超越上述第二步。

也许你会想,你的应用未来可能会扩展到超过上述第二步。然而,在实践中,在以后的某个时间点替换会话机制非常简单,唯一的成本就是在进行转换时对每个用户注销一次。实现 JWT 前端是不值得的,特别是考虑到我稍后要讨论的缺点。

易于使用

真的不是这样。如果使用 JWT 进行会话管理,你必须自己处理会话管理,无论是在客户端还是在服务器端,而标准的会话 cookie 开箱即用。JWT在任何方面来说都不容易。

更加灵活

我还没看到有人真正解释 JWT 是 如何 更灵活的。几乎每个主流的会话实现都允许您存储会话的任何数据,这与 JWT 的工作方式没有什么不同。据我所知,这只是一个时髦的词。如果你不同意,可以带上实例联系我

更加安全

很多人认为 JWT 令牌会更加安全, 因为 JWT 使用了加密学。签名的 cookie 也比 未签名的 cookie 更加安全, 这不是 JWT 独有的, 良好的会话实现也使用签名的 cookie。

“它使用密码学” 也不能神奇地使某些东西更加安全;它必须为特定目的服务,并且是实现该特定目的有效解决方案。事实上,不正确地使用密码技术可以使某些东西不那么安全。

我经常听到的关于“更安全”论点的另一个解释是,“它们不是作为 cookie 发送的”。这是完全没有意义的-cookie 只是一个 HTTP 头,使用 cookie 没有什么不安全的地方。事实上,cookie 可以很好地预防恶意客户端代码,我稍后再谈。

如果你担心有人会拦截会话 cookie, 你应当使用 TLS - 如果不使用 TLS 的话, 包括 JWT 在内,任何 会话实现都是可拦截的。

内置的过期功能

这是无稽之谈,也不是一个很有用的特性。过期也可以在服务器端实现,而且很多实现都是这样做的。实际上,服务器端过期更好——它允许应用程序清理不再需要的会话数据,如果使用有状态的 JWT 令牌并依赖于其过期机制,则不能这样做。

不需要向用于请求 cookie 许可

完全错误。没有所谓的“cookie 法律”-关于cookie的各种法律实际上涵盖了任何类型的持久性标识符,这些标识符对于服务的运行来说并不是绝对必要的。您可以想到的任何会话机制都将包括在此范围内。

简而言之:

预防 CSRF

实际上并不能预防 CSRF。 有 2 种方式存储 JWT:

唯一 正确的 CSRF 预防措施是使用 CSRF 令牌。这和会话机制无关。

移动端运行良好

废话。每个仍在使用的移动浏览器都支持 cookie,因此支持会话。对于每一个主流的的移动开发框架,以及任何严谨的 HTTP 库,都是如此。这根本不是问题。

禁用 cookie 的设备上仍然可以运行

不太可能。用户不只是阻止cookie,他们通常阻止所有持久化的手段。这包括本地存储,以及允许您持久化会话的任何其他存储机制(不管是否使用JWT)。无论是否使用 JWT 在这里都无关紧要,这是一个完全不同的问题-尝试在没有 cookie 的情况下获得身份验证是一个失败的原因。

最重要的是,阻止 所有 cookie的用户通常明白,这会破坏他们的身份验证功能,而对于他们关心的站点,单独地解除 cookie 的阻塞。作为一个 web 开发人员,这根本不是你需要解决的问题;更好的解决方案是向你的用户解释为什么你的网站需要cookie才能工作。

缺点

现在我已经讨论了所有常见的声明及其错误的原因,你可能会觉得“哦,这不是什么大事,即使 JWT 没有帮到我, 这没什么大不了的,你可能是错的。使用 JWT 作为会话机制有很多缺点,其中一些是严重的安全问题。

占用更多的空间

JWT 令牌并不是很小。特别是在使用无状态 JWT 令牌时,所有数据都直接编码到令牌中,会很快超过cookie 或 URL的大小限制。你可能决定将它们存储在Local Storage中,但是...

它们不太安全

在 cookie 中存储 JWT 时,它与任何其他会话标识符都没有区别。但是,当你将 JWT 存储在其他地方时,很容易受到新的攻击类别的攻击,此文(特别是 ”存储会话“ 节)对此进行描述:

We pick up where we left off: back at local storage, an awesome HTML5 addition that adds a key/value store to browsers and cookies. So should we store JWTs in local storage? It might make sense given the size that these tokens can reach. Cookies typically top out somewhere around 4k of storage. For a large-sized token, a cookie might be out of the question and local storage would be the obvious solution. However, local storage doesn’t provide any of the same security mechanisms that cookies do.

Local storage, unlike cookies, doesn’t send the contents of your data store with every single request. The only way to retrieve data out of local storage is by using JavaScript, which means any attacker supplied JavaScript that passes the Content Security Policy can access and exfiltrate it. Not only that, but JavaScript also doesn’t care or track whether or not the data is sent over HTTPS. As far as JavaScript is concerned, it’s just data and the browser will operate on it like it would any other data.

After all the trouble those engineers went through to make sure nobody is going to make off with our cookie jar, here we are trying to ignore all the fancy tricks they’ve given us. That seems a little backwards to me.

简单地说,使用 cookie 不是可选的,不管你是否使用JWT。

无法使某个 JWT 令牌失效

还有更多的安全问题。与会话不同(服务器可以在任何时候让会话失效), 单独的无状态 JWT 令牌无法失效。从设计上看,无论发生什么,在过期前, JWT 令牌都是有效的。这意味着,在检测到损害后,你不能使攻击者的会话无效。当用户更改密码时,也不能使旧的会话无效。

基本上,你是无能为力的,要结束会话,必须构建复杂的(并且是有状态的)基础设施,显式地检测和拒绝它们,这就克服了使用无状态 JWT 令牌的全部问题。

数据变得过时

在某种程度上,还有另一个潜在的安全问题与这个问题有关。与缓存中的数据一样,无状态令牌中的数据最终将“过时”,不再反映数据库中数据的最新版本。这意味着令牌中可能包含一些过时的信息,比如某个过时的网站 URL。但更严重的是,这也意味着某个人拥有一个角色为 “admin” 的令牌,即使你刚刚删除了他们的 ‘admin’ 角色。因为你不能使令牌失效,你无法删除它们的管理员访问权限,除非关闭整个系统。

很少经过战斗测试(battle-tested)的实现或不存在这样的实现。

你可能会认为所有这些问题都与 无状态的 JWT令牌有关,这基本上是正确的。但是,使用有状态的令牌基本上等同于常规的会话 cookie... 但是没有经过战斗测试的实现。

现存的会话实现(例如, 用于 Express 的 express-session) 已经在生产环境下使用了很多年, 因此其安全性有很大的改进。当你使用 JWT 令牌作为临时会话 cookie时,你不会得到这些好处。你将不得不滚动你自己的实现(很可能在这个过程中引入漏洞),或者使用没有多少实际使用的第三方实现。

结论

无状态 JWT 令牌无法失效或更新。取决于存储位置,JWT 会引入占用空间问题或安全问题。有状态的 JWT令牌在功能上与会话 cookie 相同,但是没有经过严格测试和审查的实现或者客户端支持。

除非你工作于 Reddit 级别的应用, 没有理由使用 JWT 令牌作为会话机制。 请使用 session

那么, JWT 适合干嘛呢?

在本文的开头,我说过 JWT 有其适用场景,只是它不适合作为会话机制。这仍然成立;JWT 作为 一次性使用的授权令牌 使用非常高效。

摘自 JSON Web Token specification:

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. [...] enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.

上述语境中, "claim" 类似于 '命令', 一次性授权, 或者是任何其他可以这样讲出来的情景:

你好, 服务器 B, 服务器 A 告诉我 我可以 <这里是 claim>, 这是 (加密的) 证明.

比如:
现有一文件托管服务, 用户必须经过认证后才能下载文件, 但是文件本身是由一个单独的,无状态的下载服务器提供。在此场景中, 你可以让应用服务器(Server A) 发布一次性使用的 "下载令牌", 该令牌用于从下载服务器(Server B) 下载文件。

该使用场景中, JWT 有几个特点:

如你所见, 将会话和 JWT 令牌组合在一起是合理的—— 会话和 JWT 都有其各自的目的, 有时你可能都需要。只是不要将 JWT 用于持久的、有效期长的数据

不要将 JWT 用于会话管理,第二部分: 为什么你的解决方案无法生效?

上一篇下一篇

猜你喜欢

热点阅读