在开始用Python的web框架Tornado的时候,以为Tornado中的get_secure_cookie
和set_secure_cookie
是用来设置加密后的Cookie信息的,但今天看了源代码之后,发现情况并非如想象的那样,secure_cookie
在Tornado中的作用是对Cookie值进行签名而已。新版本(v4.0)和老版本(1.0)相较而言,虽然增加了第二个版本的secure_cookie
,但功能仍然是一样。
在Tornado 1.0中,使用的是SHA1进行签名,而在Tornado 4.0中的新版本中使用的是SHA256进行签名,同时输出的Cookie格式有差异——开始第一个数字是版本号(secure cookie使用的version,而非Tornado的版本)。
新版本的set_secure_cookie
设置Cookie的相关源码如下:
## Tornado 4.0中web.py的部分源码## set_secure_cookie相关的源码MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1MAX_SUPPORTED_SIGNED_VALUE_VERSION = 2DEFAULT_SIGNED_VALUE_VERSION = 2DEFAULT_SIGNED_VALUE_MIN_VERSION = 1class RequestHandler(object): def set_cookie(self, name, value, domain=None, expires=None, path="/", expires_days=None, **kwargs): name = escape.native_str(name) value = escape.native_str(value) if re.search(r"[\x00-\x20]", name + value): raise ValueError("Invalid cookie %r: %r" % (name, value)) if not hasattr(self, "_new_cookie"): self._new_cookie = Cookie.SimpleCookie() if name in self._new_cookie: del self._new_cookie[name] self._new_cookie[name] = value morsel = self._new_cookie[name] if domain: morsel["domain"] = domain if expires_days is not None and not expires: expires = datetime.datetime.utcnow() + datetime.timedelta( days=expires_days) if expires: morsel["expires"] = httputil.format_timestamp(expires) if path: morsel["path"] = path for k, v in kwargs.items(): if k == 'max_age': k = 'max-age' morsel[k] = v def set_secure_cookie(self, name, value, expires_days=30, version=None, **kwargs): self.set_cookie(name, self.create_signed_value(name, value, version=version), expires_days=expires_days, **kwargs) def create_signed_value(self, name, value, version=None): self.require_setting("cookie_secret", "secure cookies") return create_signed_value(self.application.settings["cookie_secret"], name, value, version=version)def create_signed_value(secret, name, value, version=None, clock=None): if version is None: version = DEFAULT_SIGNED_VALUE_VERSION if clock is None: clock = time.time timestamp = utf8(str(int(clock()))) value = base64.b64encode(utf8(value)) if version == 1: signature = _create_signature_v1(secret, name, value, timestamp) value = b"|".join([value, timestamp, signature]) return value elif version == 2: def format_field(s): return utf8("%d:" % len(s)) + utf8(s) to_sign = b"|".join([ b"2|1:0", format_field(timestamp), format_field(name), format_field(value), b'']) signature = _create_signature_v2(secret, to_sign) return to_sign + signature else: raise ValueError("Unsupported version %d" % version)def _create_signature_v1(secret, *parts): hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) for part in parts: hash.update(utf8(part)) return utf8(hash.hexdigest())def _create_signature_v2(secret, s): hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) hash.update(utf8(s)) return utf8(hash.hexdigest())
?调用的关系如下:
-------------+--------------------------+--------------------------------+-----------------------------------+---------------------------------+-------------------------- | | | | | | | | | | | | | | | | | | | | +-----------+---------+ | | | | | | | | | | | set_secure_cookie | | | | | | | | | | | +-----------+---------+ | | | | | | | | | | +-----------+-------------+ | | | | invoke | | | | | +------------->| create_signed_value | | | | | | | | | | | +-----------+-------------+ | | | | | | | | | | +--------------+-------------+ | | | | invoke | | | | | +---------------->| create_signed_value(global)| | | | | | | | | | | +--------------+-------------+ | | | | | | | | | | +-----------------+-------------+ | | | | invoke | | | | | +---------------->| _create_signature_v2(global) | | | | | | | | | | | +-----------------+-------------+ | | | | return signature string (sha256)| | | | return source and signature |<----------------------------------+ | | result |<-------------------------------+ | | |<-------------------------| string | | | | | | | | | | | | | | | | | +--------------+----------+ | | | | | | +-------------------------- -------------------------------- ----------------------------------- ----------------->| set_cookie | | | invoke set_cookie to set secure_cookie | | | | | | | +--------------+----------+ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
需要注意,在RequestHandler
类中的create_signed_value
方法调用的是web.py
中的模块方法create_signed_value
。在获取到返回结果之后,set_secure_cookie
调用set_cookie
,将secure cookie放入cookie内。
版本2的secure cookie和版本1的签名区别就在_create_signature_v2
和_create_signature_v1
上。
原文地址:Tornado中的secure cookie, 感谢原作者分享。 你不勇敢,没人替你坚强!