ZC WSGI sessions
项目描述
这是使用 zope.session 作为底层机制的 WSGI 中间件实现持久会话。
使用方法
将 zc.wsgisessions 添加到您应用程序(例如,myapp)的 setup.py 文件中的 install_requires 列表
将以下内容添加到 myapp.ini
[filter:sessions] use = egg:zc.wsgisessions
您可以将以下内容添加到配置中
secure = true
或
http-only = off
有效词汇包括:true、false、on、off、yes、no、1 和 0。
其他选项包括
domain = .example.com max-age = 10000 path = /foo
您还可以指定用于会话存储的数据库名称
db-name = appdb
在数据库中间件之后但在应用程序之前将 sessions 添加到管道中。
将此功能添加到数据库中间件的初始化器列表中
zc.wsgisessions.sessions.initialize_database(database)
您还可以传递以下关键字参数:db_name、namespace、secret、timeout 和 resolution。
将此功能添加到 bobo.configure 列表中(您 WSGI 应用的初始化器)
zope.component.provideAdapter(zc.wsgisessions.sessions.get_session)
您可以在您的身份验证代码中使用一些辅助工具
PKG_KEY = __name__ # e.g., myapp.auth def get_user(request): return zc.wsgisessions.sessions.get(request, PKG_KEY, 'user') def save_user(request, user): zc.wsgisession.sessions.store(request, PKG_KEY, 'user', user) def forget_user(request): return zc.wsgisessions.sessions.remove(request, PKG_KEY, 'user')
当运行 Selenium 测试时,不能使用 HttpOnly cookie。请将测试应用程序的 global_conf 字典中的选项设置为 'http-only': False。
详细文档
会话
会话支持有两个方面:浏览器识别和会话存储。浏览器使用 cookie 进行识别;如果请求中没有设置 cookie,则响应会为未来的请求设置它。
会话数据使用由 zope.session 包定义的持久会话数据容器进行存储。如果不存在,则在启动时将实例添加到数据库中。我们可以通过传递关键字参数到数据库初始化器来控制某些参数。这个测试的一次运行使用默认设置,而第二次运行设置自定义参数。
>>> import re >>> import zc.wsgisessions.testing >>> import zc.wsgisessions.sessions >>> db_name = 'sessions' >>> if zc.wsgisessions.testing.TEST_DB_INIT: ... db_name = 'test' ... db = conn.get_connection(db_name).db() ... zc.wsgisessions.sessions.initialize_database( ... db, ... db_name=db_name, ... namespace='browserid_c0defeed', ... secret='0.10612221415937506119', ... timeout=(15 * 60), # 15 minutes ... resolution=60, # 1 minute ... )>>> dbroot = conn.get_connection(db_name).root() >>> dbroot['sessions'] <zope.session.session.PersistentSessionDataContainer object at 0xc0defeed>>>> if zc.wsgisessions.testing.TEST_DB_INIT: ... expected_id = re.compile('browserid_c0defeed') ... expected_secret = re.compile('0.10612221415937506119') ... expected_timeout = 15 * 60 ... expected_resolution = 60 ... else: ... expected_id = re.compile('browserid_[0-9a-f]{8}') ... expected_secret = re.compile('[0-9a-f]{20}') ... expected_timeout = 24 * 60 * 60 ... expected_resolution = 60 * 60 >>> re.match(expected_id, dbroot['browserid_info'][0]) is not None True >>> re.match(expected_secret, dbroot['browserid_info'][1]) is not None True >>> dbroot['sessions'].timeout == expected_timeout True >>> dbroot['sessions'].resolution == expected_resolution True
如果配置中将 secure 设置为 true 或请求是 https,则在 Set-Cookie 响应中添加 secure。此外,除非配置将 http-only 设置为 false,否则将添加 HttpOnly。
>>> global_conf = {} >>> filter_conf = {'db-name': db_name} >>> filter = zc.wsgisessions.sessions.BrowserIdFilter( ... global_conf, **filter_conf)(object()) >>> environ = { ... 'zodb.connection': conn.get_connection('test'), ... 'wsgi.url_scheme': 'https' ... } >>> h = dict(filter.prepare(environ, lambda *args: args)(200, [], None)[1]) >>> cookie_parts = h['Set-Cookie'].split('; ') >>> 'secure' in cookie_parts True >>> 'HttpOnly' in cookie_parts True
当在过滤器配置(在 .ini 文件中)中更改设置时,默认值将被替换。
>>> filter_conf.update({'http-only': 'false', 'secure': 'true', ... 'domain': '.example.com', 'max-age': '5000', ... 'path': '/foo'}) >>> filter = zc.wsgisessions.sessions.BrowserIdFilter( ... global_conf, **filter_conf)(object()) >>> environ['wsgi.url_scheme'] = 'http' >>> h = dict(filter.prepare(environ, lambda *args: args)(200, [], None)[1]) >>> cookie_parts = h['Set-Cookie'].split('; ') >>> 'secure' in cookie_parts True >>> 'HttpOnly' in cookie_parts False >>> 'Domain=.example.com' in cookie_parts True >>> 'Max-Age=5000' in cookie_parts True >>> 'Path=/foo' in cookie_parts True
注意,上面的 URL 方案不是 https,但由于过滤器配置中请求了它,因此已设置安全。
对于 Selenium 测试,我们需要重置 HttpOnly,因为我们正在使用开发中的 http URL 方案,因此 secure 的默认值(关闭)是可以接受的。注意,这次我们在全局配置中设置 http-only 以覆盖 .ini 文件中的设置值。
>>> global_conf = {'http-only': 'off'} >>> filter_conf = {'db-name': db_name, 'http-only': 'on'} >>> filter = zc.wsgisessions.sessions.BrowserIdFilter( ... global_conf, **filter_conf)(object()) >>> h = dict(filter.prepare(environ, lambda *args: args)(200, [], None)[1]) >>> cookie_parts = h['Set-Cookie'].split('; ') >>> 'secure' in cookie_parts False >>> 'HttpOnly' in cookie_parts False
会话存储的数据库名称在 initialize_database 中默认设置为 sessions 或提供的 db_name(对于这些测试的第二次运行为 test)。如果我们尝试从其配置(在 .ini 文件中)将错误的数据库名称传递给过滤器,我们将得到一个错误。
>>> if zc.wsgisessions.testing.TEST_DB_INIT: ... filter_conf['db-name'] = 'sessions' ... filter = zc.wsgisessions.sessions.BrowserIdFilter( ... global_conf, **filter_conf)(object()) ... else: ... filter_conf['db-name'] = 'test' ... filter = zc.wsgisessions.sessions.BrowserIdFilter( ... global_conf, **filter_conf)(object()) >>> h = dict(filter.prepare(environ, lambda *args: args)(200, [], None)[1]) Traceback (most recent call last): ... KeyError: 'browserid_info'
浏览器识别
支持 cookie 所需的信息也存储在数据库中
>>> dbroot['browserid_info'] ('browserid_...', '...')>>> cookie_name = dbroot['browserid_info'][0]>>> import webtest >>> app = webtest.TestApp(app) >>> response = app.get('https://127.0.0.1/')>>> cookie_value = app.cookies[cookie_name] >>> len(cookie_value) 54
如果我们更改数据库中的密钥,我们可以导致会话标识符被重置
>>> import random >>> import transaction>>> secret = '%.20f' % random.random() >>> dbroot['browserid_info'] = cookie_name, secret >>> transaction.commit()>>> response = app.get('https://127.0.0.1/')>>> cookie_value == app.cookies[cookie_name] False>>> cookie_value = app.cookies[cookie_name] >>> app.cookies[cookie_name] = 'bad' >>> response = app.get('https://127.0.0.1/')>>> cookie_value == app.cookies[cookie_name] False
会话存储
一旦从请求中加载了 cookie,或安排在响应中发送,就会在请求上存储一个 ISession 对象。让我们直接创建一个,以便我们可以看到它是如何工作的
>>> sdc = dbroot['sessions'] >>> session = zc.wsgisessions.sessions.Session(cookie_value, sdc)>>> pkgdata = session['myapp.auth'] >>> pkgdata['mydata'] = 42>>> sdc[cookie_value]['myapp.auth']['mydata'] 42>>> list(session) Traceback (most recent call last): ... NotImplementedError
辅助工具
>>> import webob >>> import zc.dbconnection >>> import zope.session.interfaces >>> zc.dbconnection.set_local(conn) >>> environ = {'zc.wsgisessions.session': session} >>> request = webob.Request(environ=environ)
get(request, pkg_id, key=None)
从会话中检索值;如果没有指定键,则检索 SessionPkgData 容器。
>>> pkgdata = zc.wsgisessions.sessions.get(request, 'myapp.auth') >>> zope.session.interfaces.ISessionPkgData.providedBy(pkgdata) True>>> zc.wsgisessions.sessions.get(request, 'myapp.auth', 'blah') is None True>>> pkgdata['blah'] = '!!!' >>> zc.wsgisessions.sessions.get(request, 'myapp.auth', 'blah') '!!!'>>> zc.wsgisessions.sessions.get(request, 'myapp.auth', 'mydata') 42
当指定包标识符和键名称时,如果该会话数据对象不存在,则不会创建它。
>>> zc.wsgisessions.sessions.get(request, "dontcreateme", "blah") is None True >>> adapter = zope.session.interfaces.ISession(request) >>> adapter.get("dontcreateme") is None True
store(request, pkg_id, key, value)
将键/值对存储在会话中。
>>> obj = object() >>> zc.wsgisessions.sessions.store( ... request, 'myapp.auth', 'someobject', obj) >>> zc.wsgisessions.sessions.get( ... request, 'myapp.auth', 'someobject') is obj True>>> obj = object() >>> zc.wsgisessions.sessions.store( ... request, 'myapp.data', 'someobject', obj) >>> zc.wsgisessions.sessions.get( ... request, 'myapp.auth', 'someobject') is obj False>>> zc.wsgisessions.sessions.get( ... request, 'myapp.data', 'someobject') is obj True
remove(request, pkg_id, key)
通过键从会话中删除值。如果没有指定 pkg_id,则使用 zc.wsgisessions.sessions.KEY 的默认 pkg_id。
>>> _obj = zc.wsgisessions.sessions.remove( ... request, 'myapp.auth', 'someobject') >>> zc.wsgisessions.sessions.get( ... request, 'myapp.auth', 'someobject') is None True>>> zc.wsgisessions.sessions.get( ... request, 'myapp.data', 'someobject') is obj True>>> zc.wsgisessions.sessions.remove( ... request, 'myapp.data', 'someobject') is obj True >>> zc.wsgisessions.sessions.get( ... request, 'myapp.data', 'someobject') is None True
如果不存在,则不会创建底层的会话数据映射。
>>> zc.wsgisessions.sessions.remove( ... request, "dontcreateme", "somekey") is None True >>> adapter.get("dontcreateme") is None True
更改
0.6.1 (2013-10-08)
在发布中包含 CHANGES.txt。
0.6.0 (2013-10-08)
添加 domain、max-age 和 path 配置选项。
0.5.1 (2013-06-12)
开源发布。
0.5 (2013-03-12)
使用密码学安全的随机数源(os.urandom)生成浏览器 ID。
修复了导致不必要地创建 SessionData 对象的 get/remove 辅助工具中的错误。
0.4 (2012-01-03)
接受会话存储的数据库名称参数。
0.3 (2011-11-11)
将辅助函数的参数放置在更合理的顺序中。
要求 pkg_id 以防止不良使用模式。
0.2 (2011-11-10)
使 http-only 和 secure 可配置。
测试配置选项。
测试数据库初始化和选项。
0.1 (2011-11-10)
首次发布