引言
近来发现,很多 WEB 应用系统使用 JWT 进行会话管理,原因居然是为了不服务端存储会话,或者追求可自主控制,实不知使用 JWT 进行会话管理有巨大的安全隐患!html
HTTP 会话管理
先说说 HTPP 协议,众所周知, HTTP 协议的特色就是一问一答(一次请求一次响应)。那么基于 HTTP 协议,作个购物网站,要实现用户登陆,添加商品到购物车,最终买单的功能。为了实现以上功能,识别出一系列操做都是同一个用户,最为简单的方式就是每次操做浏览器都发送帐户和密码,这样服务端知道是哪一个用户添加商品到购物车和最终买单。这样看起来很简陋,并且在每次请求中携带帐户和密码将大大增长泄露风险。 git
为了减小帐户密码在请求中携带的次数,咱们引入一个 session-id 的东西。在你携带帐户密码登陆购物网站(第一次请求),服务端验证经过后返回一个 session-id 。此后你添加商品到购物车也好,最终下单也好,反正后续的每次请求都携带好这个 session-id 便可。为何携带 session-id 就能够识别出这是哪一个客户的操做呢?由于服务端将客户的帐户信息和本次会话发送给客户端的 session-id 关联了起来,那么这个session-id 和帐户的关联信息就是 session-data。 github
在以上例子里,session-data 是存储在服务端的。其实,session-data 也能够存储到客户端。那么根据 session-data 存储的位置不一样,分为 Server Side Session 和 Client Side Session 两种模式。web
Server Side Session
Server Side Session 模式是最为常见的,如前面购物网站的例子。在此模式中,客户端(浏览器)将 session-id 存储到 本地 cookies 中。其典型实现以下图所示: 算法
上图中 User A 是个已经创建会话的老用户,他的 Cookies 中存储了 Session_id,值为“AA01”,在请求中携带这个 Session_id,服务端收到后经过 Session_id 查询到 Session Database 中对应的数据,并在程序中使用。New User是个刚开始会话的新用户,他的 Cookies 中尚未 Session_id,因而服务端生成一个新的 session_id 返回给他,并在返回中使用401状态码,告知他重定向到登陆页,进行登陆。后续若是这个 New User 登陆成功,服务端将会把他的身份信息存储到 Session Table中,并与会话初生成的 session_id 关联,这样后续的请求就如同 User A 这个老用户。json
Client Side Session
Client Side Session 模式你们可能以为有点陌生,其实用的不少。如大部分使用 JWT 进行会话管理的WEB系统,就是 Client Side Session 模式。所谓 Client Side Session 模式就是把用户的 session-data 数据存储在客户端,这样服务端就很是悠闲。其交互流程大体以下图所示: 后端
上图中左侧 User A 是个已经创建会话的老用户,他的 Cookies 中存储了 Session_data,值为当前用户的基本信息,在请求中携带着 Session_data 数据,服务端收到请求后解析出 Session_data 信息,便知道是哪一个用户的请求。右侧的 New User 用户正在登陆创建会话,服务端验证成功后,将生成 session_data 信息,返回给客户端。因为 session_data 数据是存储在客户端,为了防止篡改,服务端会对生成的 session_data 数据进行签名,甚至加密。浏览器
Server Side VS Client Side
前文介绍了 Server Side Session 和 Client Side Session 两种模式各自的原理,那么这两种模式孰优孰劣呢?在对比以前,先定一个基准,就是仅限功能性和安全性,而不是对比两种模式的原始实现性。安全
Server Side Session
优势:服务器
- 会话数据存储在服务端,不暴露相关信息,安全性高;
- 每次请求只需传递 session-id,减小流量开销;
- 服务端可方便吊销会话,控制同帐户会话并发数等全面的会话策略管理;
缺点:
- 服务端分布式部署时,需增长处理 Session-data 共享。
Client Side Session
优势:
- 分散存储;
缺点:
- 会话数据存储在客户端,且在每次请求中携带,增长泄露风险;
- 每次请求需传递更多数据,增长流量开销;
- 服务端不方便吊销会话,实现各类会话策略管理;
从以上对比能够看出,两种模式在功能性和安全性上的特色恰好相反。Server Side Session 模式优势突出,但为何还存在Client Side Session 模式呢?所谓存在即合理,由于 Client Side Session 模式原始实现简单!Client Side Session 模式服务端无需集中存储 session-data 数据,天然也无需处理 session-data 的查找,并且分散存储在客户端,服务端无需考虑分布式下的 session-data 共享。 可是 Client Side Session 模式原始实现简单对于使用者来讲并不重要,由于使用者只使用现成的库和框架,只要支持度好就行。因此,Server Side Session 比 Client Side Session 模式更为广泛。
什么是 JWT ?
首先说明 JWT(JWE、JWS)标准只是设计了一个防篡改令牌,并不是是为会话管理而设计。
JSON Web Token(JWT)是一个基于 RFC 7519 的开放数据标准,它定义了一种宽松且紧凑的数据组合方式,使用 JSON 对象在各应用之间传输信息(信息加密即 jwe,签名即 jws)。该 JSON 对象能够经过数字签名进行鉴签和校验,通常地,JWT 能够采用 HMAC 算法,RSA 或者 ECDSA 的公钥/私钥对数据进行签名操做。一个 JWT 一般有 HEADER (头),PAYLOAD (有效载荷)和 SIGNATURE (签名)三个部分组成,三者之间使用“.”连接,格式如上图所示。
JWT 的缺陷
JWT 标准设计看起来很不错,实际上包藏祸心,由于其设计,隐藏诸多安全问题,详细以下:
重置空加密算法缺陷
JWT 支持将算法设定为“None”,若是你使用JWT库时未设置关闭该功能,那么任何Token都是有效的。具体作法是将JWT第一部分 header 中 alg 字段设置为 None ,再将第三部分 signature 设置为空(即不添加signature字段), 此token 可顺利经过验证。
密钥混淆攻击
JWT 最经常使用的两种算法是 HMAC 和 RSA。HMAC(对称加密算法)用同一个密钥对 token 进行签名和认证。而RSA(非对称加密算法)须要两个密钥,先用私钥加密生成 JWT ,而后使用其对应的公钥来解密验证。若是公钥泄露(不少人以为公钥能够分发),那么将算法 RS256 修改成 HS256 ,即非对称加密向降低级为对称加密。再使用这个泄露的公钥对 token 进行签名,后端收到后根据头中指定的算法,将使用公钥对 token 验证,如此便认证经过。
密钥暴力破解
若是 JWT 使用对称加密算法(如 HS256), 这意味着对令牌进行签名的密钥也用于对其进行验证。因为签名验证是一个自包含的过程,所以能够测试 token 自己的有效密钥,而没必要将其发送回应用程序进行验证。若是密钥设置过于简单,如经常使用词汇、生日年份等,结合已知的泄露密码列表,将很快破解出密钥,如此即可伪造出任意token 。
kid 指定攻击
kid 即为 key ID ,存在于 jwt header 中,是一个可选的字段,用来指定加密算法的密钥。如在头部注入新的 kid 字段,并指定 HS256 算法的 key 为 123456,生成新的token,服务端收到将使用指定的密钥123456来验证token。
如何避免 JWT 的缺陷
以上列举的问题中,除了密钥暴力破解,其他皆为 jwt 标准设计引起的缺陷。若是你选择使用 jwt 标准,那么请找一个靠谱的实现库,并进行安全测试。请避免使用对称加密算法,并正确配置安全项,如开启验证 jwt 头部,禁止 alg 设置为 none,禁止密钥降级等安全措施。不过最好的避免方式就是不用 jwt ,改用 paseto ,一个替代 jwt 的新标准。
JWT 进行会话管理的“优势”
首先,如前文所述,JWT 标准并不是是为会话管理而设计,固然 paseto 标准( JWT 标准的替代者)也不是。现现在大多数使用 JWT 进行会话管理其实是 Client Side Session 模式。前文讲到 Client Side Session 模式将会话数据存储到客户端,那么为了防止数据篡改或者泄露,通常对存储在客户端的数据进行签名或加密。不少人就利用 JWT 实现库将会话数据放在 PAYLOAD 区域,而后生成一个token,发送给客户端。客户端若是是网页浏览器,甚至经过 js 将收到的 token 放置在 localStorage 中。以上操做让开发者感受一切尽在掌握之中,却不知集万千漏洞于一身。
使用 JWT 进行会话管理的人声称优势以下:
- 自然支持分布式验证;
- 高并发降低低服务器压力;
- 灵活易用;
- 更安全,可防止CSRF;
- 在移动设备上效果更好;
- 适用于阻止 cookie 的用户。
以上声称的优势其实是 Client Side Session 模式和自主控制 token 带来的。可是我要告诉各位,对于使用者来讲,以上优势所有是伪命题。
自然支持分布式验证
这个特色是真的,然而现实状况是几乎没有人真正须要这个特色。由于 session 共享技术很成熟,现有软件框架都提供了良好的支持方案。
高并发降低低服务器压力
看上去高并发下,Client Side Session 模式没有服务端存储和查询,可下降服务器压力。然而,若是真的是高并发,更应该考虑业务处理对服务器带来的压力,而不是会话存储和查询,由于这根本就是九牛一毛。
灵活易用
这彻底是个错误,自定义使用 JWT 进行会话管理,须要更多的代码配置来处理,何来灵活易用?
更安全,可防止CSRF
前文介绍 JWT 标准,已说明 JWT 存在不少设计问题,更没必要说 Client Side Session 模式带来信息泄露的风险。而对于能够防止CSRF,除非你把 token 放在 cookies 之外区域,如 localStorage 中,使用JS来操做,然而这将带来更大的风险。
在移动设备上效果更好
好久之前,部分移动浏览器存在不支持cookie的状况,但这已是过去式了。如今但凡是个靠谱的 HTTP 库,移动开发框架都支持 cookie,因此这根本不是个问题。
适用于阻止 cookie 的用户
确实有用户会阻止 cookie 的使用,为了不被跟踪。但创建会话就是要求登陆验证身份信息,因此,选择阻止 cookie 使用的用户明白,为何我登陆不了。对于真心想避免跟踪的用户,不只仅会阻止cookie,而是会阻止一切客户端存储。这样,Client Side Session 模式下,会话信息也是无处可存。
若是你偏心 Client Side Session 模式,并且确实以为 Client Side Session 模式能够知足你的需求,那仅需使用服务端框架支持的 Client Side Session 模式实现便可,而没必要去使用 JWT 。
HTTP 会话管理的正确之道
对于安全会话,首先是须要使用 HTTPS,用于保证传输通道的安全。而后使用 Server Side Session 模式的实现,如网页浏览器中的 cookie 存储随机标识符,即 session-id,该 session-id 与服务端存储的 session-data 配对。若是你须要保持更久的会话,可加入一个长期的 remember me id 来实现,而不是随意延长 session-id 的有效期。
有朋友说,JWT 会话管理可经过扩展避免 Client Side Session 模式下的缺点。如吊销会话,服务端可加入黑名单机制,或者服务端记录签发出去的 token,并设置有效期,而后每次请求进行比对。那么问题来了,这样仍是 Client Side Session 模式吗?
补充
有朋友说 JWT 很是简单的实现了单点登陆,一问究竟,我竟无言以对。原来他是指多个系统配置同样的密钥进行token验证,便可实现系统间无缝身份验证。没错,这其实就是 Client Side Session 模式的优势,在特定场景下可实现“单点登陆”的效果。但现实大多数场景下是不可能达成的,由于首先须要各个不一样公司开发的软件系统都采用 JWT 进行会话管理,接着互相信任共享同一个密钥(这是极其不安全的)。如今主流解决单点登陆的协议是OIDC 或者是 OAuth2,再古老些也是 SAML 或 CAS。
有朋友问,既然 JWT 并非为会话管理设计的,那 JWT 应该用来作什么?JWT 适用于一次性令牌,即短有效性,一次性使用。如用于密码找回邮件中的短效一次性连接,或者是文件下载。
参考文献
[1] [EB/OL].http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/.2021-05-04
[2] paseto [EB/OL].https://github.com/paragonie/paseto/.2021-05-04
[3] [EB/OL].https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid.2021-05-04
[4] [EB/OL].https://geekkeen.github.io/http-cookie-session.html.2021-05-04
[5] [EB/OL].https://blog.by24.cn/archives/about-session.html.2021-05-04