python flaskweb开发 data = s.loads(token)原理分析
来源:互联网 发布:sn ty gm js是什么 编辑:程序博客网 时间:2024/05/19 03:23
一, 内容回顾
首先我们来看生成令牌和解码令牌的代码:
from itsdangerous import TimedJSONWebSignatureSerializer
s = TimedJSONWebSignatureSerializer(app.config['SECRET_KEY'], expires_in=3600)
token = s.dumps({'confirm': 23})
data = s.loads(token)
上次我们讲到dumps函数生成的token是 (1) 和 (2)用 ‘.’连接起来的字符串
(1)header字典({‘alg’: 'HS256', 'iat': 当前时间, 'exp': 过期时间})的字符串base64编码 + obj字典({'confirm': 23})的字符串的base64编码 中间用 ‘.’进行分隔 (value)
(2)key(b'itsdangerous' + b'signer' + app.config['SECRECT_KEY'] sha1加密后的字符串)作为键, value(就是(1)) 作为值, 用hashlib.sha256进行加密后的字符串的base64编码
小知识:
python2.x里, b前缀没什么具体意义, 只是为了兼容python3.x的这种写法
python3.x里默认的str是(py2.x里的)unicode, bytes是(py2.x)的str, b”“前缀代表的就是bytes
这里我们用的是python2.x, 所以不用去考虑b
二,这节我们来看data = s.loads(token), 分析loads函数是如何把token还原为data的:
我们首先来看loads函数:
参数s被赋值为token, salt和return_header采用默认值None和False。
def loads(self, s, salt=None, return_header=False): payload, header = JSONWebSignatureSerializer.loads( self, s, salt, return_header=True) if 'exp' not in header: raise BadSignature('Missing expiry date', payload=payload) if not (isinstance(header['exp'], number_types) and header['exp'] > 0): raise BadSignature('expiry date is not an IntDate', payload=payload) if header['exp'] < self.now(): raise SignatureExpired('Signature expired', payload=payload, date_signed=self.get_issue_date(header)) if return_header: return payload, header return payload
(1)TimedJSONWebSignatureSerializer的loads方法中调用了父类的loads方法:
s=token , salt=None, return_header=True
def loads(self, s, salt=None, return_header=False): """Reverse of :meth:`dumps`. If requested via `return_header` it will return a tuple of payload and header. """ payload, header = if header.get('alg') != self.algorithm_name: raise BadHeader('Algorithm mismatch', header=header, payload=payload) if return_header: return payload, header return payload
1. payload, header = self.load_payload(self.make_signer(salt, self.algorithm).unsign(want_bytes(s)), return_header=True)
1.1 self.make_signer(salt, self.algorithm)
salt=None, self.algorithm = HMACAlgorithm(hashlib.sha256)
def make_signer(self, salt=None, algorithm=None): if salt is None: salt = self.salt key_derivation = 'none' if salt is None else None if algorithm is None: algorithm = self.algorithm return self.signer(self.secret_key, salt=salt, sep='.', key_derivation=key_derivation, algorithm=algorithm)self.salt = wantbytes(b'itsdangerous')= ‘itsdangrous’ wantbytes函数的作用是把unicode字符串转换成str。
key_derivation = None
所以make_signer函数最后返回的是self.signer(app.config['SECRECT_KEY'], salt='itsdangerous', sep = '.',key_derivation=None,algorithm=HMACAlgorithm(hashlib.sha256)')
self.signer = Signer, 即Singer(app.config['SECRECT_KEY'], salt='itsdangerous', sep = '.', key_derivation=None, algorithm=HMACAlgorithm(hashlib.sha256))
Singner的构造函数:
class Signer(object): default_digest_method = staticmethod(hashlib.sha1) default_key_derivation = 'django-concat' def __init__(self, secret_key, salt=None, sep='.', key_derivation=None, digest_method=None, algorithm=None): self.secret_key = want_bytes(secret_key) self.sep = sep self.salt = 'itsdangerous.Signer' if salt is None else salt if key_derivation is None: key_derivation = self.default_key_derivation self.key_derivation = key_derivation if digest_method is None: digest_method = self.default_digest_method self.digest_method = digest_method if algorithm is None: algorithm = HMACAlgorithm(self.digest_method) self.algorithm = algorithm所以Singer(app.config['SECRECT_KEY'], salt=b'itsdangerous', sep = '.', key_derivation=None, algorithm='HS256')创建了一个Singner类的实例,并设置了如下属性:
self.secrect_key = want_bytes(app.config['SECRECT_KEY'])
self.sep = '.'
self.salt = b'itsdangrous'
self.key_derivation = 'django-concat'
self.difest_method = staticmethod(hashlib.sha1)
self.algorithm = 'HS256'
结论:所以self.make_signer(salt, self.algorithm)返回的是Singner的实例
然后这个实例调用unsign方法: self.make_signer(salt, self.algorithm).unsign(want_bytes(s))
s=token
def unsign(self, signed_value): """Unsigns the given string.""" signed_value = want_bytes(signed_value) sep = want_bytes(self.sep) if sep not in signed_value: raise BadSignature('No %r found in value' % self.sep) value, sig = signed_value.rsplit(sep, 1) if self.verify_signature(value, sig): return value raise BadSignature('Signature %r does not match' % sig, payload=value)这个函数把token分成了(1) (2)两部分, 然后调用了verify_signatue函数:
def verify_signature(self, value, sig): """Verifies the signature for the given value.""" key = self.derive_key() try: sig = base64_decode(sig) except Exception: return False return self.algorithm.verify_signature(key, value, sig)derive_key函数:
def derive_key(self): """This method is called to derive the key. If you're unhappy with the default key derivation choices you can override them here. Keep in mind that the key derivation in itsdangerous is not intended to be used as a security method to make a complex key out of a short password. Instead you should use large random secret keys. """ salt = want_bytes(self.salt) if self.key_derivation == 'concat': return self.digest_method(salt + self.secret_key).digest() elif self.key_derivation == 'django-concat': return self.digest_method(salt + b'signer' + self.secret_key).digest() elif self.key_derivation == 'hmac': mac = hmac.new(self.secret_key, digestmod=self.digest_method) mac.update(salt) return mac.digest() elif self.key_derivation == 'none': return self.secret_key else: raise TypeError('Unknown key derivation method')
这个函数返回 staticmethod(hashlib.sha1)(b'itsdangrous' + b'signer' + app.config['SECRECT_KEY']).digest(),赋值给key
然后把(2)进行base64解码给sig
value 是(1)
verify_signatue函数返回self.algorithm.verify_signature(key, value, sig) 即HMACAlgorithm(hashlib.sha256).verify_signature(key, value, sig)
创建了HMACAlgorithm类的实例, 并设置实例属性self.digest_method = hashlib.sha256,然后调用父类verify_signature方法,
def verify_signature(self, key, value, sig): """Verifies the given signature matches the expected signature""" return constant_time_compare(sig, self.get_signature(key, value))子类self.get_signature方法,返回的就是key作为键, value作为值, 用hashlib.sha256加密后的字符串
和sig一样, 所以constant_time_compare函数返回True,
结论:所以unsign函数返回value。
self.load_payload(value, return_header=True), load_payload函数:
def load_payload(self, payload, return_header=False): payload = want_bytes(payload) if b'.' not in payload: raise BadPayload('No "." found in value') base64d_header, base64d_payload = payload.split(b'.', 1) try: json_header = base64_decode(base64d_header) except Exception as e: raise BadHeader('Could not base64 decode the header because of ' 'an exception', original_error=e) try: json_payload = base64_decode(base64d_payload) except Exception as e: raise BadPayload('Could not base64 decode the payload because of ' 'an exception', original_error=e) try: header = Serializer.load_payload(self, json_header, serializer=json) except BadData as e: raise BadHeader('Could not unserialize header because it was ' 'malformed', original_error=e) if not isinstance(header, dict): raise BadHeader('Header payload is not a JSON object', header=header) payload = Serializer.load_payload(self, json_payload) if return_header: return payload, header return payload我们知道, value就是header字典({‘alg’: 'HS256', 'iat': 当前时间, 'exp': 过期时间})的字符串base64编码 + obj字典({'confirm': 23})的字符串的base64编码 中间用 ‘.’进行分隔 (value)
这个函数把两个编码后的字符串分割开,然后分别解码成字典字符串,然后分别调用Serializer.load_payload函数:
def load_payload(self, payload, serializer=None): """Loads the encoded object. This function raises :class:`BadPayload` if the payload is not valid. The `serializer` parameter can be used to override the serializer stored on the class. The encoded payload is always byte based. """ if serializer is None: serializer = self.serializer is_text = self.is_text_serializer else: is_text = is_text_serializer(serializer) try: if is_text: payload = payload.decode('utf-8') return serializer.loads(payload) except Exception as e: raise BadPayload('Could not load the payload because an ' 'exception occurred on unserializing the data', original_error=e)
json_header就是"{‘alg’: 'HS256', 'iat': 当前时间, 'exp': 过期时间}"
所以函数返回的就是字典字符串 json.loads后得到的字典 , header = {‘alg’: 'HS256', 'iat': 当前时间, 'exp': 过期时间}
再看payload = Serializer.load_payload(self, json_payload)
json_payload就是"{'confirm': 23}"
所以函数返回的就是字典字符串json.loads后的到的字典, payload ={'confirm': 23}
结论:header = {‘alg’: 'HS256', 'iat': 当前时间, 'exp': 过期时间} payload ={'confirm': 23}
然后我们回到JSONWebSignatureSerializer的loads函数:
def loads(self, s, salt=None, return_header=False): """Reverse of :meth:`dumps`. If requested via `return_header` it will return a tuple of payload and header. """ payload, header = self.load_payload( self.make_signer(salt, self.algorithm).unsign(want_bytes(s)), return_header=True) if header.get('alg') != self.algorithm_name: raise BadHeader('Algorithm mismatch', header=header, payload=payload) if return_header: return payload, header return payload
现在header和payload都有了, 返回payload和header到子类TimedJSONWebSignatureSerializer的loads函数:
def loads(self, s, salt=None, return_header=False): payload, header = JSONWebSignatureSerializer.loads( self, s, salt, return_header=True) if 'exp' not in header: raise BadSignature('Missing expiry date', payload=payload) if not (isinstance(header['exp'], number_types) and header['exp'] > 0): raise BadSignature('expiry date is not an IntDate', payload=payload) if header['exp'] < self.now(): raise SignatureExpired('Signature expired', payload=payload, date_signed=self.get_issue_date(header)) if return_header: return payload, header return payload
返回payload,{'confirm': 23}, 所以data = s.loads(token), data的值就是{'confirm': 23}!!!!!!!
现在问题来了, 怎么从token到data绕了这么大一圈???
我猜主要是为了防止有人伪造token, 所以重点还是在app.config['SECRECT_KEY']和id, 下次再分析吧...看代码看的心累
- python flaskweb开发 data = s.loads(token)原理分析
- FlaskWeb开发
- python loads
- flaskweb开发(一)
- Flaskweb开发学习笔记
- python json loads遇到中文的情况分析。
- Python中Json库loads方法ValueError异常分析
- flaskWeb开发(基于python的web开发实战)-第一部分-Flask简介
- python json loads dumps
- Python json dumps() && loads()
- 【web开发原理】B/S架构原理分析
- 使用python搭建第一个FlaskWeb程序
- <<FlaskWeb开发:基于Python的Web应用开发实战>>一处笔误导致AttributeError: 'bool' object has no attribute '__call__'
- 【Flask】FlaskWeb开发上手点滴(01)-入门
- 【Flask】FlaskWeb开发上手点滴(02)-模板
- flaskweb开发中密码加密处理
- token原理
- Token原理
- Windows下MXnet GPU版本环境搭建
- 【五】机器学习之路——线性回归python实现(1)
- PyQt笔记001——入门小窗口
- 分布单值概述
- SpringSecurity(五):RememberMe以及源码分析
- python flaskweb开发 data = s.loads(token)原理分析
- WebPack学习笔记
- JavaScript学习——childNodes属性、nodeType属性
- 搜索二叉树
- spring boot 连接 redis
- java输出保留n位小数
- Django官方教程(八)【创建你的第一个 Django 项目,第六部分】
- leetcode 35. Search Insert Position
- nodemcu响应chunked数据