You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python Aiohttp同域多端口Cookie行为异常问题求助

Aiohttp跨端口Cookie传递问题:直接传cookies对象失败,转字典却成功的原因

我在用Aiohttp处理Cookie时遇到了一个奇怪的问题:Aiohttp的CookieJar类遵循RFC6265规范,该规范明确了两点:

  • 给定主机的Cookie应跨该主机的所有端口共享
  • Cookie不提供端口隔离,同一服务器不同端口的服务可读取同一Cookie

但我搭建了两个运行在localhost不同端口的Aiohttp服务:一个提供登录接口并返回Cookie,另一个的接口需要验证Cookie才能访问。测试时发现,直接传递登录响应的cookies对象访问受保护接口返回401,而将Cookie转为键值对字典传递则返回200,请问这是什么原因?

以下是使用aiohttp、pytest、pytest-aiohttp编写的测试用例:

import functools

import pytest
from aiohttp import web


pytestmark = pytest.mark.asyncio


def attach_session(f):
    @functools.wraps(f)
    async def wrapper(request: web.Request):
        session_id = request.cookies.get("testcookie")
        request["mysession"] = session_id

        response = await f(request)
        response.set_cookie("testcookie", session_id)
        return response

    return wrapper


def is_logged_in(f):
    @functools.wraps(f)
    @attach_session
    async def wrapper(request: web.Request):
        session = request["mysession"]
        if not session:
            raise web.HTTPUnauthorized
        return await f(request)

    return wrapper


async def login(_: web.Request):
    response = web.Response()
    response.set_cookie("testcookie", "somerandomstring")
    return response


@is_logged_in
async def some_endpoint(request: web.Request):
    return web.Response(text="sweet")


@pytest.fixture
def auth_client(event_loop, aiohttp_client):
    app = web.Application()
    app.router.add_post("/login", login)
    return event_loop.run_until_complete(aiohttp_client(app))


@pytest.fixture
def core_client(event_loop, aiohttp_client):
    app = web.Application()
    app.router.add_get("/some_endpoint", some_endpoint)
    return event_loop.run_until_complete(aiohttp_client(app))


async def test_login(auth_client):
    resp = await auth_client.post("/login")
    assert resp.status == 200
    assert resp.cookies.get("testcookie").value == "somerandomstring"


async def test_some_endpoint_anonymous(core_client):
    resp = await core_client.get("/some_endpoint")
    assert resp.status == 401


async def test_some_endpoint_as_logged_in(auth_client, core_client):
    resp1 = await auth_client.post("/login")
    resp2 = await core_client.get("/some_endpoint", cookies=resp1.cookies)
    assert resp2.status == 401


async def test_some_endpoint_as_logged_in_again(auth_client, core_client):
    resp1 = await auth_client.post("/login")

    _cookie = list(resp1.cookies.values())[0]
    resp2 = await core_client.get(
        "/some_endpoint", cookies={_cookie.key: _cookie.value}
    )
    assert resp2.status == 200

问题根源:Cookie对象附带的端口属性导致匹配失败

当你从auth_client的登录响应中拿到resp1.cookies时,这里面的每个Cookie都是包含完整属性的Morsel对象(来自http.cookies.SimpleCookie),其中会自动带上port属性,值为auth_client服务运行的端口。

而aiohttp的客户端在发送请求时,会严格按照Cookie的属性进行匹配:如果Cookie的port属性和目标请求的端口不一致,这个Cookie就不会被发送到服务器。因为core_client运行在另一个端口,所以直接传递resp1.cookies时,这个带端口的Cookie被客户端过滤掉了,后端服务拿不到testcookie,自然返回401。

而当你把Cookie转为{key: value}的字典传递时,相当于手动构造了只包含键值对的Cookie信息,没有附带port等限制属性。此时aiohttp客户端会根据RFC6265规范,自动允许该Cookie跨localhost的不同端口共享,所以Cookie会被正常发送到core_client的服务,后端能验证通过,返回200。

你可以在测试中打印resp1.cookies["testcookie"]["port"],就能看到它的值是auth_client的端口,和core_client的端口不一致,这就是问题的核心。

内容的提问来源于stack exchange,提问作者Clément de Nailly

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 10:05:24