我一直在尋找一種方法來在Google App Engine中執行基於cookie的身份驗證/會話,因爲我不喜歡基於memcache的會話的想法,而且我也不喜歡這個想法強制用戶創建Google帳戶只是爲了使用網站。我偶然發現某人的posting提到了Tornado框架中的一些簽名cookie功能,它看起來像我所需要的。我想到的是將用戶的ID存儲在防篡改cookie中,並且可能使用裝飾器來請求處理程序來測試用戶的身份驗證狀態,並且作爲副作用,用戶ID將可用於請求處理程序數據存儲工作等。這個概念與ASP.NET中的表單身份驗證類似。這段代碼來自Tornado框架的web.py模塊。Google App Engine - 安全Cookie
根據文檔字符串,它「簽名和時間戳一個cookie,以便它不能僞造」和 「返回給定的簽名cookie,如果它有效,或無。」
我試過在App Engine項目中使用它,但我不明白試圖讓這些方法在請求處理程序的上下文中工作的細微差別。有人可以告訴我正確的方式來做到這一點,而不會丟失FriendFeed開發者投入的功能嗎? set_secure_cookie和get_secure_cookie部分是最重要的部分,但能夠使用其他方法也會很好。
#!/usr/bin/env python
import Cookie
import base64
import time
import hashlib
import hmac
import datetime
import re
import calendar
import email.utils
import logging
def _utf8(s):
if isinstance(s, unicode):
return s.encode("utf-8")
assert isinstance(s, str)
return s
def _unicode(s):
if isinstance(s, str):
try:
return s.decode("utf-8")
except UnicodeDecodeError:
raise HTTPError(400, "Non-utf8 argument")
assert isinstance(s, unicode)
return s
def _time_independent_equals(a, b):
if len(a) != len(b):
return False
result = 0
for x, y in zip(a, b):
result |= ord(x)^ord(y)
return result == 0
def cookies(self):
"""A dictionary of Cookie.Morsel objects."""
if not hasattr(self,"_cookies"):
self._cookies = Cookie.BaseCookie()
if "Cookie" in self.request.headers:
try:
self._cookies.load(self.request.headers["Cookie"])
except:
self.clear_all_cookies()
return self._cookies
def _cookie_signature(self,*parts):
self.require_setting("cookie_secret","secure cookies")
hash = hmac.new(self.application.settings["cookie_secret"],
digestmod=hashlib.sha1)
for part in parts:hash.update(part)
return hash.hexdigest()
def get_cookie(self,name,default=None):
"""Gets the value of the cookie with the given name,else default."""
if name in self.cookies:
return self.cookies[name].value
return default
def set_cookie(self,name,value,domain=None,expires=None,path="/",
expires_days=None):
"""Sets the given cookie name/value with the given options."""
name = _utf8(name)
value = _utf8(value)
if re.search(r"[\x00-\x20]",name + value):
# Don't let us accidentally inject bad stuff
raise ValueError("Invalid cookie %r:%r" % (name,value))
if not hasattr(self,"_new_cookies"):
self._new_cookies = []
new_cookie = Cookie.BaseCookie()
self._new_cookies.append(new_cookie)
new_cookie[name] = value
if domain:
new_cookie[name]["domain"] = domain
if expires_days is not None and not expires:
expires = datetime.datetime.utcnow() + datetime.timedelta(
days=expires_days)
if expires:
timestamp = calendar.timegm(expires.utctimetuple())
new_cookie[name]["expires"] = email.utils.formatdate(
timestamp,localtime=False,usegmt=True)
if path:
new_cookie[name]["path"] = path
def clear_cookie(self,name,path="/",domain=None):
"""Deletes the cookie with the given name."""
expires = datetime.datetime.utcnow() - datetime.timedelta(days=365)
self.set_cookie(name,value="",path=path,expires=expires,
domain=domain)
def clear_all_cookies(self):
"""Deletes all the cookies the user sent with this request."""
for name in self.cookies.iterkeys():
self.clear_cookie(name)
def set_secure_cookie(self,name,value,expires_days=30,**kwargs):
"""Signs and timestamps a cookie so it cannot be forged"""
timestamp = str(int(time.time()))
value = base64.b64encode(value)
signature = self._cookie_signature(name,value,timestamp)
value = "|".join([value,timestamp,signature])
self.set_cookie(name,value,expires_days=expires_days,**kwargs)
def get_secure_cookie(self,name,include_name=True,value=None):
"""Returns the given signed cookie if it validates,or None"""
if value is None:value = self.get_cookie(name)
if not value:return None
parts = value.split("|")
if len(parts) != 3:return None
if include_name:
signature = self._cookie_signature(name,parts[0],parts[1])
else:
signature = self._cookie_signature(parts[0],parts[1])
if not _time_independent_equals(parts[2],signature):
logging.warning("Invalid cookie signature %r",value)
return None
timestamp = int(parts[1])
if timestamp < time.time() - 31 * 86400:
logging.warning("Expired cookie %r",value)
return None
try:
return base64.b64decode(parts[0])
except:
return None
的uid = 1234 | 1234567890個| d32b9e9c67274fa062e2599fd659cc14
配件:
1. UID是其中的關鍵
2. 1234名是明確
3 1234567890你的價值是時間戳
4. d32b9e9c67274fa062e2599fd659cc14是由數值和時間戳製成的簽名
我沒打算用旋風App Engine的,我只是想設置和獲取以他們的方式簽署cookie。我看了一下tipfy/werkzeug安全cookie代碼,我認爲他們在Tornado做的更優雅。 – tponthieux 2010-03-28 08:52:30