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
itsdangerous.py

refer:

1、https://pythonhosted.org/itsdangerous/

2、http://itsdangerous.readthedocs.io/en/latest/

3、http://cxymrzero.github.io/blog/2015/03/18/flask-token/

原文地址:https://www.cnblogs.com/shengulong/p/7475537.html