一、JWT 介绍
先看一个字符串:
‘eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxODAwMSwidXNlcl9yb2xlIjoiYWRtaW4iLCJleHAiOjE1NjkwNzQ2ODIsIm5iZiI6MTM3MTcyMDkzOSwiaXNzIjoiYmlnX2Jvc3MifQ.8RiaABi35CCHqIg1acdav09Q6-V7jhxguzPPhns9liE’
上面字符串,中间有两个点分隔:
这个就是JWT的三部分。JSON Web Tokens由三个部分组成,用点(.)分隔,它们是:Header+ Payload +Signature。
其中,
(1)、头部信息主要是算法本身的简介。
(2)payload:
iss:jwt 签发者;
sub:jwt 所面向的用户;
aud:接收 jwt 的一方;
exp:jwt 的过期时间,这个过期时间必须要大于签发时间,这是一个秒数;
nbf:定义在什么时间之前,该 jwt 都是不可用的;
iat:jwt 的签发时间。
上面的标准中注册的声明中常用的有 exp 和 nbf。
(3)signature:
RS256 :是一种非对称算法, 它使用公共/私钥对: 标识提供方采用私钥生成签名, JWT 的使用方获取公钥以验证签名,私钥解密。
HS256 :是一种对称算法, 双方之间仅共享一个 密钥。优点是解密速度快,缺点是对密钥双方都有,保管风险大一点,此外是一对一。
在pyjwt库中,‘RS256’:即指 RSAAlgorithm(RSAAlgorithm.SHA256),HS256’具体是指 HMACAlgorithm(HMACAlgorithm.SHA256)。
pyjwt库中, 有几个重要的依赖库:
import hashlib
import hmac
import json
从代码中,可以看到,这两种算法参数,在加解密时的不同:
def HS256_payload(payload,key):
key = 'secret_adadadpqeipqreiupqidfak'#这个自己设好,对称性的密钥
encoded = jwt.encode(payload, key, algorithm='HS256')
decoded = jwt.decode(encoded, key, algorithms='HS256') #algorithms多了s
return decoded==payload
def RS256_payload(payload,key):
private_key = b'PRIVATE KEY_k'
public_key = b'PUBLIC KEY_AQY'
# 私钥加密
encoded = jwt.encode(payload, private_key, algorithm='RS256')
# 公钥解密
decoded = jwt.decode(encoded, public_key, algorithms='RS256')#algorithms多了s
return decoded==payload
二、从代码的角度来看JWT
1、token
header ={'alg': 'HS256',
'typ': 'JWT'
}
payload = {'user_id': 18001,
'user_role': 'admin',
'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=30),
'nbf': 1371720939,
'iss': "big_boss"
}
key = 'secret_adadadpqeipqreiupqidfak'#这个自己设好,对称性的密钥
encoded = jwt.encode(payload, key, headers=header,algorithm='HS256')
print("encoded: ",encoded)
这个就是中间带两个点的JWT token。
token = str(encoded,encoding = "utf-8")
‘eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxODAwMSwidXNlcl9yb2xlIjoiYWRtaW4iLCJleHAiOjE1NjkwNzQ2ODIsIm5iZiI6MTM3MTcyMDkzOSwiaXNzIjoiYmlnX2Jvc3MifQ.8RiaABi35CCHqIg1acdav09Q6-V7jhxguzPPhns9liE’
2、base64。
JWT和base64有什么关系? 有,因为,其中首部和payload相应的字符串,都是base64后的结果。什么是base64?
上面资料引自:https://www.jianshu.com/p/570c1acdd236,表示感谢!
3、首部:
import base64
header ={'alg': 'HS256',
'typ': 'JWT'
}
header_byte = bytes(json.dumps(header),encoding='utf-8')
header_encode = base64.b64encode(header_byte) #
header_encode =='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'
base64.b64decode(header_encode) ==header_byte
# b'{"typ":"JWT","alg":"HS256"}'
4、payload:
字符串如下:
eyJ1c2VyX2lkIjoxODAwMSwidXNlcl9yb2xlIjoiYWRtaW4iLCJleHAiOjE1NjkwNTY2MTMsIm5iZiI6MTM3MTcyMDkzOSwiaXNzIjoiYmlnX2Jvc3MifQ
这个如何得来?为什么要加两个“=”? 有意思的是,在解码时,加N个(N>=2)“==”对解码没有影响。一般情况,会多加几个 “=” 。
string ='eyJ1c2VyX2lkIjoxODAwMSwidXNlcl9yb2xlIjoiYWRtaW4iLCJleHAiOjE1NjkwNTY2MTMsIm5iZiI6MTM3MTcyMDkzOSwiaXNzIjoiYmlnX2Jvc3MifQ''
payload_byte = bytes(json.dumps(payload),encoding='utf-8')
payload_encode = base64.b64encode(payload_byte)
print(payload_encode)
str(payload_encode,encoding = "utf-8") == string +'=='
payload_encode:
b’eyJ1c2VyX2lkIjogMTgwMDEsICJ1c2VyX3JvbGUiOiAiYWRtaW4iLCAiZXhwIjogMTU2OTA3NDY4MiwgIm5iZiI6IDEzNzE3MjA5MzksICJpc3MiOiAiYmlnX2Jvc3MifQ==’
5、signature:
是把首部和payload两部分合起来,加密的结果。
header_str = str(header_encode,encoding = "utf-8")
payload_str = str(payload_encode,encoding = "utf-8")[:-2] #去除2个“==”
key = 'secret_adadadpqeipqreiupqidfak'
join_str = header_str+ payload_str + key # 待证实
siginature = (加密函数,HS256)(join_str)
关于pyjwt中encode的原码:
def encode(self,
payload, # type: Union[Dict, bytes]
key, # type: str
algorithm='HS256', # type: str
headers=None, # type: Optional[Dict]
json_encoder=None # type: Optional[Callable]
):
segments = []
if algorithm is None:
algorithm = 'none'
if algorithm not in self._valid_algs:
pass
# Header
header = {'typ': self.header_typ, 'alg': algorithm}
if headers:
self._validate_headers(headers)
header.update(headers)
json_header = force_bytes(
json.dumps(
header,
separators=(',', ':'),
cls=json_encoder
)
)
segments.append(base64url_encode(json_header))
segments.append(base64url_encode(payload))
# Segments
signing_input = b'.'.join(segments)
try:
alg_obj = self._algorithms[algorithm]
key = alg_obj.prepare_key(key)
signature = alg_obj.sign(signing_input, key)
except KeyError:
if not has_crypto and algorithm in requires_cryptography:
raise NotImplementedError(
"Algorithm '%s' could not be found. Do you have cryptography "
"installed?" % algorithm
)
else:
raise NotImplementedError('Algorithm not supported')
segments.append(base64url_encode(signature))
return b'.'.join(segments)
三、验证token
如果是token:
decoded = jwt.decode(encoded, key,headers=header, algorithms='HS256')
如果是token:
decoded = jwt.decode(bytes(token,encoding='utf-8'), key,headers=header, algorithms='HS256')
decoded ==payload
四、验证的时效性
在payload中,
'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=30),
'nbf': 1371720939,
这两项会验证明,如果时间过期,会无法验证。此时,验证自动就会失效,从而起到token自动认证的作用。
1、time()
import jwt
import json
import time as t
nowtime = t.time()
payload ={
"name":"wang",
"phone":"13787878787",
"exp":int(nowtime + 20*1) #60
}
secret ="how are you?"
encoded = jwt.encode(payload,secret,algorithm='HS256')
token =str(encoded,encoding="utf-8")
t0=t.time()
for i in range(10):
t.sleep( 5 )
decoded =jwt.decode(token,secret,algorithm='HS256')
print("i=>:",i," cost time:",t.time()-t0)
print("decode:",decoded)
assert decoded ==payload
2、datetime
改动部分代码,其它一样。
nowtime = dt.datetime.now()
exp_time = nowtime + dt.timedelta(seconds =20)
print("exp_time:",exp_time)
payload ={
"name":"wang",
"phone":"13787878787",
"exp": exp_time
}
结果如下:为什么?
exp_time: 2019-09-24 20:22:31.085716
i=>: 0 cost time: 4.999645709991455
decode: {'name': 'wang', 'phone': '13787878787', 'exp': 1569356551}
i=>: 1 cost time: 10.00132966041565
decode: {'name': 'wang', 'phone': '13787878787', 'exp': 1569356551}
i=>: 2 cost time: 15.001086235046387
decode: {'name': 'wang', 'phone': '13787878787', 'exp': 1569356551}
i=>: 3 cost time: 20.00347375869751
decode: {'name': 'wang', 'phone': '13787878787', 'exp': 1569356551}
i=>: 4 cost time: 25.00635576248169
decode: {'name': 'wang', 'phone': '13787878787', 'exp': 1569356551}
i=>: 5 cost time: 30.009093284606934
decode: {'name': 'wang', 'phone': '13787878787', 'exp': 1569356551}
i=>: 6 cost time: 35.00780892372131
decode: {'name': 'wang', 'phone': '13787878787', 'exp': 1569356551}
i=>: 7 cost time: 40.00885272026062
decode: {'name': 'wang', 'phone': '13787878787', 'exp': 1569356551}
i=>: 8 cost time: 45.008975982666016
decode: {'name': 'wang', 'phone': '13787878787', 'exp': 1569356551}
i=>: 9 cost time: 50.00984859466553
decode: {'name': 'wang', 'phone': '13787878787', 'exp': 1569356551}
问题是,你看到,datetime下的"exp":1569356551
与time方法的:“1569326991”
这个有很不一样。
因为,datetime的时间放在time之前,理论上因datetime的“exp”>time的“exp”;但事实上是相反的。慢了近30000秒!为什么会这样?
time0 =t.time()
time1= dt.datetime.now()
print("time=>time0:",time0)
print("datetime=>time1:",time1)
# datetime
seconds_from_1970= t.mktime(time1.timetuple())
print("datetime=> seconds_from_1970:",seconds_from_1970)
print("diff:",seconds_from_1970 -time0)
因为取整的原因,两者有差:
time=>time0: 1569330254.1146894
datetime=>time1: 2019-09-24 21:04:14.114689
time=>time0: 1569329927.7502277
datetime=> seconds_from_1970: 1569329927.0
diff: -0.750227689743042
综个所述,建议JWT有定时时,用time方法来做,会更好!