☰
Remark42 多域名部署指南:配置跨域嵌入的评论认证与 Cookie 会话保持
2026/10/13 10:50:40 网站建设 项目流程
  • 后端
  • 前端

【免费下载链接】remark42

comment engine

项目地址:https://gitcode.com/gh_mirrors/re/remark42
点击查看免费下载

导读

本文讲解如何让单个 Remark42 实例同时服务于多个域名:宿主页面在example1.org、example2.org上嵌入评论组件,而 Remark42 自身运行在remark.example.com。文章以官方手册为主线,结合仓库源码(后端参数解析、认证库 Cookie 写入、前端 widget 的 Cookie 生成逻辑)与真实浏览器实测结果,给出推荐配置组合,并解释ALLOWED_HOSTS、AUTH_SEND_JWT_HEADER、AUTH_SAME_SITE三个参数在第三方 Cookie 时代各自的真实作用与安全代价。读完你既能照抄可运行的配置,也能理解"为什么这样配"的底层机制。

当前哪些认证方式在跨域场景下不工作?

在跨域嵌入场景(Remark42 所在域名 ≠ 宿主页面域名)中,认证能力取决于认证方式本身是"在 iframe 内完成"还是"在独立顶层页面完成":

  • OAuth 类认证(GitHub、Google 等)无法在非原始域名上工作。OAuth 流程在一个弹窗(popup)中完成,弹窗是独立顶层页面,其设置的 Cookie 属于 Remark42 自己的域名,嵌入的 iframe 永远看不到它。因此这类认证被限制在 Remark42 安装域名及其子域上。
  • Telegram、Email 与匿名认证可以在允许的 HTTPS 嵌入域名上工作,前提是设置AUTH_SEND_JWT_HEADER=true。这三类认证由 widget 在 iframe 内部完成,配合前端写入的分区 Cookie,可以跨域保持会话。

从源码看,认证令牌的传递确实依赖响应头与 Cookie 两条通道:backend/app/cmd/server.go中SendJWTHeader参数负责"额外把 JWT 放进响应头",而backend/vendor/github.com/go-pkgz/auth/v2/token/jwt.go定义了默认的X-JWT响应头与XSRF-TOKENCookie 契约。

快速配置:两个参数即可上路

官方推荐的跨域配置为:

ALLOWED_HOSTS="'self',https://example1.org,https://example2.org" AUTH_SEND_JWT_HEADER=true

对应命令行参数形式为:

remark42 server --allowed-hosts="'self',https://example1.org,https://example2.org" --auth.send-jwt-header

各参数要点如下:

  • ALLOWED_HOSTS(--allowed-hosts):逗号分隔的域名白名单,直接决定 CSPframe-ancestors指令,即"允许哪些站点以 iframe 方式嵌入评论组件"。仓库中的参数声明见 backend/app/cmd/server.go(env-delim:",",逗号分隔)。
  • 'self':表示"Remark42 自身安装的域名"。保留它才能让remark42.example.com/web/这类页面正常工作,方便自测。
  • AUTH_SEND_JWT_HEADER(--auth.send-jwt-header):让服务器在设置 Cookie 之外,再把 JWT 通过X-JWT响应头送回前端;前端将其存入自己的客户端 Cookie。默认值为false。

关键提醒:启用AUTH_SEND_JWT_HEADER后,不再需要同时设置AUTH_SAME_SITE=none——这是本文档沿用多年后纠正的旧结论,下文用实测数据说明原因。

⚠️ 安全提示:启用该参数前务必阅读官方 安全考量章节:JWT 会存放在 JavaScript 可读的 Cookie 中,相比服务器设置的HttpOnlyCookie 增加了 XSS 暴露面。应在已具备较强 XSS 防护的站点上使用。

默认 Cookie 属性对照

以 Chromium 默认第三方 Cookie 策略下匿名登录实测(https、嵌入式 widget),启用/不启用AUTH_SEND_JWT_HEADER时浏览器 Cookie 容器内的差异如下:

namehttpOnlypartition keywritten by
JWTyesnonethe server
XSRF-TOKENnononethe server
JWTnothe embedding sitethe widget
XSRF-TOKENnothe embedding sitethe widget
  • 启用AUTH_SEND_JWT_HEADER后,表中前三行齐全:服务器写入无分区键的HttpOnly JWT与XSRF-TOKEN,同时 widget 在嵌入站点分区内写入自己的JWT与XSRF-TOKEN。
  • 不启用时,Cookie 容器中只有 widget 自己写入的分区 Cookie 对。

实测表明:登录、发帖、刷新页面在"宽松浏览器"和"拦截第三方 Cookie 的浏览器"中两种情况都能工作。该设置真正增加的是第一行的无分区HttpOnly JWT——以第三方 Cookie 身份投递到所有白名单域名(只要浏览器仍允许)。没有任何功能依赖它,因此保持默认(不开启)暴露面更小。

三个参数的技术原理

ALLOWED_HOSTS:CSP frame-ancestors

ALLOWED_HOSTS设置的是 CSP 的frame-ancestors。

AUTH_SAME_SITE:服务器 Cookie 的 SameSite 属性

AUTH_SAME_SITE(--auth.same-site)设置服务器所写 Cookie 的 SameSite,解析函数在 backend/app/cmd/server.go。none允许这些 Cookie 在 Remark42 展示的任何域名上被设置。

default并不等于Lax。default表示 Remark42 完全不发出SameSite属性,把决定权交给浏览器,而不同浏览器行为不同:Chromium 把缺失属性视为Lax,从而拒绝跨站写入 Cookie;Firefox 则直接接受。这个差异不是细节——它决定了读者最终拿到下面两组 Cookie 中的哪一组。

SameSite=None单独并不够,配AUTH_SEND_JWT_HEADER则不再必要

拦截第三方 Cookie 的浏览器会丢弃 Remark42 为其他域名的读者设置的 Cookie,无论其SameSite取值如何——除非该 Cookie 被显式标记为Partitioned(CHIPS),而服务器设置的 Cookie 并没有分区标记。

AUTH_SEND_JWT_HEADER=true正是补上这一环的机制:

  1. 令牌通过X-JWT响应头返回(仓库前端常量定义见 frontend/apps/remark42/app/common/fetcher-core.ts,请求头组装逻辑见同文件authHeaders);
  2. widget 把令牌写入自己的 Cookie——从嵌入式 iframe 内部写入,标记为SameSite=None; Secure; Partitioned;
  3. 浏览器将该 Cookie 关联到"嵌入站点"这个分区键,刷新后原样送回。

这个 Cookie 完全是 widget 自己的行为,与AUTH_SAME_SITE无关——后者只影响服务器设置的那一对 Cookie。前端生成这些属性的代码在 frontend/apps/remark42/app/common/cookie-format.ts:跨域 + https 时返回{ path: '/', sameSite: 'None', secure: true, partitioned: true };同源时返回SameSite=Strict。IS_THIRD_PARTY的判定(比较window.parent.location.host与自身 host)见 frontend/apps/remark42/app/common/constants.ts。

唯一例外是Firefox 的"阻止所有第三方 Cookie"(block all third-party cookies):该模式连分区 Cookie 一并丢弃,因此该标志在 Firefox 上此模式无效,且没有任何配置能绕过。

补充一点:浏览器若拒绝一个不带SameSite=None的跨站Set-Cookie,是直接拒绝,而不是"以更严格属性接收"。所以在默认设置下,服务器的 Cookie 对在 Cookie 容器中是缺席的,而非带着更严格属性存在。

注意:以上机制适用于 Email、Telegram 与匿名认证——它们由 widget 在 iframe 内完成。它救不了 OAuth:OAuth 在作为顶层页面的弹窗中完成,其 Cookie 属于 Remark42 域名,嵌入式 iframe 永远看不到。

各浏览器的真实行为(实测矩阵)

该矩阵在真实域名、真实证书下测得:Remark42 在一个可注册域,宿主页面在另一个可注册域,登录后刷新页面。Chrome 与 Firefox 通过 Playwright 驱动,Safari 通过其自身的 WebDriver(Safari 列是 Safari 本身,而非近似值)。每个"blocked"列都用对照 Cookie 验证过:从 widget 帧内写入的普通第三方 Cookie 必须被丢弃,否则该次运行并未真正拦截任何东西,测试不成立。

configurationChrome, defaultChrome, third-party cookies blockedFirefox, defaultFirefox, "block all third-party"Safari
neither setfailsfailsworksfailsfails
AUTH_SEND_JWT_HEADERonlyworksworksworksfailsworks
AUTH_SAME_SITE=noneonlyworksfailsworksfailsfails
bothworksworksworksfailsworks

矩阵中有四点值得单独强调:

1. Firefox 什么都不配置也能保持登录。Firefox 的 Total Cookie Protection 会把服务器的无属性 Cookie 归入嵌入站点的分区,而不是拒绝它,因此在默认设置下,跨域刷新后 Email、Telegram、匿名认证在原生 Firefox 上依然存活。其余被测浏览器都不这样:Chrome 默认不发送SameSite属性并按Lax处理,Safari 直接按第三方 Cookie 拦截。不要把这行解读为推荐——它只在一个引擎上成立,读者一旦开启"阻止所有第三方 Cookie"立即失效。

2. Safari 无需任何配置就已经打破了旧的配置配方。它默认拦截第三方 Cookie,却仍然接受Partitioned,所以仅设AUTH_SAME_SITE=none在 Safari 上早已对所有读者失效。这不是未来才会发生的弃用,而是现在的事实。开启头部标志后,widget 自己的分区 Cookie 在帧内可读,会话得以跨刷新存活。

3. Firefox 以另一条更脆弱的路径达到"works"。Chrome 与 Safari 拒绝服务器无SameSite属性的 Cookie,这给 widget 自己写入分区 Cookie 对腾出了空间;Firefox 则接受该 Cookie,又因为服务器JWT是HttpOnly的,浏览器不允许 widget 的脚本覆盖同名 Cookie(JavaScript 设置的 Cookie 不能替换同名的HttpOnlyCookie)。所以在 Firefox 上,即使开了头部标志,会话仍然依赖一个普通的、无分区的第三方 Cookie——读者一旦拦截第三方 Cookie,会话立刻消失。

4. 没有任何配置能在 Firefox 的"阻止所有第三方 Cookie"下存活。该模式连分区 Cookie 一并丢弃,Partitioned这个逃生通道也不适用。开启该模式的读者无法在嵌入式 widget 上保持登录,Remark42 对此无能为力。

两组参数的全部组合清单

  • 默认(两参数都不动):评论可在任意域名显示;认证在任意域名都不可用——除了 Remark42 安装域名及其子域,以及默认设置下的 Firefox(原因见上:Firefox 将服务器无属性 Cookie 存入按站点分区而非拒绝,跨域刷新后 Email/Telegram/匿名认证存活;其余被测浏览器一律跨站拒绝该 Cookie)。
  • 仅设ALLOWED_HOSTS:评论只在白名单域名显示;认证情况与默认一致(安装域名、子域、默认设置下的 Firefox 可用)。
  • 仅设AUTH_SAME_SITE=none:评论可在任意域名显示;认证在仍允许第三方 Cookie 的浏览器上可用,在拦截它们的浏览器上失效。
  • 同时设ALLOWED_HOSTS与AUTH_SAME_SITE=none:评论只在白名单域名显示,认证限制同上。
  • ALLOWED_HOSTS+AUTH_SEND_JWT_HEADER=true,AUTH_SAME_SITE保持默认(推荐组合):评论只在白名单域名显示;Email、Telegram、匿名认证在除 Firefox"阻止所有第三方 Cookie"以外的所有第三方 Cookie 策略下都能跨刷新存活(该模式无配置可救)。额外叠加AUTH_SAME_SITE=none不会改变读者能否保持登录,只是让服务器无分区 Cookie 在浏览器仍接收它们的地方多出现一份而已。

安全考量总结

启用AUTH_SEND_JWT_HEADER意味着:

  • XSS 暴露增加:JWT 存放在 JavaScript 可读的 Cookie 中,不同于服务器HttpOnlyCookie(官方参数文档的完整说明见 安全考量章节)。
  • 缓解手段(由仓库前端实现保证):同源时SameSite=Strict(防止 Cookie 被跨站携带);跨域嵌入时SameSite=None; Secure; Partitioned(分区键为嵌入的顶层站点,不同站点拿到的是互不可达的独立 Cookie,但同一站点下的页面与子域共享);HTTPS 下自动附加Secure;配合 XSRF Double Submit Cookie 模式(XSRF 值与 JWT ID 对应)。
  • 官方明确仅在以下场景启用:确实需要跨域认证支持;理解并接受 XSS 风险上升;站点已实施强 XSS 防护。参数默认值为false,见 参数表。

一句话结论

要让单个 Remark42 实例服务多个域名,正确姿势是:ALLOWED_HOSTS列出全部嵌入域名(含'self'),开启AUTH_SEND_JWT_HEADER,不要再叠加AUTH_SAME_SITE=none。这套组合在 Chrome(含拦截第三方 Cookie 与无痕模式)、Safari、默认 Firefox 上均能保持登录;唯一无法覆盖的是 Firefox 的"阻止所有第三方 Cookie",那是浏览器层面放弃所有第三方 Cookie(含分区 Cookie)的结果,任何配置都无法挽救。

  • 后端
  • 前端

【免费下载链接】remark42

comment engine

项目地址:https://gitcode.com/gh_mirrors/re/remark42
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询