Django前后端分离1——jwt

一.前戏

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 非对称加密

  1. 加密: 公钥加密,私钥解密
  2. 签名: 私钥签名, 公钥验签

二.JWT:json web token

1.组成

  1. header:格式为字典,该部分数据需要转成json串并用base64 加密
{'alg':'HS256', 'typ':'JWT'}
# alg代表要使用的 算法
# typ表明该token的类别 - 此处必须为 大写的 JWT
  1. 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',
}
  1. 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校验规则

  1. 解析header, 确认alg
  2. 签名校验:
    根据传过来的header和payload按 alg指明的算法进行签名(签名时需要用到服务端自定义的密钥),将签名结果和传过来的sign进行对比,若对比一致,则校验通过
  3. 获取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.用户执行注册/登录

  1. 一旦基础数据校验成功,后端生成token, 并且token包含此次注册/登录用户的用户名;并通过response返回给前端[json]
  2. 前端拿到response 的token后,将token存入到浏览器本地存储 ;方法如右:window.localStorage.setItem(‘dnblog’, token)

3.用户每次访问应用

  1. 从本地存储中拿出token ,window.localStorage.getItem(‘dnblog’)
  2. JS 将 token 放入 request 的 Authorization 头,发送http 请求向后端索要数据
  3. 服务器-接到前端请求【当前URL 加了 loging_check,并且请求方法在 methods参数中】:ex: loging_check(‘POST’), 则当前URL POST方法时进行如下校验
    ​ 1,从 request Authorization 头 拿出token,
    ​ 2,校验
    ​ 3,校验不通过,返回前端异常码
    ​ 4,校验通过,正常执行对应URL的视图函数
  4. 前端一旦接到 关于token的 异常码, 则删除本地存储中的token;
    ​ 且将用户转至登录界面

token解决了前后端分离时的登陆状态问题,但不能解决安全问题,安全问题需要从协议层如https来解决

发布了106 篇原创文章 · 获赞 8 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_43667990/article/details/100713421
今日推荐