什么是JWT 根据维基百科的定义,JSON WEB Token(JWT,读作 [/dʒɒt/]),是一种基于JSON的、用于在网络上声明某种主张的令牌(token)。JWT通常由三部分组成: 头信息(header), 消息体(payload)和签名(signature)。 头信息指定了该JWT使用的签名算法: header = '{'alg':'HS256','typ':'JWT'}' HS256 表示使用了 HMAC-SHA256 来生成签名。 消息体包含了JWT的意图: payload = '{'loggedInAs':'admin','iat':1422779638}'//iat表示令牌生成的时间 未签名的令牌由base64url编码的头信息和消息体拼接而成(使用”.”分隔),签名则通过私有的key计算而成: key = 'secretkey' unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload) signature = HMAC-SHA256(key, unsignedToken) 最后在未签名的令牌尾部拼接上base64url编码的签名(同样使用”.”分隔)就是JWT了: token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature) # token看起来像这样: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI JWT常常被用作保护服务端的资源(resource),客户端通常将JWT通过HTTP的Authorizationheader发送给服务端,服务端使用自己保存的key计算、验证签名以判断该JWT是否可信: 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),在一些Framework的帮助下(比如spring-session和hazelcast),session复制也并没有想象中的麻烦。所以除非你的应用访问量非常非常非常(此处省略N个非常)大,使用cookie-session配合外部session存储完全够用了。 2.该方案可防护CSRF攻击 跨站请求伪造Cross-site request forgery(简称CSRF, 读作 [sea-surf])是一种典型的利用cookie-session漏洞的攻击,这里借用spring-security的一个例子来解释CSRF: 假设你经常使用bank.进行网上转账,在你提交转账请求时bank.的前端代码会提交一个HTTP请求: POST /transfer HTTP/1.1Host: bank.cookie: JsessionID=randomid; Domain=bank.; Secure; HttpOnlyContent-Type: application/x-www-form-urlencodedamount=100.00&routingNumber=1234&account=9876 你图方便没有登出bank.,随后又访问了一个恶意网站,该网站的HTML页面包含了这样一个表单: <form action='https://bank./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.发起请求时,你的cookie会被自动发送过去。 因此,有些人认为前端代码将JWT通过HTTP header发送给服务端(而不是通过cookie自动发送)可以有效防护CSRF。在这种方案中,服务端代码在完成认证后,会在HTTP response的header中返回JWT,前端代码将该JWT存放到Local Storage里待用,或是服务端直接在cookie中保存HttpOnly=false的JWT。 在向服务端发起请求时,用Javascript取出JWT(否则前端Javascript代码无权从cookie中获取数据),再通过header发送回服务端通过认证。由于恶意网站的代码无法获取bank.的cookie/Local Storage中的JWT,这种方式确实能防护CSRF,但将JWT保存在cookie/Local Storage中可能会给另一种攻击可乘之机,我们一会详细讨论它:跨站脚本攻击——XSS。 3.该方案更安全 由于JWT要求有一个秘钥,还有一个算法,生成的令牌看上去不可读,不少人误认为该令牌是被加密的。但实际上秘钥和算法是用来生成签名的,令牌本身不可读仅是因为base64url编码,可以直接解码,所以如果JWT中如果保存了敏感的信息,相对cookie-session将数据放在服务端来说,更不安全。 除了以上这些误解外,使用JWT管理session还有如下缺点:
看到这里后,你可能发现,将JWT保存在Local Storage中,并使用JWT来管理session并不是一个好主意,那有没有可能“正确”地使用JWT来管理session呢?比如:
这个方案看上去是挺不错的,恭喜你,你重新发明了cookie-session,可能实现还不一定有现有的好。 那究竟JWT可以用来做什么 我的同事做过一个形象的解释:
放到系统集成的场景中,JWT更适合一次性操作的认证:
在这里,服务A负责认证用户身份(相当于上例中领导批准请假),并颁布一个很短过期时间的JWT给浏览器(相当于上例中的请假单),浏览器(相当于上例中的请假员工)在向服务B的请求中带上该JWT,则服务B(相当于上例中的HR员工)可以通过验证该JWT来判断用户是否有权执行该操作。这样,服务B就成为一个安全的无状态的服务了。 总结
|
|