JWT 全称: json-web-token
JWT的大白话解释:
现在比较火的token中的一种,为了解决HTTP协议无状态的问题,开发出来的。就是一种解决方案。
1. 三大组成
- header
在Python来看就是一个字典格式,元数据如下:
{'alg':'HS256', 'typ':'JWT'}
# alg代表要使用的 算法 HMAC-SHA256 简写HS256
# typ表明该token的类别 此处必须为 大写的 JWT
该部分数据需要转换成json串并用base64转码
- payload
在cookie和session中会将用户id或名字写入到其中,在token中会将其写在payload中。
格式为字典-此部分分为公有声明和私有声明
公有声明: JWT提供了内置关键字用于描述常见的问题
此部分均为可选项,用户根据自己需求 按需添加key,常见公共声明如下:
{'exp':xxx, # EXpiration Time 此token的过期时间的时间戳 time.time()+300s 给一个未来过期时间
'iss':xxx, # (issuer) Claim 指明此token的签发者 是那台机器签发的token (当前项目没用)
'aud':xxx, # (Audience) Claim 指明此token的签发群体 token签发面向群体是那些人 区分pc,ios,android (当前项目没用)
'iat':xxx, # (ISSued At) Claim 指明此创建时间的时间戳
# 以上四项是我们的公有声明 保留字
# 下边私有声明
'username':'xxx',
}
私有声明: 用户可根据自己业务需求,添加自定义的key,
公有声明和私有声明均在同一个字典中;转成json串并用bsase64转码
- Signature 签名
签名规则如下:
根据header中的alg确定具体算法,以下用HS256为例:
HS256(自定义的key,base64后的header + b’.‘ + base64后的payload,digestmod=‘SHA256’)
解释:用自定义的key,对base64后的header + b’.’ + base64后的payload进行hmac计算。
2. jwt结果格式
base64(header) + b'.' + base64(payload) + b'.' + base64(Signature)
注意:
这里base转码需要使用base64.urlsafe_b64encode(b"key")
还需要将base转码后的“=”删除,浪费带宽
最终结果: 是一个字节码
b'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDU
wODI5NTQuMTI0NDUxMiwibmFtZSI6ImxkeXQifQ==.5+0x
IGN43F# bTRbBMVzarms8UB1tElRzrRwycSNEJNyk='
3. 校验jwt规则
- 解析header,确认alg使用的算法
- 签名校验-根据传过来的header和payload按 alg指明的算法进行签名,将签名结果和传过来的sign进行对比,若比对一致,则校验通过
- 获取payload自定义内容
JWT和cookie、session相比:
资源
JWT整个过程中除了一个自定义的加密key外没有任何存储的东西,都是计算。
session要将很大的数据存储在服务器中,太浪费资源。
安全
JWT来源性能校验,证明这个东西很难被伪造。具备一定的转码技术,可以让这个token不那么的明文化。
下边是JWT代码的实现和jwt源码基本上一样的,抛离了加减密,jwt主体部分高度一致:
import base64
import hmac
import time
import json
import copy
class Jwt():
@staticmethod # 静态方法的装饰器封装一下 专门负责做计算用的函数
def encode(self_payload, key, exp=300):
# self_payload 含有私有声明的字典
# key 自定的key
# exp 过期时间
# 生成header
header = {'typ': 'JWT', 'alg': 'HS256'}
# header_json = json.dumps(header) # 这样转为json串不行,有空格,损耗带宽
header_json = json.dumps(header, separators=(',', ':'), sort_keys=True)
# 这样逗号冒号前后就没有空格了,sort_keys=True 使出来的json串变的有序了,在做hmac或其他哈希的计算的时候,串值一定是稳定的
# separators分割符 第一个参数代表的是每个键值对之间用什么分割,第二个参数是每个键和值之间用什么分割
# sort_keys 生成有序的json串
header_json_base64 = Jwt.b64encode(header_json.encode())
# init payload
self_payload_copy = copy.deepcopy(self_payload) # 为了不污染传进来的字典
# 给拷贝出来的字典中加入公有声明
self_payload_copy["exp"] = time.time() + exp # 过期时间
self_payload_copy_json = json.dumps(self_payload_copy, separators=(',', ':'), sort_keys=True)
self_payload_copy_json_base64 = Jwt.b64encode(self_payload_copy_json.encode())
# init sign
hm = hmac.new(key.encode(), header_json_base64 + b'.' + self_payload_copy_json_base64,
digestmod="SHA256") # 两个都是字节码所以连接符*点*也要是字节码
hm_base64 = Jwt.b64encode(hm.digest()) # 取hm的二进制结果,然后进行base64的转码
# jwt token 诞生 字节码
return header_json_base64 + b'.' + self_payload_copy_json_base64 + b'.' + hm_base64
@staticmethod
def b64encode(js): # 为了将base64转换修改为urlsafe
return base64.urlsafe_b64encode(js).replace(b"=", b"")
@staticmethod
def b64decode(bs):
# 加回来等号
rem = len(bs) % 4 # 取余
if rem > 0:
bs += b'=' * (4 - rem)
return base64.urlsafe_b64decode(bs)
@staticmethod
def decode(token, key):
# 传入jwt的值(令牌) 和只有调用者知道的key
# 校验签名
header_bs, payload_bs, signature_bs = token.split(b".") # 因为是字节串
hm = hmac.new(key.encode(), header_bs + b"." + payload_bs, digestmod="SHA256")
if signature_bs != Jwt.b64encode(hm.digest()): # 将签名结果和传过来的sign进行对比
raise
# 校验时间
payload_js = Jwt.b64decode(payload_bs) # 解码为json
payload = json.loads(payload_js) # 解码为字典
now = time.time() # 当前时间
if int(now) > int(payload["exp"]): # 登录时间过期
raise
return payload # 返回自定义内容
if __name__ == '__main__':
# 测试
s = Jwt.encode({"name": "lyt"}, "1234567", 300) # 制作令牌
print(Jwt.decode(s, "1234567")) # 校验令牌 返回payload明文 字典类型
通过上面的代码,就可以自己写出一个能保持HTTP状态的一个token令牌啦。
如果要测试自己生成的的token令牌是否相对规范和严谨可以到https://jwt.io/
自己检查奥!
分隔符-----------------------------------------------------------------------------------------------------------------------------------------嘿嘿
在前后端分离的结构中解决HTTP协议无状态,使用token可以解决。JWT是token中比较火的一种。所以我们要掌握好哟!!!
少年易老学难成,一寸光阴不可轻。