前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >讲真,别再使用JWT了!

讲真,别再使用JWT了!

作者头像
物流IT圈
发布2019-07-16 11:39:26
2.4K0
发布2019-07-16 11:39:26
举报
文章被收录于专栏:物流IT圈

抱歉,当了回标题党。JWT的确有价值,只是它经常被误用。

摘要:

  • 在Web应用中,用JWT代替session并不是个好主意
  • 适合JWT的使用场景

什么是JWT

根据维基百科中定义,JSON WEB TokenJWT)是一种基于JSON的、用于在网络上声明某种主张的令牌(token)。

JWT通常由三部分组成:

头信息(header), 消息体(payload)和签名(signature)。

(1)header指定了该JWT使用的签名算法:

代码语言:javascript
复制
header = '{"alg":"HS256","typ":"JWT"}'

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

(2)消息体用以描述JWT的意图:

代码语言:javascript
复制
payload = '{"loggedInAs":"admin","iat":1422779638}'//iat表示令牌生成的时间

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

代码语言:javascript
复制
key = 'secretkey'  unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload)  
signature = HMAC-SHA256(key, unsignedToken) 

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

代码语言:javascript
复制
token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + 
encodeBase64(signature)# token看起来像这样: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWR
taW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI 

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

代码语言:javascript
复制
Authorization: Bearer eyJhbGci*...<snip>...*yu5CSpyHI

那怎么就误用了呢?

随着近几年来RESTful API开始流行,用HTTP header来传递认证令牌似乎变得理所应当,而对于单页应用(SPA)、前后端分离的架构似乎也正在促成WEB应用放弃拥有悠久历史的cookie-session认证机制,转而使用JWT来管理用户session。支持此方案的人们认为:

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

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

站在这个角度来理解确实算是一个优点,但实际上外部session存储方案已经非常成熟了(如Redis),在框架的帮助下(如spring-session和hazelcast),session复制并没有想象中的麻烦。除非你的应用访问量非常非常非常大,使用cookie-session配合外部session存储完全够用了。

2.该方案可避免CSRF攻击

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

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

代码语言:javascript
复制
POST /transfer HTTP/1.1
Host: bank.example.com
cookie: JsessionID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencodedamount=100.00&routingNumber=1234&account=9876

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

代码语言:javascript
复制
<form action="https://bank.example.com/transfer" method="post">
   <input type="hidden" name="amount" value="10000.00"/>
   <input type="hidden" name="routingNumber" value="evilsRoutingNumber"/>
   <input type="hidden" name="account" value="evilsAccountNumber"/>
   <input type="submit" value="点击送你1个亿!"/></form>

你被“点击送你1个亿”吸引了,于是你点了该按钮,于是你向攻击者的账号送了10000元。现实中的攻击可能一般会更加隐蔽,恶意网站的页面可能使用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要求有一个秘钥,还有对应的算法,生成的令牌看上去不可读,不少人误认为该令牌是被加密的。但实际上秘钥和算法是用来生成签名的,令牌本身不可读是因为进行了base64编码。但是base64编码是可以直接进行解码的。如果JWT中如果保存了敏感的信息,相对于cookie-session将数据存储在服务端来说,更不安全。

除了以上的误解外,使用JWT代替cookie-session还有如下缺点:

  1. 更多的空间占用。如果将原存在服务端session中的信息都放在JWT中保存,会造成JWT占用的空间变大,需要考虑客户端cookie的空间限制等因素,如果放在Local Storage,则可能会受到XSS攻击。
  2. 更不安全。如果将JWT保存在Local Storage中,然后通过Javascript取出后作为HTTP header发送给服务端,则容易受到跨站脚本攻击。跨站脚本(Cross site script,简称XSS)是一种“HTML注入”,由于攻击的脚本多数时候是跨域的,所以称之为“跨域脚本”,这些脚本代码可以盗取cookie或是Local Storage中的数据。具体可以查看XSS攻击的相关文章。
  3. 无法作废已颁布的令牌。所有的认证信息都在JWT中,由于在服务端没有状态,即使知道了某个JWT可能被盗取了,也没有办法将其作废。在JWT过期之前(一般都会给设置过期时间),你无能为力。
  4. 不易应对数据过期。与3类似,在这种应用下JWT有点类似缓存,由于无法作废已颁布的令牌,在其过期前,只能忍受“过期”的数据。

JWT究竟适合用来做什么?

JWT(其实还有SAML)最适合的应用场景就是“开票”,或者说“签字”。 在有纸化办公的场景下,多个部门、多个组织之间的协同工作往往都需要拿着A部门领导的“签字”或者“盖章”去B部门“使用”或者“访问”对应的受管控的资源,其实这种“领导签字/盖章”和JWT是相似的,都是一种由具备权限的授权者“签发”并“授权”的“票据”。这种"票据"通畅具有可验证性(领导的签名/盖章可以被验证,且难于模仿)和不可篡改性(涂改过的文件不被接受);并且这种"票据"通常是“一次性”的,在访问到对应的资源后,该票据一般会被收走留底,用于后续的审计、追溯等用途。 举两个例子:

  1. 员工张三需要请假一天,填写了请假申请单,张三在获得其部门领导签字后,将请假单交给HR,HR确认领导签字无误后,将请假单收回,并在请假记录表中做相应记录。
  2. 员工李四因公外出需要使用公务用车,于是填写用车申请单,部门领导签字后李四将申请单交给公务车司机老王,老王驾驶车辆外出办事,同时将用车申请单收回并存档。

在以上的两个例子中,“请假申请单”和“用车申请单”就是JWT中的payload,领导签字就是base64后的数字签名,领导是issuer,“HR部门的韩梅梅”和“司机老王”即为JWT的audience,audience需要验证领导签名是否合法,验证合法后根据payload中请求的资源给予相应的权限,同时将JWT收回。

放到一些系统集成的应用场景中,其实JWT更适合一次性操作的认证:

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

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

总结

  1. 在Web应用中,别再把JWT当做session使用,在绝大多数场景下,传统的cookie-session机制工作得更好;
  2. JWT适合一次性的安全认证,颁发一个有效期极短的JWT,即使暴露了危险也很小。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-12-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 驼马精英 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 抱歉,当了回标题党。JWT的确有价值,只是它经常被误用。
  • 摘要:
  • 什么是JWT
  • 那怎么就误用了呢?
    • 1.该方案更易于水平扩展
      • 2.该方案可避免CSRF攻击
        • 3.该方案更安全
        • JWT究竟适合用来做什么?
        • 总结
        相关产品与服务
        云数据库 Redis
        腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档