一.前戏
1.base64
方法 | 作用 | 参数 | 返回值 |
---|---|---|---|
b64encode | 将输入的参数转化为base64规则的串 | 预加密的明文,类型为bytes;例:b‘guoxiaonao’ | base64对应编码的密文,类型为bytes;例:b’Z3VveGlhb25hbw==’ |
b64decode | 将base64串 解密回 明文 | base64密文,类型为bytes;例:b’Z3VveGlhb25hbw==’ | 参数对应的明文,类型为bytes;例:b’guoxiaonao’ |
urlsafe_b64encode | 作用同b64encode,但是会将 ‘+‘替换成 ‘-’,将’/‘替换成’_’ | 同b64encode | 同b64encode |
urlsafe_b64decode | 作用同b64decode | 同b64decode | 同b64decode |
import base64
s = b'guoxiaonao'
b_s = base64.b64encode(s) # b_s打印结果为 b'Z3VveGlhb25hbw=='
#base64解密
ss = base64.b64decode(b_s) # ss打印结果为 b'guoxiaonao'
2.SHA-256 安全散列算法的一种(hash)
import hashlib
s = hashlib.sha256() # 创建sha256对象
s.update(b'xxxx') # 添加欲hash的内容,类型为 bytes
s.digest() # 获取加密后的摘要
3.HMAC-SHA256 是一种通过特别计算方式产生的消息认证码,使用散列算法同时结合一个加密密钥。它可以用来保证数据的完整性,同时可以用来作某个消息的身份验证
import hmac
# 生成hmac对象
# 第一个参数为加密密钥key,bytes类型,
# 第二个参数为欲加密的串,bytes类型
# 第三个参数为hmac的算法,指定为SHA256
h = hmac.new(key, str, digestmod='SHA256 ')
h.digest() #获取最终结果
4.RSA256 非对称加密
- 加密: 公钥加密,私钥解密
- 签名: 私钥签名, 公钥验签
二.JWT:json web token
1.组成
- header:格式为字典,该部分数据需要转成json串并用base64 加密
{'alg':'HS256', 'typ':'JWT'}
# alg代表要使用的 算法
# typ表明该token的类别 - 此处必须为 大写的 JWT
- payload:格式为字典-内部存有 公有声明 和 私有声明
公共声明:可选项,根据自己需求 按需添加,JWT提供了内置关键字用于描述常见的问题
私有声明:根据自己业务需求
公共声明和私有声明均在同一个字典中;转成json串并用base64加密
{
# 常见的公有声明
'exp':xxx, # Expiration Time 此token的过期时间的时间戳
'iss':xxx,# (Issuer) Claim 指明此token的签发者
'aud':xxx, # (Audience) Claim 指明此token签发面向群体,安卓,ios,浏览器等
'iat':xxx, # (Issued At) Claim 指明此创建时间的时间戳
# 私有申明按业务需求添加,如:
'username': 'kzzf',
}
- signature 签名
签名规则:根据header中的alg确定 具体算法,以下用 HS256为例
用自定义的密钥key, 对base64后的header + ‘.’ + base64后的payload进行hmac计算
HS256(key , base64后的header + ‘.’ + base64后的payload)
2.jwt的最终面貌
base64(header) + ‘.’ + base64(payload) + ‘.’ + base64(sign)
b’eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Imd1b3hpYW9uYW8iLCJpc3MiOiJnZ2cifQ.Zzg1u55DCBqPRGf9z3-NAn4kbA-MJN83SxyLFfc5mmM’
3.jwt校验规则
- 解析header, 确认alg
- 签名校验:
根据传过来的header和payload按 alg指明的算法进行签名(签名时需要用到服务端自定义的密钥),将签名结果和传过来的sign进行对比,若对比一致,则校验通过 - 获取payload自定义内容
三.手写JWT的生成和验证
import base64, hmac, json, time
import copy
# 自定义类,提供jwt的加密和接密接口
class Jwt:
def __init__(self):
pass
@staticmethod
def encode(payload, key, exp=300):
# 创建header
header = {"alg": "HS256", "typ": "JWT"}
# separators参数:生成紧凑型的json字符串(原字典中如果有空格的话,转成json后默认是会保留的)
# 指明json字符串中 每个键值对 以及 key和value之间 用什么相连
# sort_key:json字符串按key排序输出,保证每次转换之后得到相同的字符串(因为字典是无序的)
header_j = json.dumps(header, separators=(',', ':'), sort_keys=True)
# 使用自定义的b64encode方法进行加密
header_bs = Jwt.b64encode(header_j.encode())
# 创建payload
payload = copy.deepcopy(payload)
# 设置过期时间标记
payload['exp'] = int(time.time()) + exp
payload_j = json.dumps(payload, separators=(',', ':'), sort_keys=True)
# 使用python提供的base64方法加密
payload_bs = base64.urlsafe_b64encode(payload_j.encode())
# 生成sign 预签名串
to_sign_str = header_bs + b'.' + payload_bs
if isinstance(key, str):
key = key.encode()
# 创建加密对象,hamc new 中的参数需要用bytes格式
hmac_obj = hmac.new(key, to_sign_str, digestmod='SHA256')
# 获得签名的摘要(结果)
sign = hmac_obj.digest()
sign_bs = Jwt.b64encode(sign)
return header_bs + b'.' + payload_bs + b'.' + sign_bs
# jwt解密方法,返回payload
@staticmethod
def decode(token, key):
'''
校验token
:param token:
:param key:
:return:
'''
header_bs, payload_bs, sign = token.split(b'.')
if isinstance(key, str):
key = key.encode()
# 重新计算签名
hmac_obj = hmac.new(key, header_bs+b'.'+payload_bs, digestmod='SHA256')
new_sign = Jwt.b64encode(hmac_obj.digest())
if sign != new_sign:
# 当前传过来的token违法,raise异常,由外部进行捕获
raise JwtError('token不合法')
# token验证合法,判断是否过期
# 将base64的payload解码为json串,这里得到的是bytes格式
payload_j = Jwt.b64decode(payload_bs)
print(type(payload_j))
payload = json.loads(payload_j)
# 拿到过期时间
exp = payload['exp']
now = time.time()
# 对比是否过期
if now > exp:
raise JwtError('token已过期')
return payload
@staticmethod
def b64encode(s):
# 原生base64编码:每3个字符进行编码,最终可能出现等号,此处将等号替换掉,解码时也要做对应的处理
return base64.urlsafe_b64encode(s).replace(b'=', b'')
@staticmethod
def b64decode(bs):
# 将编码时=替换为''的进行恢复,补足长度
rem = len(bs) % 4
bs += b'=' * (4-rem)
return base64.urlsafe_b64decode(bs)
# 自定义jwt异常类
class JwtError(Exception):
def __init__(self, error_msg):
self.error = error_msg
def __str__(self):
return '<JwtError error %s>' % self.error
if __name__ == '__main__':
res = Jwt.encode({'username':'kzzf'}, 'abcd1234')
print(Jwt.decode(res, 'abcd1234'))
四.pyjwt
pip3 install pyjwt
方法 | 参数说明 | 返回值 |
---|---|---|
encode(payload, key, algorithm) | payload: jwt三大组成中的payload,需要组成字典,按需添加公有声明和私有声明 例如: {‘username’: ‘guoxiaonao’, ‘exp’: 1562475112} 参数类型: dict | token串 返回类型:bytes |
key : 自定义的加密key 参数类型:str | ||
algorithm: 需要使用的加密算法[HS256, RSA256等] 参数类型:str | ||
decode(token,key,algorithm,) | token: token串 参数类型: bytes | payload明文 返回类型:dict |
key : 自定义的加密key ,需要跟encode中的key保持一致 参数类型:str | ||
algorithm: 同encode | ||
issuer: 发布者,若encode payload中添加 ‘iss’ 字段,则可针对该字段校验 参数类型:str | 若iss校验失败,则抛出jwt.InvalidIssuerError | |
audience:签发的受众群体,若encode payload中添加’aud’字段,则可针对该字段校验 参数类型:str | 若aud校验失败,则抛出jwt.InvalidAudienceError |
PS: 若encode的时候 payload中添加了exp字段,则exp字段的值需为 当前时间戳+此token得有效期时间, 例如希望token 300秒后过期 {‘exp’: time.time() + 300}; 在执行decode时,若检查到exp字段,且token过期,则抛出jwt.ExpiredSignatureError
五.客户端tokne的生命周期
1.用户未登录:前端 肯定是没有token的
2.用户执行注册/登录
- 一旦基础数据校验成功,后端生成token, 并且token包含此次注册/登录用户的用户名;并通过response返回给前端[json]
- 前端拿到response 的token后,将token存入到浏览器本地存储 ;方法如右:window.localStorage.setItem(‘dnblog’, token)
3.用户每次访问应用
- 从本地存储中拿出token ,window.localStorage.getItem(‘dnblog’)
- JS 将 token 放入 request 的 Authorization 头,发送http 请求向后端索要数据
- 服务器-接到前端请求【当前URL 加了 loging_check,并且请求方法在 methods参数中】:ex: loging_check(‘POST’), 则当前URL POST方法时进行如下校验
1,从 request Authorization 头 拿出token,
2,校验
3,校验不通过,返回前端异常码
4,校验通过,正常执行对应URL的视图函数 - 前端一旦接到 关于token的 异常码, 则删除本地存储中的token;
且将用户转至登录界面
token解决了前后端分离时的登陆状态问题,但不能解决安全问题,安全问题需要从协议层如https来解决