6

Tôi đã tìm kiếm một cách để thực hiện xác thực/phiên dựa trên cookie trong Google App Engine vì tôi không thích ý tưởng về các phiên dựa trên memcache và tôi cũng không thích ý tưởng buộc người dùng tạo tài khoản google chỉ để sử dụng trang web. Tôi tình cờ gặp một người nào đó của posting đã đề cập đến một số chức năng cookie đã ký từ khung Tornado và nó trông giống như những gì tôi cần. Điều tôi lưu ý là lưu trữ id của người dùng trong cookie bằng chứng giả mạo và có thể sử dụng trình trang trí cho trình xử lý yêu cầu để kiểm tra trạng thái xác thực của người dùng và như một lợi ích phụ mà id người dùng sẽ có sẵn cho trình xử lý yêu cầu công việc kho dữ liệu và như vậy. Khái niệm này sẽ tương tự như các hình thức xác thực trong ASP.NET. Mã này đến từ mô đun web.py của khung Tornado.Google App Engine - Cookie an toàn

Theo các tài liệu, nó "Ký tên và đặt thời gian cho một cookie để nó không thể được giả mạo" và "Trả về cookie đã ký nếu nó xác nhận hoặc Không."

Tôi đã cố gắng sử dụng nó trong một dự án App Engine, nhưng tôi không hiểu các sắc thái của việc cố gắng làm cho các phương thức này hoạt động trong ngữ cảnh của trình xử lý yêu cầu. Ai đó có thể cho tôi thấy đúng cách để làm điều này mà không làm mất chức năng mà các nhà phát triển FriendFeed đưa vào nó? Các phần set_secure_cookie và get_secure_cookie là quan trọng nhất, nhưng nó sẽ rất tốt để có thể sử dụng các phương thức khác.

#!/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

Parts:
1. uid là tên của khóa
2. 1234 là giá trị của mình rõ ràng
3. 1234567890 là timestamp
4. d32b9e9c67274fa062e2599fd659cc14 là chữ ký được tạo từ giá trị và dấu thời gian

Trả lời

3

này hoạt động nếu có ai quan tâm:

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 

Nó có thể được sử dụng như thế này:

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') 
12

Tornado không bao giờ có nghĩa là hoạt động với trí thông minh h App Engine (đó là "máy chủ riêng của nó" qua và thông qua). Tại sao bạn không chọn thay vì một số khung mà có nghĩa là cho App Engine từ từ "go" và nhẹ và dandy, chẳng hạn như tipfy? Nó cung cấp cho bạn xác thực bằng cách sử dụng hệ thống người dùng của riêng mình hoặc bất kỳ của riêng của App Engine users, OpenIn, OAuth và Facebook; phiên với cookie an toàn hoặc kho dữ liệu GAE; và nhiều hơn nữa, tất cả trong một cách tiếp cận "phi khung" siêu nhẹ dựa trên WSGI và Werkzeug. Có gì không thích ?!

+1

tôi đã không có ý định sử dụng Tornado với App Engine, tôi chỉ muốn thiết lập và lấy đã ký cookie theo cách họ đã làm. Tôi đã xem xét mã cookie bảo mật của mẹo/werkzeug và tôi nghĩ rằng những gì họ đang làm ở Tornado thanh lịch hơn. – tponthieux

0

Nếu bạn chỉ muốn lưu trữ ID người dùng của người dùng trong cookie (có lẽ bạn có thể xem hồ sơ của họ trong kho dữ liệu), bạn không cần cookie 'an toàn' hoặc chống giả mạo - bạn chỉ cần một không gian tên đủ lớn để đoán ID người dùng không thực tế - ví dụ: GUID hoặc dữ liệu ngẫu nhiên khác.

Một lựa chọn trước khi thực hiện việc này, trong đó sử dụng kho dữ liệu để lưu trữ phiên, là Beaker. Cách khác, bạn có thể tự mình xử lý với tiêu đề cookie/cookie, nếu bạn thực sự chỉ cần lưu trữ ID người dùng của họ.

+0

Lưu trữ id của người dùng trong cookie không phải là vấn đề, nhưng đó không phải là tất cả những gì tôi theo sau. GUID của công cụ ứng dụng không thực tế để đoán và sử dụng một số GUID khác để xác thực người dùng dường như gặp nhiều rắc rối hơn giá trị của nó. Việc có id của người dùng trong cookie đã ký sẽ giải quyết vấn đề một cách độc đáo miễn là thuật toán băm chạy nhanh một cách hợp lý. Tôi đã nhìn Beaker vài lần trước và quyết định chống lại nó bởi vì nó không giống như những gì tôi muốn. – tponthieux

+0

Tôi chắc chắn ai đó sẽ thấy điều này và biết chính xác cách làm cho mã từ Tornado hoạt động. Nó được thể hiện ngoài ngữ cảnh trong việc đăng câu hỏi, nhưng các đoạn mã có nghĩa là trở thành một phần của trình xử lý yêu cầu Tornado. Tôi đã thử mở rộng trình xử lý yêu cầu webapp, nhưng tôi không thể làm cho nó hoạt động được. Việc sửa chữa có lẽ là một cái gì đó đơn giản, nhưng tôi cần một người có nhiều kinh nghiệm để chỉ cho tôi cách làm điều đó. – tponthieux

+0

Tôi tò mò tại sao bạn quyết tâm sử dụng mô-đun phiên của Tornado? Có một số mô-đun phiên tốt khác, bao gồm Beaker, cung cấp tùy chọn chỉ có cookie đã ký. –

0

Có người gần đây được chiết xuất xác thực và phiên mã từ Tornado và tạo ra một thư viện mới đặc biệt cho game.

Có lẽ đây là nhiều hơn thì bạn cần, nhưng vì họ đã làm điều đó đặc biệt cho GAE bạn không cần phải lo lắng về việc thích nghi với điều đó cho mình.

Thư viện của họ được gọi là gaema.Đây là thông báo của họ trong nhóm Python GAE vào ngày 4 tháng 3 năm 2010: http://groups.google.com/group/google-appengine-python/browse_thread/thread/d2d6c597d66ecad3/06c6dc49cb8eca0c?lnk=gst&q=tornado#06c6dc49cb8eca0c

+0

Điều này khá thú vị. Khung công tác webapp nên thêm hỗ trợ cho cơ chế xác thực của bên thứ ba vì nó có vẻ là một xu hướng phổ biến. Dưới đây là những gì họ nói trên trang gaema "gaema chỉ xác thực người dùng và không cung cấp sự kiên trì như phiên hoặc cookie bảo mật để giữ cho người dùng đăng nhập .." – tponthieux

3

Đối với những người vẫn đang tìm kiếm, chúng tôi đã trích xuất chỉ thực hiện cookie Tornado mà bạn có thể sử dụng với App Engine tại ThriveSmart. Chúng tôi đang sử dụng thành công trên App Engine và sẽ tiếp tục cập nhật nó.

Cookie thư viện riêng của mình là tại địa chỉ: http://github.com/thrivesmart/prayls/blob/master/prayls/lilcookies.py

Bạn có thể nhìn thấy nó trong hành động trong ứng dụng ví dụ của chúng tôi bao gồm. Nếu cấu trúc của kho lưu trữ của chúng tôi thay đổi, bạn có thể tìm lilcookes.py trong github.com/thrivesmart/prayls

Tôi hy vọng điều đó sẽ hữu ích cho người nào đó ở ngoài đó!