JWT

 JWT

 

前段时间我也简单的用了一下JWT,感觉它很废(个人观点),why?,下面是我整合其他人的一些观点。

 

声明

本文内容的作用范围仅限 Web 应用。

词汇定义

无状态 JWT:JWT 中存储所有认证授权信息,服务端不存储任何相关数据。

有状态 JWT:JWT 中存储认证授权信息的 ID,具体数据存储在服务端。

Session / Cookie :有几种实现形式:

签名的 Cookie 中直接存储Session 信息,服务端不存储 Session 信息(与无状态 JWT 类似)

签名的 Cookie 中存储Session ID,服务端存储 Session 信息(与有状态 JWT 类似)

不签名的 Cookie 由于安全性过低,不推荐使用。所以这里也不再提及。

使用 JWT 的荒唐理由

很多人推荐在前后端分离的 Web 应用中使用 JWT 来做认证授权,他们的理由如下:

l  易于横向拓展

l  易于使用

l  更灵活

l  更安全

l  避免了CSRF 攻击

l  内置过期功能

l  移动设备上的兼容性更好,而不像 Cookie

l  即使用户禁用Cookie,应用也能很好的工作

l  RESTful API 应该是无状态的,所以认证授权也应该是无状态的

……

不过,个人觉得这些都是些站不住的脚的理由。

易于横向拓展

这是从技术上讲,唯一一个说得过去的理由,但这也仅限于使用无状态的 JWT 的情境。

易于使用

这是最让我难以信服的理由,在 Web 应用中,JWT 的使用一点都不简单。

设想一个使用 JWT 进行认证授权的前后端分离应用:

初期:通过用户名 / 密码进行认证授权,获取到可用的无状态 JWT。通过获取到的无状态 JWT 访问 API,好简单。

需求变更 1:JWT 没有过期时间的话,被恶意获取并滥用,该怎么办?于是为无状态 JWT 加上了过期时间。

需求变更 2:加上了过期时间的无状态 JWT,在用户将要提交表单的前一刻过期了,用户得重新登录,用户不能接受这种设定,表示不再使用你的应用。于是客户端开始使用各种策略刷新无状态 JWT。

需求变更 3:用户说他的帐号密码泄漏了,有人登录了他的帐号。客服帮忙重置了密码,但是恶意登录者通过尚未过期的无状态 JWT 又改了密码,用户仍旧无法登录自己的帐号。所以,修改密码后,得让修改密码前的所有无状态 JWT 失效。然而,这是无状态 JWT 做不到的。于是,不得不开始在服务端存储一些 JWT 的相关信息,以获取对用户的更高级别的控制权。就这样,无状态 JWT 变成了有状态 JWT。

以上描述的使用 JWT 进行认证授权的前后端分离应用,客户端要处理 JWT 的刷新,服务端需要保存 JWT 的相关状态,也就是说,前后端都要去维护 JWT 的状态。而传统 Session / Cookie 的认证授权方式只需要后端维护状态。

所以,个人认为,Web 应用中使用 JWT 是让应用变得更复杂了。

驳回。

更灵活

「JWT 中可以随意地添加数据,这样可以更灵活。」但,差不多所有的 Session 实现也都能随意地保存数据。凭借这个理由来说 JWT 优于 Session / Cookie,难以令人信服。

更安全

「JWT 做了密码学上加密,所以更安全。」其实,JWT 中只涉及到了 Base64 编码和签名,并没有涉及到加密,它只能保证数据不被篡改,而不能用来加密数据。从这点上来讲,JWT 中包含的数据和签名的 Cookie 中包含的数据的处境相同 —— 防篡改。

「浏览器请求资源时,总会携带Cookie 中的相应的数据,这会有受到 CSRF 攻击的风险。而 JWT 则不会。」文章的开头提到:Cookie 是数据存储机制,JWT 是认证授权机制,这两者并不相关。命题有问题,就不反驳了。

错误问题的正确答案没有意义。

倒不如说「浏览器请求资源时,总会携带Cookie 中的相应的数据,这会有受到 CSRF 攻击的风险。而如果把相关信息存到 HTML Web Storage 里,就不会有这个风险了。」确实,不会有受到 CSRF攻击的风险了。但受 XSS 影响的可能性却增大了—— 浏览器并不能限制 JavaScript 环境中的 API 对 HTML5 Web Storage 的访问,其中保存的信息在 XSS 攻击面前变得唾手可得。

总结:

JWT 存储在开启 HttpOnly 和 Secure 的 Cookie 里,这和Cookie 里存储 Session 信息没什么差别。会受到CSRF 攻击,但不会受 XSS 攻击。

JWT 存储在 HTML5 Web Storage 里,更不安全了。

使用 JWT 并没有更安全。

驳回。

内置过期功能

说实话,这个功能对于 Web 应用来说,没什么用。服务端的设置过期的能力才是更可靠的。如果依赖存储在客户端的 JWT 的过期功能,很多事情都是没法做或是需要付出更多成本的,比如:

重设密码后,强制用户登出,重新登录。如果不借助其他机制来限制 JWT,即使用户登出并重新登录。在原有的 JWT 过期以前,还是能通过原有 JWT 来访问 API 的。

……

驳回。

移动设备上的兼容性更好

移动设备上的主流浏览器、主流移动设备开发框架、严谨的 HTTP 库都有对 Cookie 的良好支持。

「移动设备上的兼容性更好」算是个理由,但是过时了。

驳回。

即使用户禁用 Cookie,应用也能很好的工作

如果用户禁用了 Cookie,通常情况下,他们也会禁用其他持久化存储机制,比如 HTML5 Web Storage。那,JWT 也无法工作了。

驳回。

知道为何禁用 Cookie 的用户,绝大多数都清楚这会影响他们的使用体验。开发者不应该为「用户禁用 Cookie」寻找折中的解决方案,而应该提示用户启用 Cookie,并告知用户为什么要使用 Cookie。

RESTful API 应该是无状态的,所以认证授权也应该是无状态的

用无状态 JWT 实现一个精确统计在线用户数量的功能,再来重新说这个话。

驳回。

JWT 的缺点

将以上的内容,总结总结,可以归纳出几个缺点。另外,再加上几个没提到的缺点。

占用太多空间

上面提到 JWT 不应该被存储在 HTML5 Web Storage 里,那就只能存储在 Cookie 里了。但是,Cookie 的存储容量是有限制的(通常为 4 KB)。

而,JWT 占用的空间又不是那么小,尤其是在使用无状态 JWT 时,所有的数据都被放到 JWT 里,数据大小很快就会超过 Cookie 的容量限制。

无法完全掌控其有效期。

从最初设计上讲,无状态 JWT 不支持撤销。不管发生什么,JWT 会持续有效,直到过期时间。

如果不通过带状态的 JWT 管理机制将 JWT 管理起来,是无法完全掌控其有效期的。

数据实效性差

无状态 JWT 一旦被生成,就不会再和服务端有任何瓜葛。

一旦服务端中的相关数据更新,无状态JWT 中存储的数据由于得不到更新,就变成了过期的数据。

如果 JWT 中存储有权限相关信息,比如当前角色为 admin,但是由于 JWT 所有者滥用自身权利,高级管理员将权利滥用者的角色降为 user。但是由于 JWT 无法实时刷新,必需要等到 JWT 过期,重新登录时,高级管理员的设置才能生效。

实现缺乏真实场景的检验

以上这些缺点都只针对无状态 JWT。如果使用有状态 JWT,就可以规避这些缺点。

不过,有状态 JWT 基本上等同于 Session / Cookie。但,不同于 Session / Cookie,有状态 JWT 很少有经过真实场景检验的实现。

已存在的 Session /Cookie 实现(比如 express-session)已经在生产环境使用了很多很多年,因此,它很多安全方面的问题都得到了解决。但有状态 JWT 的实现却没有这方面的检验。

适合 JWT 的场景

JWT 的最佳用途是「一次性授权 Token」,这种场景下的 Token 的特性如下:

有效期短

只希望被使用一次

真实场景的例子 —— 文件托管服务,由两部分组成:

Web 应用:这是一个可以被用户登录并维持状态的应用,用户在应用中挑选想要下载的文件。

文件下载服务:无状态下载服务,只允许通过密钥下载。

如何把 JWT 用在这个场景中呢?

用户登录到 Web 应用中,挑选好想要下载的文件,点击下载。

认证服务颁发包含下载信息的、具有较短过期时间的 JWT。JWT 中包含的信息可以是这样的:

{  "file":"/books/我这一辈子.pdf",  "exp": 1500719759621}

使用 JWT 从文件下载服务下载文件。

如果觉得这个例子不贴切,就当我随意写了一个吧。

总结

无状态 JWT

不能被撤销或更新

存储在 Cookie 中,会面临存储空间受限的问题

存储在 HTML5 WebStorage 中,会面临 XSS 攻击的问题

有状态 JWT

与 Session / Cookie 类似,但缺乏真实场景的检验

不要用 JWT 来做 Web 应用的会话管理,请用 Session / Cookie。

 

 

 

使用 JWT 来作 Web 应用的会话管理

上篇文章推荐使用 Session / Cookie 来做会话管理。

如果你已经上了 JWT 的贼船,一时还下不来,就不得不对它修修补补了。

声明

如未注明 JWT 有无状态,以下 JWT 特指无状态 JWT。

使用 Cookie 存储 JWT

HTML5 Security Cheat Sheet 中并不推荐使用 HTML5 WebStorage 来存储 JWT。因为浏览器中的JavaScript 环境不能限制其 API 对 HTML5Web Storage 的访问。存储在其中的敏感数据 XSS 攻击面前变得唾手可得。

那……是不是保证不被 XSS 就能用 HTML5 Web Storage 来存储 JWT 了?理论上是的,但是 XSS 是很难被彻底防御的。通常的方法是「转义所有不可信数据」。但是,截至目前(2017),很多 Web 应用都开始引用 CDN 上托管的 JS 文件、使用 NPM / Bower 等包管理器提供的第三方库、市场分析或广告 JS,你可以保证所有这些都是安全可靠的吗?很难,即使能,也要耗费大量精力做审查。

所以,老老实实地用 Cookie 吧。

使用 Cookie 存储 JWT 时,需要为 Cookie 启用 HttpOnly 和 Secure

HttpOnly:禁止浏览器的 JavaScript 环境访问Cookie,防御针对 Cookie 的 XSS。

Secure:Cookie 只在 HTTPS 请求中被传输。

按照上面设置 Cookie 的话,浏览器的JavaScript 环境不能访问 Cookie,可是如果要使用 JWT Claims 中包含的信息该怎么办?

在客户端登录时,服务端将包含 JWT 的 Set-Cookie 头与 JWT Claims 一并返回,客户端在设置 Cookie 时,同时将 JWT Claims 存储在 HTML5 Web Storage 中。之后每次更新 JWT 时,一并更新 Web Storage 中的 JWT Claims 就可以了。

这样,有效的 JWT 存储在了安全的Cookie 中,用于认证授权;JWT Claims 包含的信息存储在了 HTML5 Web Storage 中,提供给 JavaScript 环境。

虽然 Cookie 让 JWT 幸免于 XSS,但还有 CSRF 需要防御。

补丁 1:JWT 的续期

轮循续期

方式:按照指定时间间隔,通过现有 JWT 获取新的 JWT。

将 JWT 的有效时间设置得较长。比如一周。

用户登录应用,通过认证,获取 JWT

用户使用应用的期间,每隔一段时间刷新 JWT。比如 10 分钟。

在用户关闭应用,又重新打开时,检查 JWT 存在性与有效性:

如果 JWT 存在且有效,马上刷新 JWT。(为什么要马上刷新 JWT,请参看情境 1)

如果 JWT 不存在或失效,需重新登录。

情境1:如果用户每天都使用应用,但是每次都只使用 5 分钟,这样就等不到没 10 分钟进行一次的 JWT 刷新。用户这样用了一周,它的 JWT 还是一周前获取的。这样就没有达到续期的效果。

定时续期

方式:在 JWT 临近过期的时间点,使用现有JWT 请求新的 JWT。

与上一方法类似,但需要处理更多边界条件,而且续期效果不佳。

还有更好的办法吗?

如果有,欢迎讨论。

补丁 2:修改密码后 JWT 验证

出于安全考虑,在用户修改密码后,修改密码前签发的 JWT 应该不能再被继续使用了。但是 JWT 是无状态的,这种需求要怎么实现呢?

在 JWT 中添加签发时间,通过比对签发时间和密码修改时间来判断 JWT 是不是能被继续使用:

JWT 签发时间 > 密码修改时间,JWT 有效

JWT 签发时间 <= 密码修改时间,JWT 无效

这种方法不需要在服务器上保存 JWT 状态,算是比较优雅的一种做法了。

补丁 3:单点登录

适合情境:需要单点登录的站点均位于同一域名下。

比如,现在有 4 个站点托管于 4 个域名下:

login.example.com

1.example.com

2.example.com

3.example.com

通过登录 login.example.com 获取包含 JWT,并设置 domain 为顶级域的 cookieSet-Cookie 头形如:

Set-Cookie: jwt=xxx.yyy.zzz; HttpOnly; Secure; max-age=980000; domain=.example.com

这样 example.com 和 *.example.com 就都可以接收到这个 Cookie,也就能使用其中包含的 JWT 了。

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值