会议

A session 是一个名称空间,它对于一段连续活动有效,可用于表示用户与Web应用程序的交互。

本章介绍如何配置会话,哪些会话实现 Pyramid 提供开箱即用、如何存储和检索会话中的数据,以及会话特定的功能:闪存消息。

使用默认会话工厂

要使用会话,必须设置 session factory 在你 Pyramid 配置。

在中提供了非常基本的会话工厂实现 Pyramid 核心。它使用cookie存储会话信息。此实现有以下限制:

  • 此实现使用的cookie中的会话信息是 not 已加密,因此任何访问用户浏览器的cookie存储的人或任何访问cookie传播的网络的人都可以查看它。
  • 在会话的序列化表示形式中可存储的最大字节数小于4000。这只适用于非常小的数据集。

但是,它是数字签名的,因此客户机在没有访问密钥的情况下无法轻易篡改内容。

您可以在 Pyramid 通过使用 pyramid.config.Configurator.set_session_factory() 方法。

1
2
3
4
5
6
from pyramid.session import SignedCookieSessionFactory
my_session_factory = SignedCookieSessionFactory('itsaseekreet')

from pyramid.config import Configurator
config = Configurator()
config.set_session_factory(my_session_factory)

警告

默认情况下 SignedCookieSessionFactory() 实现包含以下安全问题:

  • 会话数据是 未加密的 (但已签署/认证)。

    这意味着攻击者无法更改会话数据,但他们可以查看。在会话对象中保留敏感信息时,不应使用它,因为应用程序用户和有权访问用户网络流量的第三方都可以轻松读取这些信息。

    至少,使用tls和set secure=True 以避免网络上的任意用户查看会话内容。

  • 如果您使用此会话实现,并且无意中在应用程序中创建了跨站点脚本漏洞,因为会话数据未加密存储在cookie中,因此作恶者也更容易获得当前用户的跨站点脚本令牌。

    集合 httponly=True 通过从客户端javascript隐藏cookie来缓解此漏洞。

简而言之,除了最基本的应用程序外,其他应用程序都可以使用不同的会话工厂实现(最好是将会话数据保存在服务器上的实现),在这些应用程序中,“会话安全无关紧要”,您确信应用程序没有跨站点脚本漏洞,并且您确信不会暴露您的密钥。

金字塔2.0中Isession的更改

Pyramid 2 pyramid.interfaces.ISession 接口被更改为要求会话实现只需要支持JSON可序列化数据类型。这是一个比先前要求更严格的合同,即所有对象都是可处理的,并且是出于安全目的而执行的。这是一个向后不兼容的更改。以前,如果客户端会话实现受到破坏,则会使应用程序容易受到远程代码执行攻击,使用专门设计的会话在反序列化时执行代码。

对于具有兼容性问题的用户,可以设计一个可以处理这两种格式的序列化程序,直到您对客户机有时间进行合理升级感到满意为止。请记住,会话应该是短期的,因此受影响的客户机数量应该很小(最多不超过一个身份验证令牌)。示例序列化程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pyramid.session import JSONSerializer
from pyramid.session import PickleSerializer
from pyramid.session import SignedCookieSessionFactory

class JSONSerializerWithPickleFallback(object):
    def __init__(self):
        self.json = JSONSerializer()
        self.pickle = PickleSerializer()

    def dumps(self, value):
        # maybe catch serialization errors here and keep using pickle
        # while finding spots in your app that are not storing
        # JSON-serializable objects, falling back to pickle
        return self.json.dumps(value)

    def loads(self, value):
        try:
            return self.json.loads(value)
        except ValueError:
            return self.pickle.loads(value)

# somewhere in your configuration code
serializer = JSONSerializerWithPickleFallback()
session_factory = SignedCookieSessionFactory(..., serializer=serializer)
config.set_session_factory(session_factory)

使用会话对象

一旦为应用程序配置了会话工厂,就可以通过 session 任何属性 request 对象。例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from pyramid.response import Response

def myview(request):
    session = request.session
    if 'abc' in session:
        session['fred'] = 'yes'
    session['abc'] = '123'
    if 'fred' in session:
        return Response('Fred was in the session')
    else:
        return Response('Fred was not in the session')

第一次调用此视图时 Fred was not in the session . 后续调用产生 Fred was in the session 当然,假设客户端在多个请求中维护会话的标识。

您可以像使用Python字典一样使用会话。它支持所有字典方法,以及一些额外的属性和方法。

额外属性:

created
一个整数时间戳,指示创建此会话的时间。
new
布尔值。如果 new 是真的,此会话是新的。否则,它是由已经序列化的数据构成的。

额外方法:

changed()
在会话命名空间中改变可变值时调用此函数。请参阅下面的gotchas,了解您应该何时以及为什么调用它的详细信息。
invalidate()
当您想使会话无效时调用此函数(转储所有数据,并可能设置一个清除cookie)。

会话对象支持的方法和属性的正式定义位于 pyramid.interfaces.ISession 文档。

一些问题:

  • 会话数据的键和值必须是可序列化的JSON。这意味着,通常它们是对象基本类型的实例,例如字符串、列表、字典、元组、整数等。如果将对象放置在会话数据键或值中,而该值不是JSON可序列化的,则在序列化会话时会引发错误。也请看 金字塔2.0中Isession的更改 .
  • 如果在会话对象中放置可变值(例如,列表或字典),然后对该值进行变异,则必须调用 changed() 会话对象的方法。在这种情况下,会话无法知道它已被修改。但是,当您直接修改会话对象时,例如设置一个值(即, __setitem__ )或移除键(例如, delpop ,会话将自动知道需要重新序列化其数据,从而调用 changed() 是不必要的。打电话没什么害处 changed() 在这两种情况下,如果有疑问,请在更改会话数据后调用它。

使用备用会话工厂

本文撰写时存在以下会话工厂。

会话工厂 后端 描述
pyramid_nacl_session PyNaCl 定义一个基于pickle的加密cookie序列化程序,使用pynacl为cookie状态生成对称加密。
pyramid_redis_sessions Redis 用于金字塔的服务器端会话库,使用redis进行存储。
pyramid_beaker Beaker 由烧杯会话系统支持的金字塔会话工厂。

创建自己的会话工厂

如果没有默认或其他可用的会话实现 Pyramid 适合您,您可以通过实现 session factory . 您的会话工厂应返回 session . 两种类型的接口在 pyramid.interfaces.ISessionFactorypyramid.interfaces.ISession . 您可以在 pyramid.session 模块作为灵感。

Flash消息

“flash messages”只是存储在 session . 要使用Flash消息,必须启用 session factory 如上所述 使用默认会话工厂使用备用会话工厂 .

Flash消息有两个主要用途:在执行内部重定向之后,只向用户显示一次状态消息,并允许通用代码记录消息以进行一次性显示,而无需直接访问HTML模板。用户界面由 session 对象。

使用 session.flash 方法

要将消息添加到闪存消息队列,请使用会话对象的 flash() 方法:

request.session.flash('mymessage')

这个 flash() 方法将消息附加到闪存队列,必要时创建队列。

flash() 接受三个参数:

flash(message, queue='', allow_duplicate=True)

这个 message 参数是必需的。它表示您希望稍后向用户显示的消息。它通常是一根绳子,但是 message 您提供的内容不会以任何方式修改。

这个 queue 参数允许您选择要向其追加所提供消息的队列。这可用于将不同类型的消息推送到闪存中,以便以后在页面的不同位置显示。您可以为队列传递任何名称,但它必须是字符串。每个队列都是独立的,可以通过 pop_flash() 或通过检查 peek_flash() 分别地。 queue 默认为空字符串。空字符串表示默认的闪存消息队列。

request.session.flash(msg, 'myappsqueue')

这个 allow_duplicate 参数默认为 True . 如果这是 False ,如果尝试添加队列中已存在的消息值,则不会添加该值。

使用 session.pop_flash 方法

一旦一条或多条消息被添加到闪存队列中, session.flash() API session.pop_flash() API可用于弹出整个队列并返回以供使用。

要从Flash对象弹出特定的消息队列,请使用会话对象的 pop_flash() 方法。这将返回添加到闪存队列的消息列表,并清空队列。

pop_flash(queue='')
>>> request.session.flash('info message')
>>> request.session.pop_flash()
['info message']

调用 session.pop_flash() 和上面一样,没有相应的调用 session.flash() 将返回空列表,因为队列已弹出。

>>> request.session.flash('info message')
>>> request.session.pop_flash()
['info message']
>>> request.session.pop_flash()
[]

使用 session.peek_flash 方法

一旦一条或多条消息被添加到闪存队列中, session.flash() API session.peek_flash() API可以用来“扫视”那个队列。不像 session.pop_flash() ,队列不会从闪存弹出。

peek_flash(queue='')
>>> request.session.flash('info message')
>>> request.session.peek_flash()
['info message']
>>> request.session.peek_flash()
['info message']
>>> request.session.pop_flash()
['info message']
>>> request.session.peek_flash()
[]