He estado buscando una manera de hacer sesiones/autenticación basada en cookies en Google App Engine porque no me gusta la idea de las sesiones basadas en Memcache, y tampoco me gusta la idea de forzar a los usuarios a crear cuentas de google solo para usar un sitio web. Me encontré con el posting de alguien que mencionaba algunas funciones de cookies firmadas del marco Tornado y parece que es lo que necesito. Lo que tengo en mente es almacenar la identificación de un usuario en una cookie inviolable y tal vez usar un decorador para los manejadores de solicitudes para probar el estado de autenticación del usuario, y como beneficio adicional, la identificación del usuario estará disponible para el manejador de solicitudes. trabajo de datastore y tal. El concepto sería similar a la autenticación de formularios en ASP.NET. Este código proviene del módulo web.py del marco Tornado.Google App Engine - Cookies seguras
De acuerdo con los documentos, "Firma y marca la hora de una cookie para que no se pueda falsificar" y "Devuelve la cookie firmada si valida, o Ninguna".
He intentado usarlo en un proyecto de App Engine, pero no entiendo los inconvenientes de intentar que estos métodos funcionen en el contexto del controlador de solicitudes. ¿Puede alguien mostrarme la forma correcta de hacerlo sin perder la funcionalidad que los desarrolladores de FriendFeed le ponen? Las porciones set_secure_cookie y get_secure_cookie son las más importantes, pero sería bueno poder usar los otros métodos también.
#!/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
Partes:
1. UID es el nombre de la clave
2. 1234 es su valor en clara
3. 1234567890 es la marca de tiempo
4. d32b9e9c67274fa062e2599fd659cc14 es la firma hecha a partir del valor y la marca de tiempo
yo no tenía la intención de utilizar Tornado con App Engine, sólo quiero fijar a buscar las cookies firmadas de la forma en que lo hicieron. Eché un vistazo al código de cookie seguro tipfy/werkzeug y creo que lo que están haciendo en Tornado es más elegante. – tponthieux