同域不同端口的CherryPy会话

4 投票
2 回答
1821 浏览
提问于 2025-04-15 21:21

下面是一个脚本,它会启动两个子进程,每个进程都是一个CherryPy应用(你可以按Ctrl+C或者你系统上对应的组合键来结束这两个进程)。如果你用CP 3.0运行它(记得修改“StartServer”中的3.0/3.1特定行),然后访问:

http://localhost:15002/

...你会看到一个空的字典。接着访问:

http://localhost:15002/set?val=10

http://localhost:15002/

...你会看到字典里有了新内容。然后访问:

http://localhost:15012/

...再回到

http://localhost:15002/

...你会发现没有任何变化。

如果你用CP 3.1试试同样的操作(记得“StartServer”中的那些行!),在最后一步时,字典又变成空的了。这种情况在Windows和Debian上都发生,Python 2.5和2.6也是如此。

你可以尝试各种方法:换成文件存储,分开存储路径……唯一的区别是会出现会话合并,而不是被清空。我也看过另一篇帖子,里面有建议把会话工具的配置键放在应用的配置中,而不是全局配置中,但我觉得这和这里的用法无关,因为这些应用是独立运行的。

我该怎么做才能让独立的CherryPy应用不互相干扰呢?

注意:我最开始在CherryPy邮件列表上问过这个问题,但还没有得到回复,所以我在这里试试。希望这样没问题。

import os, os.path, socket, sys
import subprocess
import cgi

import cherrypy

HTTP_PORT = 15002
HTTP_HOST = "127.0.0.1"

site1conf = {
    'global' : {
        'server.socket_host' : HTTP_HOST,
        'server.socket_port' : HTTP_PORT,
        'tools.sessions.on' : True,
#        'tools.sessions.storage_type': 'file',
#        'tools.sessions.storage_path': '1',
#        'tools.sessions.storage_path': '.',
        'tools.sessions.timeout' : 1440}}

site2conf = {
    'global' : {
        'server.socket_host' : HTTP_HOST,
        'server.socket_port' : HTTP_PORT + 10,
        'tools.sessions.on' : True,
#        'tools.sessions.storage_type': 'file',
#        'tools.sessions.storage_path': '2',
#        'tools.sessions.storage_path': '.',
        'tools.sessions.timeout' : 1440}}


class Home(object) :

    def __init__(self, key):
        self.key = key

    @cherrypy.expose
    def index(self):
        return """\
<html>
<body>Session:
<br>%s
</body>
</html> """ % cgi.escape(str(dict(cherrypy.session)))

    @cherrypy.expose
    def set(self, val):
        cherrypy.session[self.key.upper()] = val
        return """\
<html>
<body>Set %s to %s</body>
</html>""" % (cgi.escape(self.key), cgi.escape(val))

def StartServer(conf, key):
    cherrypy.config.update(conf)

    print 'Starting server (%s)' % key
    cherrypy.tree.mount(Home(key), '/', {})

    # Start the web server.
    #### 3.0
    # cherrypy.server.quickstart()
    # cherrypy.engine.start()
    ####

    #### 3.1
    cherrypy.engine.start()
    cherrypy.engine.block()
    ####

def Main():
    # Start first webserver
    proc1 = subprocess.Popen(
        [sys.executable, os.path.abspath(__file__), "1"])
    proc2 = subprocess.Popen(
        [sys.executable, os.path.abspath(__file__), "2"])

    proc1.wait()
    proc2.wait()

if __name__ == "__main__":

    print sys.argv

    if len(sys.argv) == 1:
        # Master process
        Main()
    elif(int(sys.argv[1]) == 1):
        StartServer(site1conf, 'magic')
    elif(int(sys.argv[1]) == 2):
        StartServer(site2conf, 'science')
    else:
        sys.exit(1)

2 个回答

1

简而言之: 把CherryPy的配置参数 tools.sessions.name 改成每个应用独特的名字。

详细说明:

我知道这个问题很老了,但我觉得答案很简单。写下来是为了帮助以后搜索的人。

CherryPy使用一个cookie来查找会话。默认情况下,这个cookie叫“session_id”,它的值是一个随机的十六进制字符串。如果CherryPy收到一个它不认识的session_id,它会生成一个新的session_id。这是为了防止会话固定攻击。

当你在同一个域名下有两个应用时,它们都使用相同的cookie名字(也就是“session_id”),但彼此都不认识对方的session_id,所以它们会用新的session_id覆盖掉对方的。这就导致你从一个应用切换到另一个应用时,原来的会话失效了。

解决方法很简单:在CherryPy的配置中,你可以通过设置 tools.sessions.name 为其他名字来覆盖session_id,比如“myapp_session_id”和“myotherapp_session_id”。

你需要确保会话存储是分开的,这一点你已经正确识别出来了。

根据上面的例子,你可以这样做:

site1conf = {
    'global': {
        'server.socket_host': HTTP_HOST,
        'server.socket_port': HTTP_PORT,
        'tools.sessions.on': True,
        'tools.sessions.storage_type': 'file',
        'tools.sessions.storage_path': '/tmp/site1_sessions/',
        'tools.sessions.name': 'site1_session_id',
        'tools.sessions.timeout': 1440
    }
}
site2conf = {
    'global': {
        'server.socket_host': HTTP_HOST,
        'server.socket_port': HTTP_PORT + 10,
        'tools.sessions.on': True,
        'tools.sessions.storage_type': 'file',
        'tools.sessions.storage_path': '/tmp/site2_sessions/',
        'tools.sessions.name': 'site2_session_id',
        'tools.sessions.timeout': 1440
    }
}

注意:在我自己使用CherryPy 10.0.0的应用中,我在应用级别和路径级别都使用了这个配置选项。我没有在旧版本的CherryPy上测试过,但从源代码来看,这个功能已经可以用了十多年了。

自从写下这些,我还为CherryPy的文档贡献了一些更新,相关内容在这里: http://docs.cherrypy.org/en/latest/pkg/cherrypy.lib.html#session-fixation-protection

3

存储会话标识符的 cookie 是和主机绑定的,而不是主机加端口。当你第一次访问一个网站时,你会在 3.1 版本中获得一个新的会话 ID(但在 3.0 版本中没有),然后你可以填充会话数据并查看它。接着,你用这个会话 ID 访问其他端口,但现在这个 ID 就失效了(我相信你可以在调试模式下的日志中看到这一点)。所以服务器会给你发送一个新的会话 ID。然后你再返回到第一个服务器,这时你的标识符又失效了,所以你又会得到一个新的。当然,这个新标识符的会话里没有任何数据。

更新:根据 RFC 2109 的第 4.3.1 节,关于 Set-Cookie 的解释是:

用户代理会分别跟踪来自每个源服务器(通过名称或 IP 地址和端口区分)的 Set-Cookie 响应头中到达的状态信息。

不过,标准的解释并不是那么明显。这里引用了 Firefox 跟踪器中相关的一个 问题

关于 cookies 有两个 RFC,2109(用于 set-cookie)和 2965(用于 set-cookie2)。

在 RFC 2109 的第 4.3.1 节中,关于 Set-Cookie 的解释中提到
“域名默认为请求主机。”在第 2 节的术语中提到“请求主机和请求 URI 指的是客户端发送给服务器的值,分别是主机(但不包括端口)和 HTTP 请求行的绝对 URI(http_URL)的 abs_path 部分。请注意,请求主机必须是完全合格的主机名。”在 RFC 2965 的第 3.3.1 节中,关于 Set-Cookie2 的解释中提到“域名默认为有效的请求主机。”它还提到“端口的默认行为是 cookie 可以返回给任何请求端口。”在第 1 节的术语中提到“请求主机和请求 URI 指的是客户端发送给服务器的值,分别是主机(但不包括端口)和 HTTP 请求行的绝对 URI(http_URL)的 abs_path 部分。”(和 RFC 2109 一样)

我对这些的理解是,除非 set-cookie2 头明确指定了端口号,否则不应该使用端口号来记录 cookie 域名。

撰写回答