Google App Engine - безопасные файлы cookie
-
22-09-2019 - |
Вопрос
Я искал способ выполнения аутентификации/сеансов на основе файлов cookie в Engine Google App, потому что мне не нравится идея сеансов на основе Memcache, и мне также не нравится идея заставлять пользователей создавать учетные записи Google просто для Используйте веб -сайт. Я наткнулся на чей -то публикация Это упомянуло некоторые подписанные функции печенья из рамки торнадо, и это похоже на то, что мне нужно. Что я имею в виду, так это хранение идентификатора пользователя в файле cookie -файлов, возможно, использование декоратора для обработчиков запроса для проверки состояния аутентификации пользователя, и в качестве пособия идентификатор пользователя будет доступен для обработки запросов для работа данных и тому подобное. Концепция была бы похожа на аутентификацию форм в ASP.NET. Этот код поступает из модуля Web.py Framework.
Согласно DocStrings, это «подписывает и отмечает временные метки cookie, так что его нельзя создать» и «возвращает данный подписанный cookie, если оно подтверждает, или нет».
Я пытался использовать его в проекте приложения, но я не понимаю нюансов попыток заставить эти методы работать в контексте обработчика запроса. Может ли кто -нибудь показать мне правильный способ сделать это, не теряя функциональности, которую разработчики 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 - это подпись, сделанная из значения и временной метки
Решение 3
Это работает, если кто -то заинтересован:
from google.appengine.ext import webapp
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
class ExtendedRequestHandler(webapp.RequestHandler):
"""Extends the Google App Engine webapp.RequestHandler."""
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 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):
"""Hashes a string based on a pass-phrase."""
hash = hmac.new("MySecretPhrase",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.request.cookies:
return self.request.cookies[name]
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))
new_cookie = Cookie.BaseCookie()
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
for morsel in new_cookie.values():
self.response.headers.add_header('Set-Cookie',morsel.OutputString(None))
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
Это можно использовать так:
class MyHandler(ExtendedRequestHandler):
def get(self):
self.set_cookie(name="MyCookie",value="NewValue",expires_days=10)
self.set_secure_cookie(name="MySecureCookie",value="SecureValue",expires_days=10)
value1 = self.get_cookie('MyCookie')
value2 = self.get_secure_cookie('MySecureCookie')
Другие советы
Tornado
никогда не предназначался для работы с App Engine (это «собственный сервер» до конца). Почему бы тебе не выбрать вместо этого какую -то основу, которая был предназначен для приложений из слова «Go» и легкий и денди, например, как Типфи? Он дает вам аутентификацию с использованием собственной пользовательской системы или любого из собственных приложений users
, Openin, Oauth и Facebook; сеансы с безопасным файлом cookie или GAE Datastore; и гораздо больше, кроме того, все в великолепно легком подходе «без кадров», основанном на WSGI и Werkzeug. Что не нравится?!
Для тех, кто все еще смотрит, мы извлекли только реализацию Cookie Cookie Tornado, которую вы можете использовать с помощью App Engine на ThriveMart. Мы успешно используем его в App Engine и будем продолжать обновлять его.
Сама библиотека печенья находится по адресу:http://github.com/thrivesmart/prayls/blob/master/prayls/lilcookies.py
Вы можете увидеть это в действии в нашем примере, которое включено. Если структура нашего репозитория когда -либо меняется, вы можете искать lilcookes.py в github.com/thrivesmart/prayls
Я надеюсь, что это полезно для кого -то там!
Если вы хотите сохранить только идентификатор пользователя пользователя в cookie (предположительно, чтобы вы могли посмотреть их записи в хранилище данных), вам не нужны «безопасные» или защитные файлы cookie - вам просто нужно пространство имен, которое достаточно большое, чтобы Сделайте угадание идентификаторов пользователей непрактичным - например, Гиды или другие случайные данные.
Один предварительно изготовленный вариант для этого, который использует хранилище данных для хранения сеанса, Стакан. Анкет С другой стороны, вы можете справиться с этим сами с помощью заголовков Set-Cookie/Cookie, если вам действительно нужно сохранить их идентификатор пользователя.
Кто -то недавно извлекли аутентификацию и код сеанса из торнадо и создал новую библиотеку специально для GAE.
Возможно, это больше, чем вам нужно, но так как они сделали это специально для Gae, вам не нужно беспокоиться о адаптации самостоятельно.
Их библиотека называется Gaema. Вот их объявление в Gae Python Group 4 марта 2010 года:http://groups.google.com/group/google-appengine-python/browse_thread/thread/d2d6c597d66ecad3/06c6dc49cb8eca0c?lnk=gst&q=tornado#06c6dc49cb8eca0c