JWT的原理与隐患

什么是JWT

JWT通常由三部分组成:头信息(header), 消息体(payload)和签名(signature)。

头信息指定了该JWT使用的签名算法

header = '{"alg":"HS256","typ":"JWT"}'

HS256 表示使用了 HMAC-SHA256 来生成签名。

消息体包含了JWT的意图

payload = '{"loggedInAs":"admin","iat":1422779638}'  //iat表示令牌生成的时间

未签名的令牌由base64url编码的头信息和消息体拼接而成(使用”.”分隔),签名则通过私有的key计算而成

最后在未签名的令牌尾部拼接上base64url编码的签名(同样使用”.”分隔)就是JWT了:

JWT常常被用作保护服务端的资源(resource),客户端通常将JWT通过HTTP的Authorization header发送给服务端,服务端使用自己保存的key计算、验证签名以判断该JWT是否可信。

为什么会误用

1.该方案更易于水平扩展

在cookie-session方案中,cookie内仅包含一个session标识符,而诸如用户信息、授权列表等都保存在服务端的session中。如果把session中的认证信息都保存在JWT中,在服务端就没有session存在的必要了。当服务端水平扩展的时候,就不用处理session复制(session replication)/ session黏连(sticky session)或是引入外部session存储了。

从这个角度来说,这个优点确实存在,但实际上外部session存储方案已经非常成熟了(比如Redis),在一些Framework的帮助下(比如 spring-session 和 hazelcase),session复制也并没有想象中的麻烦。所以除非你的应用访问量非常非常非常(此处省略N个非常)大,使用cookie-session配合外部session存储完全够用了。

2.该方案可防护CSRF攻击

跨站请求伪造 Cross-site request forgery(简称CSRF)是一种典型的利用cookie-session漏洞的攻击,这里借用​​​​​​​ spring-security 的一个例子来解释CSRF:

假设你经常使用bank.example.com进行网上转账,在你提交转账请求时bank.example.com的前端代码会提交一个HTTP请求:

POST /transfer HTTP/1.1
Host: bank.example.com
cookie: JsessionID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876

你图方便没有登出bank.example.com,随后又访问了一个恶意网站,该网站的HTML页面包含了这样一个表单:

<form action="https://bank.example.com/transfer" method="post">
    <input type="hidden" name="amount" value="100.00"/>
    <input type="hidden" name="routingNumber" value="evilsRoutingNumber"/>
    <input type="hidden" name="account" value="evilsAccountNumber"/>
    <input type="submit" value="点击就送!"/>
</form>

你被“点击就送”吸引了,当你点了提交按钮时你已经向攻击者的账号转了100元。现实中的攻击可能更隐蔽,恶意网站的页面可能使用Javascript自动完成提交。尽管恶意网站没有办法盗取你的session cookie(从而假冒你的身份),但恶意网站向bank.example.com发起请求时,你的cookie会被自动发送过去。

因此,有些人认为前端代码将JWT通过HTTP header发送给服务端(而不是通过cookie自动发送)可以有效防护CSRF。在这种方案中,服务端代码在完成认证后,会在HTTP response的header中返回JWT,前端代码将该JWT存放到Local Storage里待用,或是服务端直接在cookie中保存HttpOnly=false的JWT。

在向服务端发起请求时,用Javascript取出JWT(否则前端Javascript代码无权从cookie中获取数据),再通过header发送回服务端通过认证。由于恶意网站的代码无法获取bank.example.com的cookie/Local Storage中的JWT,这种方式确实能防护CSRF,但将JWT保存在cookie/Local Storage中可能会给另一种攻击可乘之机,我们一会详细讨论它:跨站脚本攻击——XSS。

3.该方案更安全

由于JWT要求有一个秘钥,还有一个算法,生成的令牌看上去不可读,不少人误认为该令牌是被加密的。但实际上秘钥和算法是用来生成签名的,令牌本身不可读仅是因为base64url编码,可以直接解码,所以如果JWT中如果保存了敏感的信息,相对cookie-session将数据放在服务端来说,更不安全。

除了以上这些误解外,使用JWT管理session还有如下缺点

缺点

更多的空间占用

如果将原存在服务端session中的各类信息都放在JWT中保存在客户端,可能造成JWT占用的空间变大,需要考虑cookie的空间限制等因素,如果放在Local Storage,则可能受到XSS攻击

更不安全

这里是特指将JWT保存在Local Storage中,然后使用Javascript取出后作为HTTP header发送给服务端的方案。在Local Storage中保存敏感信息并不安全,容易受到跨站脚本攻击,跨站脚本(Cross site script,简称xss)是一种“HTML注入”,由于攻击的脚本多数时候是跨域的,所以称之为“跨域脚本”,这些脚本代码可以盗取cookie或是Local Storage中的数据。

无法作废已颁布的令牌

所有的认证信息都在JWT中,由于在服务端没有状态,即使你知道了某个JWT被盗取了,你也没有办法将其作废。在JWT过期之前(你绝对应该设置过期时间),你无能为力

不易应对数据过期

与上一条类似,JWT有点类似缓存,由于无法作废已颁布的令牌,在其过期前,你只能忍受“过期”的数据。

那究竟JWT可以用来做什么?

JWT(其实还有SAML)最适合的应用场景就是“开票”,或者“签字”

放到系统集成的场景中,JWT更适合一次性操作的认证

服务B你好, 服务A告诉我,我可以操作<JWT内容>, 这是我的凭证(即JWT)

在这里,服务A负责认证用户身份(相当于上例中领导批准请假),并颁布一个很短过期时间的JWT给浏览器(相当于上例中的请假单),浏览器(相当于上例中的请假员工)在向服务B的请求中带上该JWT,则服务B(相当于上例中的HR员工)可以通过验证该JWT来判断用户是否有权执行该操作。这样,服务B就成为一个安全的无状态的服务了。

总结

在Web应用中,别再把JWT当做session使用,绝大多数情况下,传统的cookie-session机制工作得更好

JWT适合一次性的命令认证,颁发一个有效期极短的JWT,即使暴露了危险也很小,由于每次操作都会生成新的JWT,因此也没必要保存JWT,真正实现无状态。

  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值