《使用python来实现json web tokens加密协议》
这两天是北京很冷,远在南方的广东都在下雪,这个冷可想而知了…
正体开始,我自己在尝试写一个单点登录的小系统,里面权限控制有用到jwt (json web tokens)安全策略,对于jwt,我以前专门写过一篇文章来描述他是怎么一回事, 有兴趣的朋友再瞅瞅. 这次就直接讲解在python下的json web token的实现. 不看rfc协议文档,后面直接看源码更直接一点.
文章写的不是很严谨,欢迎来喷,另外该文后续有更新的,请到原文地址查看更新。
这里额外废话聊聊JWT认证的好处:
跨域: cookies在跨域场景表现并不好。基于Token的方法允许你向任何不同域名的服务器发送Ajax请求.
无状态(或者 服务端可扩展):无须再存储Session,由于Token已经自包含了所有的用户信息。
解耦:无须被绑定在一个特定的验证方案 , 只需要得知是啥加密手法及密钥就可以加密解密。
跨站点脚本攻击:由于没有基于cookie技术,你不再需要考虑跨站点请求的安全性问题。
性能:HMACSH256算法的速度还是很利索的. 在前端下具体的表现性能我没有测过.
JWT格式
JWT是一段被base64url编码过的字符序列,并用点号分隔。它由三部分组成,头部header、载荷playload与签名sign。
另外,Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
下面是一个JWT头部 Header. (这里是typ,不是type… 前些日子我测试jwt协议时遇到这个小坑.)
{
“typ”: “JWT”,
“alg”: “HS256”
}
下面是一个JWT的消息体 Playload
{
“blog”: “xiaorui.cc”
}
然后把header和playload分别使用base64url编码,接着我们用’ . ‘ 把两个编码后的字符串连接起来,再把这拼接起来的字符串配合密钥进行HMAC SHA-256算法加密,最后再次base64编码下,这就就拿到了签名. 最后把header , playload, 签名用’ . ‘ 连接起来就生成了整个JWT .
请多理解下下面这图.
老规矩,通过学习jot代码看jwt是如何实现的…. 大家理解了jwt生成方法就很容易自己构建一个jwt生成器了.
#blog: xiaorui.cc def encode(payload, signer=None, encrypter=None): if signer and encrypter: raise SignAndEncryptError() headers = {'typ': 'JWT', 'alg': 'none'} if signer: #更新你所选用的算法,一般都是hs256 headers.update(signer.headers) if encrypter: headers.update(encrypter.headers) #头部序列化 headers_json = json.dumps(headers, separators=(',', ':')) #消息主体序列化 payload_json = json.dumps(payload, separators=(',', ':')) #使用base64来编码 header_b64 = b64encode(headers_json) payload_b64 = b64encode(payload_json) first_part = header_b64 second_part = payload_b64 third_part = '' if signer: #jwt 签名的生成方式. 他会把header playload的base64url编码加密后再次base64编码. third_part = b64encode(signer.sign(first_part + '.' + second_part)) if encrypter: pass # TODO #返回可用的JWT return first_part + '.' + second_part + '.' + third_part def b64encode(data): return base64.urlsafe_b64encode(data).rstrip('=') #使用hmac来加密 class HmacSha(JwsBase): def sign(self, signing_input, key=None): if not key: key = self.key if not key: raise KeyRequiredException() return hmac.new(key, signing_input, self.digestmod).digest()
那么如何验证这个签名是否有效,也就是没有被篡改过? 他的原理也很简单,就是拆分这个JWT,用’ . ‘来拆分出Header , Playload, Sign,然后用base64 decode解码。 第一是判断你根据密钥针对数据加密是否拿到的是同一个签名,第二是判断签名是否过期.
我这里看的是python jot这个库,貌似关注该项目的人不是很多.
#blog: xiaorui.cc In [1]: from jot import jwt In [2]: msg = jwt.encode({'status': 'ready'}) In [3]: msg Out[3]: 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdGF0dXMiOiJyZWFkeSJ9.' In [4]: jwt.decode(msg) Out[4]: {'headers': {u'alg': u'none', u'typ': u'JWT'}, 'payload': {u'status': u'ready'}, 'valid': True} In [5]: In [5]: from jot import jwt, jws In [6]: msg = jwt.encode({'status': 'ready'}, signer=jws.HmacSha( ...: bits=256, key='verysecret', key_id='client1')) In [7]: msg Out[7]: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImNsaWVudDEifQ.eyJzdGF0dXMiOiJyZWFkeSJ9.DcKKQXXUjGP7pape8BgQ3AcQSPH8toWFLY2woIVUZ-w' In [8]: jwt.decode(msg, signers=[jws.HmacSha(bits=256, key='verysecret')]) Out[8]: {'headers': {u'alg': u'HS256', u'kid': u'client1', u'typ': u'JWT'}, 'payload': {u'status': u'ready'}, 'valid': True}
明确的说一点.
payload 的值并没有被加密,就算不知道 secert_key 的值也可以得到 payload 的值。那么签名里的secert_key的意思在于校验是否被篡改数据。
Json Wen Token的实现本来就没啥难度,也就没啥太深聊的了. 需要再次说明的是JWT不能保护数据,但是能防串改,这意思是说,我抓到你非https的jwt包,我是可以看到Header及Playload消息信息的, 如果我抓到了你cookie里的密钥的话,也是可以伪造JWT的。 但话说回来,如果我能嗅觉你的包,你用啥加密也防不住. 还是强烈大家上HTTPS,还是能挡住一些手段的.