Odoo 11 SameSite Cookie 在支付回跳后被清空
明确结论
不要直接把 Odoo 11 使用的 Werkzeug 升级到最新版。更稳妥的处理方式是为 Odoo 的 session_id Cookie 添加以下属性,并确保站点始终使用 HTTPS:
SameSite=None; Secure
如果支付平台支持调整回跳方式,优先采用顶层页面的 GET 跳转,并通过服务端 webhook 确认支付结果。只有支付平台必须通过跨站 POST、iframe 等方式返回时,通常才需要设置 SameSite=None。
Chrome 中显示“Cookie 为空”,并不一定代表 Cookie 已被删除。更常见的情况是,浏览器受 SameSite 策略限制,没有在这次跨站请求中发送 Cookie。Odoo 随后创建了新 session,看起来就像原会话被清空了。
原因
旧版 Odoo 11 和 Werkzeug 创建 session Cookie 时,通常不会显式设置 SameSite。新版 Chrome 会把未声明 SameSite 的 Cookie 当作 SameSite=Lax 处理。
不同请求方式的表现如下:
- 跨站顶层
GET导航通常会携带SameSite=LaxCookie。 - 从支付域名跨站
POST回 Odoo 时,通常不会携带该 Cookie。 - iframe 和第三方上下文受到的限制更严格。
- Cookie 设置为
SameSite=None时,必须同时设置Secure,否则新版 Chrome 会拒绝该 Cookie。 SecureCookie 只能通过 HTTPS 正常发送。
因此,支付平台通过跨站 POST 返回时,Odoo 可能收不到原来的 session_id,并把该请求识别为新会话。
推荐方案:在反向代理层补充 Cookie 属性
如果 Odoo 前面部署了 Nginx,建议优先在代理层处理。这样既不需要修改旧版 Werkzeug,也能避开升级依赖可能造成的兼容问题。
支持 proxy_cookie_flags 的 Nginx 可以使用以下配置:
location / {
proxy_pass http://odoo_upstream;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cookie_flags session_id secure httponly samesite=none;
}
重新加载 Nginx 后,Odoo 返回的响应头应该类似:
Set-Cookie: session_id=...; Path=/; HttpOnly; Secure; SameSite=None
如果当前 Nginx 不支持 proxy_cookie_flags,也可以通过 proxy_cookie_path 追加属性。不过,这种方式会影响所有匹配到的 Cookie,使用前需要确认站点中各 Cookie 的作用范围:
location / {
proxy_pass http://odoo_upstream;
proxy_cookie_path / "/; Secure; HttpOnly; SameSite=None";
}
修改后先检查配置,再重新加载 Nginx:
nginx -t
systemctl reload nginx
实际服务名称可能随操作系统和部署方式而不同。
应用层兼容方案
如果无法在反向代理层修改响应头,可以等 Odoo 生成响应后、浏览器接收响应前,对 Set-Cookie 响应头进行兼容处理。
下面的代码不要求旧版 Werkzeug 支持 samesite 参数:
def patch_session_cookie(response, cookie_name='session_id'):
set_cookie_headers = response.headers.getlist('Set-Cookie')
if not set_cookie_headers:
return response
del response.headers['Set-Cookie']
cookie_prefix = cookie_name + '='
for value in set_cookie_headers:
if value.startswith(cookie_prefix):
lower_value = value.lower()
if 'samesite=' not in lower_value:
value += '; SameSite=None'
if 'secure' not in lower_value:
value += '; Secure'
response.headers.add('Set-Cookie', value)
return response
这段代码必须在 Odoo 和 Werkzeug 完成所有 response.set_cookie(...) 操作之后、WSGI 发送响应之前调用:
response = patch_session_cookie(response)
return response
除非已经确认当前 Werkzeug 的 Response.set_cookie() 支持 samesite 参数,否则不要使用下面的写法:
response.set_cookie(
'session_id',
session_id,
httponly=True,
secure=True,
samesite='None',
)
旧版 Werkzeug 可能无法识别 samesite 参数,并直接抛出 TypeError。生产环境中,最好将兼容逻辑放进 WSGI 中间件或可维护的补丁,避免临时修改在后续部署或升级时丢失。
更合适的支付回跳设计
如果支付平台允许配置,可以把支付流程拆成两条链路:
- 支付平台通过服务端 webhook 将支付结果通知 Odoo。
- 用户浏览器通过顶层
GET跳转回订单结果页。 - Odoo 根据订单号或交易号查询服务端已经确认的支付状态。
- 浏览器回跳参数只负责定位订单,不能作为支付成功的唯一依据。
例如:
GET /payment/return?transaction_ref=TX123
顶层跨站 GET 通常可以配合 SameSite=Lax 使用,其安全边界也比全站设置 SameSite=None 更清楚。
无论采用哪种方式,都不能只根据浏览器的回跳参数把订单标记为已支付。必须验证支付平台签名,或者通过服务端 webhook、支付查询接口确认结果。
排查与验证
先打开 Chrome DevTools 的 Network 面板,检查支付回跳请求:
- Request Headers 中有没有
Cookie: session_id=...。 - 回跳响应是否返回了新的
Set-Cookie。 Set-Cookie是否包含SameSite=None; Secure。- Console 或 Issues 面板是否提示 Cookie 因 SameSite 或 Secure 属性被阻止。
- 回跳 URL 是否使用同一主机名,例如有没有在
www.example.com和example.com之间切换。 - HTTP 与 HTTPS、Cookie 的
Domain和Path是否一致。
以下命令可以检查响应头,但只能确认服务器如何设置 Cookie,无法完整模拟浏览器的 SameSite 行为:
curl -I https://odoo.example.com/web/login
正常情况下,响应中应该包含类似内容:
Set-Cookie: session_id=...; Path=/; HttpOnly; Secure; SameSite=None
配置完成后,先清除测试浏览器中的旧 Cookie,重新登录,再完整执行一次支付流程。服务器配置发生变化后,已有 Cookie 不会自动获得新属性,必须等服务器重新发送 Set-Cookie。
注意事项
- Odoo 必须通过 HTTPS 对外提供服务,否则
SameSite=None; SecureCookie 无法正常工作。 - 如果由反向代理终止 TLS,需要正确传递
X-Forwarded-Proto,并确认 Odoo 已正确配置代理模式。 - 不建议只为一个 Cookie 参数就把 Werkzeug 升级到跨度很大的版本。Odoo 11 可能依赖旧版 API,应先在测试环境验证兼容性。
- 不要无条件把所有 Cookie 都改成
SameSite=None。最好只处理session_id,或者其他确实需要跨站发送的 Cookie。 SameSite=None会扩大 Cookie 在跨站请求中的发送范围。涉及状态修改的路由仍然需要保留 CSRF、防重放和支付签名校验。- 如果支付页在 iframe 中运行,浏览器的第三方 Cookie 限制仍可能阻止 Cookie。
SameSite=None无法绕过用户或浏览器禁用第三方 Cookie 的策略。 - 如果支付平台使用的是顶层
GET回跳,但 Cookie 仍未发送,应继续检查Domain、Path、HTTPS、主机名变化、Cookie 是否过期,以及响应中是否存在用于删除 Cookie 的Set-Cookie,不要只排查 SameSite。
备注:内容仅供参考。
