如何在Google App Engine上实现简单会话?

7 投票
2 回答
3258 浏览
提问于 2025-04-15 21:07

这里有一个非常基础的类,用来处理App Engine上的会话:

"""Lightweight implementation of cookie-based sessions for Google App Engine.

Classes:
Session

"""

import os
import random
import Cookie
from google.appengine.api import memcache

_COOKIE_NAME = 'app-sid'
_COOKIE_PATH = '/'
_SESSION_EXPIRE_TIME = 180 * 60


class Session(object):

    """Cookie-based session implementation using Memcached."""

    def __init__(self):
        self.sid = None
        self.key = None
        self.session = None
        cookie_str = os.environ.get('HTTP_COOKIE', '')
        self.cookie = Cookie.SimpleCookie()
        self.cookie.load(cookie_str)
        if self.cookie.get(_COOKIE_NAME):
            self.sid = self.cookie[_COOKIE_NAME].value
            self.key = 'session-' + self.sid
            self.session = memcache.get(self.key)
        if self.session:
            self._update_memcache()
        else:
            self.sid = str(random.random())[5:] + str(random.random())[5:]
            self.key = 'session-' + self.sid
            self.session = dict()
            memcache.add(self.key, self.session, _SESSION_EXPIRE_TIME)
            self.cookie[_COOKIE_NAME] = self.sid
            self.cookie[_COOKIE_NAME]['path'] = _COOKIE_PATH
            print self.cookie

    def __len__(self):
        return len(self.session)

    def __getitem__(self, key):
        if key in self.session:
            return self.session[key]
        raise KeyError(str(key))

    def __setitem__(self, key, value):
        self.session[key] = value
        self._update_memcache()

    def __delitem__(self, key):
        if key in self.session:
            del self.session[key]
            self._update_memcache()
            return None
        raise KeyError(str(key))

    def __contains__(self, item):
        try:
            i = self.__getitem__(item)
        except KeyError:
            return False
        return True

    def _update_memcache(self):
        memcache.replace(self.key, self.session, _SESSION_EXPIRE_TIME)

我希望能得到一些建议,来改进代码以提高安全性。

注意:在生产版本中,它还会在数据存储中保存会话的副本。

注意:我知道网上有很多更完整的实现,但我想更多地了解这个主题,所以请不要用“用那个”或“用另一个”库来回答这个问题。

2 个回答

0

这里有几个额外的安全措施可以考虑添加。

首先,通常会使用存储在会话中的信息来验证每一个新的请求。例如,你可以检查在会话期间,IP地址和用户代理(user-agent)是否没有变化:

newip = str( request.remote_addr );
if sesh.ip_addr != newip:
    logging.warn( "Session IP has changed to %s." % newip);
newua = rh.request.headers.get( 'User-Agent', None );
if sesh.agent != newua:
    logging.warn( "Session UA has changed to %s." % newua );

另外,也许最好是限制会话不能无限期地续期?我觉得像谷歌这样的网站,如果你试图长时间保持会话,最终会要求你重新登录。

我想每次会话续期时逐渐减少_SESSION_EXPIRE_TIME是比较简单的做法,但这并不是一个很好的解决方案。理想情况下,决定何时强制用户重新登录应该考虑到你网站的使用流程和安全需求。

5

这里有一个建议,可以让你的实现变得更简单。

你正在创建一个随机的临时密钥,用作会话的密钥,存储在内存缓存中。你提到你还会把会话存储在数据库中(那里的密钥会不同)。

为什么不把会话在数据库中的密钥也随机化,然后把这个密钥作为唯一的密钥,既用于数据库也用于内存缓存(如果需要的话)呢?这样做会不会引入新的安全问题?

下面是一些代码,用于为会话模型创建一个随机的数据库密钥:

# Get a random integer to use as the session's datastore ID.
# (So it can be stored in a cookie without being 'guessable'.)
random.seed();
id = None;
while None==id or Session.get_by_id( id ):
    id = random.randrange( sys.maxint );
seshKey = db.Key.from_path( 'Session', id );    
session = Session( key = seshKey );

要从会话中获取ID(也就是存储在cookie中的内容),可以使用:

sid = session.key().id();

在从cookie中读取'sid'后,如何获取会话实例:

session = Session.get_by_id( sid );

撰写回答