python的模块itsdangerous
这个模块主要用来签名和序列化
使用场景:
一、给字符串添加签名:
发送方和接收方拥有相同的密钥--"secret-key",发送方使用密钥对发送内容进行签名,接收方使用相同的密钥对接收到的内容进行验证,看是否是发送方发送的内容
1 >>> from itsdangerous import Signer 2 >>> s = Signer('secret-key') 3 >>> s.sign('my string, ssssssssss,dddddddddddddlsd') 4 'my string, ssssssssss,dddddddddddddlsd.nSXTxgO_UMN4gkLZcFCioa-dZSo' 5 >>> 6 >>> s.unsign('my string, ssssssssss,dddddddddddddlsd.nSXTxgO_UMN4gkLZcFCioa-dZSo') 7 'my string, ssssssssss,dddddddddddddlsd' 8 >>> s.unsign('my string, ssss.nSXTxgO_UMN4gkLZcFCioa-dZSo') 9 Traceback (most recent call last): 10 File "<stdin>", line 1, in <module> 11 File "/usr/local/lib/python2.7/site-packages/itsdangerous.py", line 374, in unsign 12 payload=value) 13 itsdangerous.BadSignature: Signature 'nSXTxgO_UMN4gkLZcFCioa-dZSo' does not match 14 >>> s.unsign('my string, ssssssssss,dddddddddddddlsd.nSXTxgO_UMN4gkLZcFCioa-dZSP') 15 Traceback (most recent call last): 16 File "<stdin>", line 1, in <module> 17 File "/usr/local/lib/python2.7/site-packages/itsdangerous.py", line 374, in unsign 18 payload=value) 19 itsdangerous.BadSignature: Signature 'nSXTxgO_UMN4gkLZcFCioa-dZSP' does not match 20 >>>
二、带时间戳的签名:
签名有一定的时效性,发送方发送时,带上时间信息,接收方判断多长时间内是否失效
>>> from itsdangerous import TimestampSigner >>> s = TimestampSigner('secret-key') >>> string = s.sign('foo') >>> s.unsign(string, max_age=5)
foo
>>> s.unsign(string, max_age=5) Traceback (most recent call last): ... itsdangerous.SignatureExpired: Signature age 15 > 5 seconds
三、序列化
>>> from itsdangerous import Serializer >>> s = Serializer('secret-key') >>> s.dumps([1, 2, 3, 4]) '[1, 2, 3, 4].r7R9RhGgDPvvWl3iNzLuIIfELmo' And it can of course also load: >>> s.loads('[1, 2, 3, 4].r7R9RhGgDPvvWl3iNzLuIIfELmo') [1, 2, 3, 4] If you want to have the timestamp attached you can use the TimedSerializer.
四、带时间戳的序列化:
>>> from itsdangerous import TimedSerializer >>> s=TimedSerializer('secret-key') >>> s.dumps([1,2,3,4]) '[1, 2, 3, 4].DI7WHQ.yVOjwQWau5mVRGuVkoqa7654VXc' >>> s.loads('[1, 2, 3, 4].DI7WHQ.yVOjwQWau5mVRGuVkoqa7654VXc') [1, 2, 3, 4] >>> s.loads('[1, 2, 3, 4].DI7WHQ.yVOjwQWau5mVRGuVkoqa7654VXc',max_age=10) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python2.7/site-packages/itsdangerous.py", line 643, in loads .unsign(s, max_age, return_timestamp=True) File "/usr/local/lib/python2.7/site-packages/itsdangerous.py", line 463, in unsign date_signed=self.timestamp_to_datetime(timestamp)) itsdangerous.SignatureExpired: Signature age 28 > 10 seconds >>> s.loads('[1, 2, 3, 4].DI7WHQ.yVOjwQWau5mVRGuVkoqa7654VXc',max_age=40) [1, 2, 3, 4] >>>
五、URL安全序列化
对于限定字符串的场景,你可以使用URL安全序列化
>>> from itsdangerous import URLSafeSerializer >>> s = URLSafeSerializer('secret-key') >>> s.dumps([1, 2, 3, 4]) 'WzEsMiwzLDRd.wSPHqC0gR7VUqivlSukJ0IeTDgo' >>> s.loads('WzEsMiwzLDRd.wSPHqC0gR7VUqivlSukJ0IeTDgo') [1, 2, 3, 4]
六、JSON Web签名
JSON Web Signatures
Starting with “itsdangerous” 0.18 JSON Web Signatures are also supported. They generally work very similar to the already existing URL safe serializer but will emit headers according to the current draft (10) of the JSON Web Signature (JWS) [draft-ietf-jose-json-web-signature].
>>> from itsdangerous import JSONWebSignatureSerializer >>> s = JSONWebSignatureSerializer('secret-key') >>> s.dumps({'x': 42}) 'eyJhbGciOiJIUzI1NiJ9.eyJ4Ijo0Mn0.ZdTn1YyGz9Yx5B5wNpWRL221G1WpVE5fPCPKNuc6UAo'
When loading the value back the header will not be returned by default like with the other serializers. However it is possible to also ask for the header by passing return_header=True. Custom header fields can be provided upon serialization:
>>> s.dumps(0, header_fields={'v': 1})
'eyJhbGciOiJIUzI1NiIsInYiOjF9.MA.wT-RZI9YU06R919VBdAfTLn82_iIQD70J_j-3F4z_aM'
>>> s.loads('eyJhbGciOiJIUzI1NiIsInYiOjF9.MA.wT-RZI9YU06R919VBdAf'
... 'TLn82_iIQD70J_j-3F4z_aM', return_header=True)
...
(0, {u'alg': u'HS256', u'v': 1})
“itsdangerous” only provides HMAC SHA derivatives and the none algorithm at the moment and does not support the ECC based ones. The algorithm in the header is checked against the one of the serializer and on a mismatch a BadSignatureexception is raised.
七、带时间戳的JSON Web签名
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer s = Serializer('secret-key', expires_in=60) s.dumps({'id': user.id}) # user为model中封装过的对象
八、盐值
这里的盐值和加密算法里的盐值概念不一样,这里的盐值(salt)可以应用到上面所有情形中,不同的盐值,生成的签名或者序列化的数值不一样
>>> s1 = URLSafeSerializer('secret-key', salt='activate-salt') >>> s1.dumps(42) 'NDI.kubVFOOugP5PAIfEqLJbXQbfTxs' >>> s2 = URLSafeSerializer('secret-key', salt='upgrade-salt') >>> s2.dumps(42) 'NDI.7lx-N1P-z2veJ7nT1_2bnTkjGTE' >>> s2.loads(s1.dumps(42)) Traceback (most recent call last): ... itsdangerous.BadSignature: Signature "kubVFOOugP5PAIfEqLJbXQbfTxs" does not match Only the serializer with the same salt can load the value: >>> s2.loads(s2.dumps(42)) 42
九、源码奉上:
1 # -*- coding: utf-8 -*- 2 """ 3 itsdangerous 4 ~~~~~~~~~~~~ 5 6 A module that implements various functions to deal with untrusted 7 sources. Mainly useful for web applications. 8 9 :copyright: (c) 2014 by Armin Ronacher and the Django Software Foundation. 10 :license: BSD, see LICENSE for more details. 11 """ 12 13 import sys 14 import hmac 15 import zlib 16 import time 17 import base64 18 import hashlib 19 import operator 20 from datetime import datetime 21 22 23 PY2 = sys.version_info[0] == 2 24 if PY2: 25 from itertools import izip 26 text_type = unicode 27 int_to_byte = chr 28 number_types = (int, long, float) 29 else: 30 from functools import reduce 31 izip = zip 32 text_type = str 33 int_to_byte = operator.methodcaller('to_bytes', 1, 'big') 34 number_types = (int, float) 35 36 37 try: 38 import simplejson as json 39 except ImportError: 40 import json 41 42 43 class _CompactJSON(object): 44 """Wrapper around simplejson that strips whitespace. 45 """ 46 47 def loads(self, payload): 48 return json.loads(payload) 49 50 def dumps(self, obj): 51 return json.dumps(obj, separators=(',', ':')) 52 53 54 compact_json = _CompactJSON() 55 56 57 # 2011/01/01 in UTC 58 EPOCH = 1293840000 59 60 61 def want_bytes(s, encoding='utf-8', errors='strict'): 62 if isinstance(s, text_type): 63 s = s.encode(encoding, errors) 64 return s 65 66 67 def is_text_serializer(serializer): 68 """Checks wheather a serializer generates text or binary.""" 69 return isinstance(serializer.dumps({}), text_type) 70 71 72 # Starting with 3.3 the standard library has a c-implementation for 73 # constant time string compares. 74 _builtin_constant_time_compare = getattr(hmac, 'compare_digest', None) 75 76 77 def constant_time_compare(val1, val2): 78 """Returns True if the two strings are equal, False otherwise. 79 80 The time taken is independent of the number of characters that match. Do 81 not use this function for anything else than comparision with known 82 length targets. 83 84 This is should be implemented in C in order to get it completely right. 85 """ 86 if _builtin_constant_time_compare is not None: 87 return _builtin_constant_time_compare(val1, val2) 88 len_eq = len(val1) == len(val2) 89 if len_eq: 90 result = 0 91 left = val1 92 else: 93 result = 1 94 left = val2 95 for x, y in izip(bytearray(left), bytearray(val2)): 96 result |= x ^ y 97 return result == 0 98 99 100 class BadData(Exception): 101 """Raised if bad data of any sort was encountered. This is the 102 base for all exceptions that itsdangerous is currently using. 103 104 .. versionadded:: 0.15 105 """ 106 message = None 107 108 def __init__(self, message): 109 Exception.__init__(self, message) 110 self.message = message 111 112 def __str__(self): 113 return text_type(self.message) 114 115 if PY2: 116 __unicode__ = __str__ 117 def __str__(self): 118 return self.__unicode__().encode('utf-8') 119 120 121 class BadPayload(BadData): 122 """This error is raised in situations when payload is loaded without 123 checking the signature first and an exception happend as a result of 124 that. The original exception that caused that will be stored on the 125 exception as :attr:`original_error`. 126 127 This can also happen with a :class:`JSONWebSignatureSerializer` that 128 is subclassed and uses a different serializer for the payload than 129 the expected one. 130 131 .. versionadded:: 0.15 132 """ 133 134 def __init__(self, message, original_error=None): 135 BadData.__init__(self, message) 136 #: If available, the error that indicates why the payload 137 #: was not valid. This might be `None`. 138 self.original_error = original_error 139 140 141 class BadSignature(BadData): 142 """This error is raised if a signature does not match. As of 143 itsdangerous 0.14 there are helpful attributes on the exception 144 instances. You can also catch down the baseclass :exc:`BadData`. 145 """ 146 147 def __init__(self, message, payload=None): 148 BadData.__init__(self, message) 149 #: The payload that failed the signature test. In some 150 #: situations you might still want to inspect this, even if 151 #: you know it was tampered with. 152 #: 153 #: .. versionadded:: 0.14 154 self.payload = payload 155 156 157 class BadTimeSignature(BadSignature): 158 """Raised for time based signatures that fail. This is a subclass 159 of :class:`BadSignature` so you can catch those down as well. 160 """ 161 162 def __init__(self, message, payload=None, date_signed=None): 163 BadSignature.__init__(self, message, payload) 164 165 #: If the signature expired this exposes the date of when the 166 #: signature was created. This can be helpful in order to 167 #: tell the user how long a link has been gone stale. 168 #: 169 #: .. versionadded:: 0.14 170 self.date_signed = date_signed 171 172 173 class BadHeader(BadSignature): 174 """Raised if a signed header is invalid in some form. This only 175 happens for serializers that have a header that goes with the 176 signature. 177 178 .. versionadded:: 0.24 179 """ 180 181 def __init__(self, message, payload=None, header=None, 182 original_error=None): 183 BadSignature.__init__(self, message, payload) 184 185 #: If the header is actually available but just malformed it 186 #: might be stored here. 187 self.header = header 188 189 #: If available, the error that indicates why the payload 190 #: was not valid. This might be `None`. 191 self.original_error = original_error 192 193 194 class SignatureExpired(BadTimeSignature): 195 """Signature timestamp is older than required max_age. This is a 196 subclass of :exc:`BadTimeSignature` so you can use the baseclass for 197 catching the error. 198 """ 199 200 201 def base64_encode(string): 202 """base64 encodes a single bytestring (and is tolerant to getting 203 called with a unicode string). 204 The resulting bytestring is safe for putting into URLs. 205 """ 206 string = want_bytes(string) 207 return base64.urlsafe_b64encode(string).strip(b'=') 208 209 210 def base64_decode(string): 211 """base64 decodes a single bytestring (and is tolerant to getting 212 called with a unicode string). 213 The result is also a bytestring. 214 """ 215 string = want_bytes(string, encoding='ascii', errors='ignore') 216 return base64.urlsafe_b64decode(string + b'=' * (-len(string) % 4)) 217 218 219 def int_to_bytes(num): 220 assert num >= 0 221 rv = [] 222 while num: 223 rv.append(int_to_byte(num & 0xff)) 224 num >>= 8 225 return b''.join(reversed(rv)) 226 227 228 def bytes_to_int(bytestr): 229 return reduce(lambda a, b: a << 8 | b, bytearray(bytestr), 0) 230 231 232 class SigningAlgorithm(object): 233 """Subclasses of `SigningAlgorithm` have to implement `get_signature` to 234 provide signature generation functionality. 235 """ 236 237 def get_signature(self, key, value): 238 """Returns the signature for the given key and value""" 239 raise NotImplementedError() 240 241 def verify_signature(self, key, value, sig): 242 """Verifies the given signature matches the expected signature""" 243 return constant_time_compare(sig, self.get_signature(key, value)) 244 245 246 class NoneAlgorithm(SigningAlgorithm): 247 """This class provides a algorithm that does not perform any signing and 248 returns an empty signature. 249 """ 250 251 def get_signature(self, key, value): 252 return b'' 253 254 255 class HMACAlgorithm(SigningAlgorithm): 256 """This class provides signature generation using HMACs.""" 257 258 #: The digest method to use with the MAC algorithm. This defaults to sha1 259 #: but can be changed for any other function in the hashlib module. 260 default_digest_method = staticmethod(hashlib.sha1) 261 262 def __init__(self, digest_method=None): 263 if digest_method is None: 264 digest_method = self.default_digest_method 265 self.digest_method = digest_method 266 267 def get_signature(self, key, value): 268 mac = hmac.new(key, msg=value, digestmod=self.digest_method) 269 return mac.digest() 270 271 272 class Signer(object): 273 """This class can sign bytes and unsign it and validate the signature 274 provided. 275 276 Salt can be used to namespace the hash, so that a signed string is only 277 valid for a given namespace. Leaving this at the default value or re-using 278 a salt value across different parts of your application where the same 279 signed value in one part can mean something different in another part 280 is a security risk. 281 282 See :ref:`the-salt` for an example of what the salt is doing and how you 283 can utilize it. 284 285 .. versionadded:: 0.14 286 `key_derivation` and `digest_method` were added as arguments to the 287 class constructor. 288 289 .. versionadded:: 0.18 290 `algorithm` was added as an argument to the class constructor. 291 """ 292 293 #: The digest method to use for the signer. This defaults to sha1 but can 294 #: be changed for any other function in the hashlib module. 295 #: 296 #: .. versionchanged:: 0.14 297 default_digest_method = staticmethod(hashlib.sha1) 298 299 #: Controls how the key is derived. The default is Django style 300 #: concatenation. Possible values are ``concat``, ``django-concat`` 301 #: and ``hmac``. This is used for deriving a key from the secret key 302 #: with an added salt. 303 #: 304 #: .. versionadded:: 0.14 305 default_key_derivation = 'django-concat' 306 307 def __init__(self, secret_key, salt=None, sep='.', key_derivation=None, 308 digest_method=None, algorithm=None): 309 self.secret_key = want_bytes(secret_key) 310 self.sep = sep 311 self.salt = 'itsdangerous.Signer' if salt is None else salt 312 if key_derivation is None: 313 key_derivation = self.default_key_derivation 314 self.key_derivation = key_derivation 315 if digest_method is None: 316 digest_method = self.default_digest_method 317 self.digest_method = digest_method 318 if algorithm is None: 319 algorithm = HMACAlgorithm(self.digest_method) 320 self.algorithm = algorithm 321 322 def derive_key(self): 323 """This method is called to derive the key. If you're unhappy with 324 the default key derivation choices you can override them here. 325 Keep in mind that the key derivation in itsdangerous is not intended 326 to be used as a security method to make a complex key out of a short 327 password. Instead you should use large random secret keys. 328 """ 329 salt = want_bytes(self.salt) 330 if self.key_derivation == 'concat': 331 return self.digest_method(salt + self.secret_key).digest() 332 elif self.key_derivation == 'django-concat': 333 return self.digest_method(salt + b'signer' + 334 self.secret_key).digest() 335 elif self.key_derivation == 'hmac': 336 mac = hmac.new(self.secret_key, digestmod=self.digest_method) 337 mac.update(salt) 338 return mac.digest() 339 elif self.key_derivation == 'none': 340 return self.secret_key 341 else: 342 raise TypeError('Unknown key derivation method') 343 344 def get_signature(self, value): 345 """Returns the signature for the given value""" 346 value = want_bytes(value) 347 key = self.derive_key() 348 sig = self.algorithm.get_signature(key, value) 349 return base64_encode(sig) 350 351 def sign(self, value): 352 """Signs the given string.""" 353 return value + want_bytes(self.sep) + self.get_signature(value) 354 355 def verify_signature(self, value, sig): 356 """Verifies the signature for the given value.""" 357 key = self.derive_key() 358 try: 359 sig = base64_decode(sig) 360 except Exception: 361 return False 362 return self.algorithm.verify_signature(key, value, sig) 363 364 def unsign(self, signed_value): 365 """Unsigns the given string.""" 366 signed_value = want_bytes(signed_value) 367 sep = want_bytes(self.sep) 368 if sep not in signed_value: 369 raise BadSignature('No %r found in value' % self.sep) 370 value, sig = signed_value.rsplit(sep, 1) 371 if self.verify_signature(value, sig): 372 return value 373 raise BadSignature('Signature %r does not match' % sig, 374 payload=value) 375 376 def validate(self, signed_value): 377 """Just validates the given signed value. Returns `True` if the 378 signature exists and is valid, `False` otherwise.""" 379 try: 380 self.unsign(signed_value) 381 return True 382 except BadSignature: 383 return False 384 385 386 class TimestampSigner(Signer): 387 """Works like the regular :class:`Signer` but also records the time 388 of the signing and can be used to expire signatures. The unsign 389 method can rause a :exc:`SignatureExpired` method if the unsigning 390 failed because the signature is expired. This exception is a subclass 391 of :exc:`BadSignature`. 392 """ 393 394 def get_timestamp(self): 395 """Returns the current timestamp. This implementation returns the 396 seconds since 1/1/2011. The function must return an integer. 397 """ 398 return int(time.time() - EPOCH) 399 400 def timestamp_to_datetime(self, ts): 401 """Used to convert the timestamp from `get_timestamp` into a 402 datetime object. 403 """ 404 return datetime.utcfromtimestamp(ts + EPOCH) 405 406 def sign(self, value): 407 """Signs the given string and also attaches a time information.""" 408 value = want_bytes(value) 409 timestamp = base64_encode(int_to_bytes(self.get_timestamp())) 410 sep = want_bytes(self.sep) 411 value = value + sep + timestamp 412 return value + sep + self.get_signature(value) 413 414 def unsign(self, value, max_age=None, return_timestamp=False): 415 """Works like the regular :meth:`~Signer.unsign` but can also 416 validate the time. See the base docstring of the class for 417 the general behavior. If `return_timestamp` is set to `True` 418 the timestamp of the signature will be returned as naive 419 :class:`datetime.datetime` object in UTC. 420 """ 421 try: 422 result = Signer.unsign(self, value) 423 sig_error = None 424 except BadSignature as e: 425 sig_error = e 426 result = e.payload or b'' 427 sep = want_bytes(self.sep) 428 429 # If there is no timestamp in the result there is something 430 # seriously wrong. In case there was a signature error, we raise 431 # that one directly, otherwise we have a weird situation in which 432 # we shouldn't have come except someone uses a time-based serializer 433 # on non-timestamp data, so catch that. 434 if not sep in result: 435 if sig_error: 436 raise sig_error 437 raise BadTimeSignature('timestamp missing', payload=result) 438 439 value, timestamp = result.rsplit(sep, 1) 440 try: 441 timestamp = bytes_to_int(base64_decode(timestamp)) 442 except Exception: 443 timestamp = None 444 445 # Signature is *not* okay. Raise a proper error now that we have 446 # split the value and the timestamp. 447 if sig_error is not None: 448 raise BadTimeSignature(text_type(sig_error), payload=value, 449 date_signed=timestamp) 450 451 # Signature was okay but the timestamp is actually not there or 452 # malformed. Should not happen, but well. We handle it nonetheless 453 if timestamp is None: 454 raise BadTimeSignature('Malformed timestamp', payload=value) 455 456 # Check timestamp is not older than max_age 457 if max_age is not None: 458 age = self.get_timestamp() - timestamp 459 if age > max_age: 460 raise SignatureExpired( 461 'Signature age %s > %s seconds' % (age, max_age), 462 payload=value, 463 date_signed=self.timestamp_to_datetime(timestamp)) 464 465 if return_timestamp: 466 return value, self.timestamp_to_datetime(timestamp) 467 return value 468 469 def validate(self, signed_value, max_age=None): 470 """Just validates the given signed value. Returns `True` if the 471 signature exists and is valid, `False` otherwise.""" 472 try: 473 self.unsign(signed_value, max_age=max_age) 474 return True 475 except BadSignature: 476 return False 477 478 479 class Serializer(object): 480 """This class provides a serialization interface on top of the 481 signer. It provides a similar API to json/pickle and other modules but is 482 slightly differently structured internally. If you want to change the 483 underlying implementation for parsing and loading you have to override the 484 :meth:`load_payload` and :meth:`dump_payload` functions. 485 486 This implementation uses simplejson if available for dumping and loading 487 and will fall back to the standard library's json module if it's not 488 available. 489 490 Starting with 0.14 you do not need to subclass this class in order to 491 switch out or customer the :class:`Signer`. You can instead also pass a 492 different class to the constructor as well as keyword arguments as 493 dictionary that should be forwarded:: 494 495 s = Serializer(signer_kwargs={'key_derivation': 'hmac'}) 496 497 .. versionchanged:: 0.14: 498 The `signer` and `signer_kwargs` parameters were added to the 499 constructor. 500 """ 501 502 #: If a serializer module or class is not passed to the constructor 503 #: this one is picked up. This currently defaults to :mod:`json`. 504 default_serializer = json 505 506 #: The default :class:`Signer` class that is being used by this 507 #: serializer. 508 #: 509 #: .. versionadded:: 0.14 510 default_signer = Signer 511 512 def __init__(self, secret_key, salt=b'itsdangerous', serializer=None, 513 signer=None, signer_kwargs=None): 514 self.secret_key = want_bytes(secret_key) 515 self.salt = want_bytes(salt) 516 if serializer is None: 517 serializer = self.default_serializer 518 self.serializer = serializer 519 self.is_text_serializer = is_text_serializer(serializer) 520 if signer is None: 521 signer = self.default_signer 522 self.signer = signer 523 self.signer_kwargs = signer_kwargs or {} 524 525 def load_payload(self, payload, serializer=None): 526 """Loads the encoded object. This function raises :class:`BadPayload` 527 if the payload is not valid. The `serializer` parameter can be used to 528 override the serializer stored on the class. The encoded payload is 529 always byte based. 530 """ 531 if serializer is None: 532 serializer = self.serializer 533 is_text = self.is_text_serializer 534 else: 535 is_text = is_text_serializer(serializer) 536 try: 537 if is_text: 538 payload = payload.decode('utf-8') 539 return serializer.loads(payload) 540 except Exception as e: 541 raise BadPayload('Could not load the payload because an ' 542 'exception occurred on unserializing the data', 543 original_error=e) 544 545 def dump_payload(self, obj): 546 """Dumps the encoded object. The return value is always a 547 bytestring. If the internal serializer is text based the value 548 will automatically be encoded to utf-8. 549 """ 550 return want_bytes(self.serializer.dumps(obj)) 551 552 def make_signer(self, salt=None): 553 """A method that creates a new instance of the signer to be used. 554 The default implementation uses the :class:`Signer` baseclass. 555 """ 556 if salt is None: 557 salt = self.salt 558 return self.signer(self.secret_key, salt=salt, **self.signer_kwargs) 559 560 def dumps(self, obj, salt=None): 561 """Returns a signed string serialized with the internal serializer. 562 The return value can be either a byte or unicode string depending 563 on the format of the internal serializer. 564 """ 565 payload = want_bytes(self.dump_payload(obj)) 566 rv = self.make_signer(salt).sign(payload) 567 if self.is_text_serializer: 568 rv = rv.decode('utf-8') 569 return rv 570 571 def dump(self, obj, f, salt=None): 572 """Like :meth:`dumps` but dumps into a file. The file handle has 573 to be compatible with what the internal serializer expects. 574 """ 575 f.write(self.dumps(obj, salt)) 576 577 def loads(self, s, salt=None): 578 """Reverse of :meth:`dumps`, raises :exc:`BadSignature` if the 579 signature validation fails. 580 """ 581 s = want_bytes(s) 582 return self.load_payload(self.make_signer(salt).unsign(s)) 583 584 def load(self, f, salt=None): 585 """Like :meth:`loads` but loads from a file.""" 586 return self.loads(f.read(), salt) 587 588 def loads_unsafe(self, s, salt=None): 589 """Like :meth:`loads` but without verifying the signature. This is 590 potentially very dangerous to use depending on how your serializer 591 works. The return value is ``(signature_okay, payload)`` instead of 592 just the payload. The first item will be a boolean that indicates 593 if the signature is okay (``True``) or if it failed. This function 594 never fails. 595 596 Use it for debugging only and if you know that your serializer module 597 is not exploitable (eg: do not use it with a pickle serializer). 598 599 .. versionadded:: 0.15 600 """ 601 return self._loads_unsafe_impl(s, salt) 602 603 def _loads_unsafe_impl(self, s, salt, load_kwargs=None, 604 load_payload_kwargs=None): 605 """Lowlevel helper function to implement :meth:`loads_unsafe` in 606 serializer subclasses. 607 """ 608 try: 609 return True, self.loads(s, salt=salt, **(load_kwargs or {})) 610 except BadSignature as e: 611 if e.payload is None: 612 return False, None 613 try: 614 return False, self.load_payload(e.payload, 615 **(load_payload_kwargs or {})) 616 except BadPayload: 617 return False, None 618 619 def load_unsafe(self, f, *args, **kwargs): 620 """Like :meth:`loads_unsafe` but loads from a file. 621 622 .. versionadded:: 0.15 623 """ 624 return self.loads_unsafe(f.read(), *args, **kwargs) 625 626 627 class TimedSerializer(Serializer): 628 """Uses the :class:`TimestampSigner` instead of the default 629 :meth:`Signer`. 630 """ 631 632 default_signer = TimestampSigner 633 634 def loads(self, s, max_age=None, return_timestamp=False, salt=None): 635 """Reverse of :meth:`dumps`, raises :exc:`BadSignature` if the 636 signature validation fails. If a `max_age` is provided it will 637 ensure the signature is not older than that time in seconds. In 638 case the signature is outdated, :exc:`SignatureExpired` is raised 639 which is a subclass of :exc:`BadSignature`. All arguments are 640 forwarded to the signer's :meth:`~TimestampSigner.unsign` method. 641 """ 642 base64d, timestamp = self.make_signer(salt) \ 643 .unsign(s, max_age, return_timestamp=True) 644 payload = self.load_payload(base64d) 645 if return_timestamp: 646 return payload, timestamp 647 return payload 648 649 def loads_unsafe(self, s, max_age=None, salt=None): 650 load_kwargs = {'max_age': max_age} 651 load_payload_kwargs = {} 652 return self._loads_unsafe_impl(s, salt, load_kwargs, load_payload_kwargs) 653 654 655 class JSONWebSignatureSerializer(Serializer): 656 """This serializer implements JSON Web Signature (JWS) support. Only 657 supports the JWS Compact Serialization. 658 """ 659 660 jws_algorithms = { 661 'HS256': HMACAlgorithm(hashlib.sha256), 662 'HS384': HMACAlgorithm(hashlib.sha384), 663 'HS512': HMACAlgorithm(hashlib.sha512), 664 'none': NoneAlgorithm(), 665 } 666 667 #: The default algorithm to use for signature generation 668 default_algorithm = 'HS256' 669 670 default_serializer = compact_json 671 672 def __init__(self, secret_key, salt=None, serializer=None, 673 signer=None, signer_kwargs=None, algorithm_name=None): 674 Serializer.__init__(self, secret_key, salt, serializer, 675 signer, signer_kwargs) 676 if algorithm_name is None: 677 algorithm_name = self.default_algorithm 678 self.algorithm_name = algorithm_name 679 self.algorithm = self.make_algorithm(algorithm_name) 680 681 def load_payload(self, payload, return_header=False): 682 payload = want_bytes(payload) 683 if b'.' not in payload: 684 raise BadPayload('No "." found in value') 685 base64d_header, base64d_payload = payload.split(b'.', 1) 686 try: 687 json_header = base64_decode(base64d_header) 688 except Exception as e: 689 raise BadHeader('Could not base64 decode the header because of ' 690 'an exception', original_error=e) 691 try: 692 json_payload = base64_decode(base64d_payload) 693 except Exception as e: 694 raise BadPayload('Could not base64 decode the payload because of ' 695 'an exception', original_error=e) 696 try: 697 header = Serializer.load_payload(self, json_header, 698 serializer=json) 699 except BadData as e: 700 raise BadHeader('Could not unserialize header because it was ' 701 'malformed', original_error=e) 702 if not isinstance(header, dict): 703 raise BadHeader('Header payload is not a JSON object', 704 header=header) 705 payload = Serializer.load_payload(self, json_payload) 706 if return_header: 707 return payload, header 708 return payload 709 710 def dump_payload(self, header, obj): 711 base64d_header = base64_encode(self.serializer.dumps(header)) 712 base64d_payload = base64_encode(self.serializer.dumps(obj)) 713 return base64d_header + b'.' + base64d_payload 714 715 def make_algorithm(self, algorithm_name): 716 try: 717 return self.jws_algorithms[algorithm_name] 718 except KeyError: 719 raise NotImplementedError('Algorithm not supported') 720 721 def make_signer(self, salt=None, algorithm=None): 722 if salt is None: 723 salt = self.salt 724 key_derivation = 'none' if salt is None else None 725 if algorithm is None: 726 algorithm = self.algorithm 727 return self.signer(self.secret_key, salt=salt, sep='.', 728 key_derivation=key_derivation, algorithm=algorithm) 729 730 def make_header(self, header_fields): 731 header = header_fields.copy() if header_fields else {} 732 header['alg'] = self.algorithm_name 733 return header 734 735 def dumps(self, obj, salt=None, header_fields=None): 736 """Like :meth:`~Serializer.dumps` but creates a JSON Web Signature. It 737 also allows for specifying additional fields to be included in the JWS 738 Header. 739 """ 740 header = self.make_header(header_fields) 741 signer = self.make_signer(salt, self.algorithm) 742 return signer.sign(self.dump_payload(header, obj)) 743 744 def loads(self, s, salt=None, return_header=False): 745 """Reverse of :meth:`dumps`. If requested via `return_header` it will 746 return a tuple of payload and header. 747 """ 748 payload, header = self.load_payload( 749 self.make_signer(salt, self.algorithm).unsign(want_bytes(s)), 750 return_header=True) 751 if header.get('alg') != self.algorithm_name: 752 raise BadHeader('Algorithm mismatch', header=header, 753 payload=payload) 754 if return_header: 755 return payload, header 756 return payload 757 758 def loads_unsafe(self, s, salt=None, return_header=False): 759 kwargs = {'return_header': return_header} 760 return self._loads_unsafe_impl(s, salt, kwargs, kwargs) 761 762 763 class TimedJSONWebSignatureSerializer(JSONWebSignatureSerializer): 764 """Works like the regular :class:`JSONWebSignatureSerializer` but also 765 records the time of the signing and can be used to expire signatures. 766 767 JWS currently does not specify this behavior but it mentions a possibility 768 extension like this in the spec. Expiry date is encoded into the header 769 similarily as specified in `draft-ietf-oauth-json-web-token 770 <http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#expDef`_. 771 772 The unsign method can raise a :exc:`SignatureExpired` method if the 773 unsigning failed because the signature is expired. This exception is a 774 subclass of :exc:`BadSignature`. 775 """ 776 777 DEFAULT_EXPIRES_IN = 3600 778 779 def __init__(self, secret_key, expires_in=None, **kwargs): 780 JSONWebSignatureSerializer.__init__(self, secret_key, **kwargs) 781 if expires_in is None: 782 expires_in = self.DEFAULT_EXPIRES_IN 783 self.expires_in = expires_in 784 785 def make_header(self, header_fields): 786 header = JSONWebSignatureSerializer.make_header(self, header_fields) 787 iat = self.now() 788 exp = iat + self.expires_in 789 header['iat'] = iat 790 header['exp'] = exp 791 return header 792 793 def loads(self, s, salt=None, return_header=False): 794 payload, header = JSONWebSignatureSerializer.loads( 795 self, s, salt, return_header=True) 796 797 if 'exp' not in header: 798 raise BadSignature('Missing expiry date', payload=payload) 799 800 if not (isinstance(header['exp'], number_types) 801 and header['exp'] > 0): 802 raise BadSignature('expiry date is not an IntDate', 803 payload=payload) 804 805 if header['exp'] < self.now(): 806 raise SignatureExpired('Signature expired', payload=payload, 807 date_signed=self.get_issue_date(header)) 808 809 if return_header: 810 return payload, header 811 return payload 812 813 def get_issue_date(self, header): 814 rv = header.get('iat') 815 if isinstance(rv, number_types): 816 return datetime.utcfromtimestamp(int(rv)) 817 818 def now(self): 819 return int(time.time()) 820 821 822 class URLSafeSerializerMixin(object): 823 """Mixed in with a regular serializer it will attempt to zlib compress 824 the string to make it shorter if necessary. It will also base64 encode 825 the string so that it can safely be placed in a URL. 826 """ 827 828 def load_payload(self, payload): 829 decompress = False 830 if payload.startswith(b'.'): 831 payload = payload[1:] 832 decompress = True 833 try: 834 json = base64_decode(payload) 835 except Exception as e: 836 raise BadPayload('Could not base64 decode the payload because of ' 837 'an exception', original_error=e) 838 if decompress: 839 try: 840 json = zlib.decompress(json) 841 except Exception as e: 842 raise BadPayload('Could not zlib decompress the payload before ' 843 'decoding the payload', original_error=e) 844 return super(URLSafeSerializerMixin, self).load_payload(json) 845 846 def dump_payload(self, obj): 847 json = super(URLSafeSerializerMixin, self).dump_payload(obj) 848 is_compressed = False 849 compressed = zlib.compress(json) 850 if len(compressed) < (len(json) - 1): 851 json = compressed 852 is_compressed = True 853 base64d = base64_encode(json) 854 if is_compressed: 855 base64d = b'.' + base64d 856 return base64d 857 858 859 class URLSafeSerializer(URLSafeSerializerMixin, Serializer): 860 """Works like :class:`Serializer` but dumps and loads into a URL 861 safe string consisting of the upper and lowercase character of the 862 alphabet as well as ``'_'``, ``'-'`` and ``'.'``. 863 """ 864 default_serializer = compact_json 865 866 867 class URLSafeTimedSerializer(URLSafeSerializerMixin, TimedSerializer): 868 """Works like :class:`TimedSerializer` but dumps and loads into a URL 869 safe string consisting of the upper and lowercase character of the 870 alphabet as well as ``'_'``, ``'-'`` and ``'.'``. 871 """ 872 default_serializer = compact_json
refer:
1、https://pythonhosted.org/itsdangerous/
2、http://itsdangerous.readthedocs.io/en/latest/
3、http://cxymrzero.github.io/blog/2015/03/18/flask-token/