在开始用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 = 1
MAX_SUPPORTED_SIGNED_VALUE_VERSION = 2
DEFAULT_SIGNED_VALUE_VERSION = 2
DEFAULT_SIGNED_VALUE_MIN_VERSION = 1
class 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
上。