登录 Cookie 设置了 Domain=example.dev,于是所有匹配子域名都进入了同一信任范围。后来一个子域名交给第三方托管,会话设计没有变化,风险却变化了。Cookie 的共享范围应与实际控制范围一起审查。
本文以服务端会话为例,不讨论把所有用户资料写入 Cookie 的方案。会话 ID 应不可预测,并由服务端验证;属性设置不能修复弱随机令牌或缺失权限检查。
从一次登录请求追踪会话生命周期
用户提交凭据,服务端验证后创建新会话,浏览器保存符合规则的 Cookie,后续请求携带会话 ID,服务端读取会话并授权资源。任何一个步骤都有独立失败:浏览器可能拒绝属性组合,Cookie 可能不适用当前主机,会话可能已过期,用户也可能没有资源权限。看到 Set-Cookie 头不代表浏览器已经保存,更不代表后续请求一定得到授权。
会话值应是不含业务秘密的不可预测标识,服务器维护身份与状态。若选择签名令牌作为 Cookie 值,签名不等于加密,内容可能被持有者读取;撤销与过期也有不同实现成本。不要为了减少一次查询就把所有个人资料写入长期 Cookie。浏览器与服务端之间的令牌是能力载体,应该尽量小、有限且可失效。
登录成功与权限提升时重新生成会话标识,并使旧状态按策略失效,可以减少会话固定风险。匿名会话中的非敏感任务状态可以受控迁移,不把原标识直接提升为认证身份。密码验证、会话创建和浏览器存储三层一起测试,才能解释用户为何登录后仍然显示未登录。
匿名访问、认证成功、敏感操作、权限变化、退出和到期,各自对应会话动作。匿名状态可以没有认证 Cookie;登录后创建新标识;敏感操作要求额外验证;权限降低即时改变服务器授权;退出撤销并删除;到期拒绝旧值。把这些阶段写清楚,Cookie 属性才有相应语境,不会被误当成整个会话系统。
多个标签页共享浏览器 Cookie,但页面内存中的用户状态可能滞后。一个标签页退出后,另一个仍显示姓名,下一次操作必须由服务端拒绝失效会话,而不是依赖前端立即同步。可以增强跨标签通知改善体验,但它不是权限保证。客户端显示已登录与服务端允许操作是不同状态,后者始终由服务端判断。
权限提升后的会话轮换也要处理并发请求。旧请求尚在进行,新会话已创建,不能让旧响应重新写入旧 Cookie 覆盖新状态。具体实现需要根据框架与会话存储设计,至少测试登录、刷新和退出并发。不要自动在每个请求中续签而没有版本或撤销规则,否则退出后的后台请求可能重新建立会话。
浏览器储存多个同名 Cookie 时,服务端不应依赖“第一个值总是正确”。使用清楚名称与主机范围,并在迁移期间明确删除旧变体。由广域 Cookie 改为 host-only 是安全改善,但如果旧广域值未撤销,风险仍然存在。迁移既需要设置新属性,也需要处理旧服务器会话和旧浏览器存储。
验收保留旧令牌副本,退出后直接用受控客户端请求,确认服务器拒绝;再测试属性限制和浏览器回调。这样同时验证能力撤销与客户端发送规则。只在浏览器看到 Cookie 消失,无法证明复制凭据已经失效。会话安全需要生命周期与属性共同成立,缺少任何一项都不能由另外一项补救。
默认不共享往往更容易推理
Set-Cookie: __Host-session=opaque-value; Path=/; Secure; HttpOnly; SameSite=Lax
__Host- 前缀在支持它的浏览器中要求 Secure、Path 为根路径且不能包含 Domain。这样 Cookie 绑定当前主机,而不是默认扩展到全部子域。需要跨子域单点登录时,应设计明确流程,不应为了少写一次登录就无条件扩大所有 Cookie 的范围。
Path 影响浏览器向哪些路径发送 Cookie,但不是可靠权限边界。一个同源页面里的恶意脚本仍可能发起请求或操作同源资源,不能把 /admin Cookie 看作与其他同源代码完全隔离。
假设主应用与文档站共享父域名,文档站由第三方托管。设置父域 Domain 会扩大 Cookie 发送范围,即使文档站没有业务需求也可能接触会话。应检查子域名资产和托管关系,不仅检查当前 DNS。弃用子域名的接管风险也会影响共享设计,域名控制不是永远稳定的事实。
跨子域单点登录可以通过独立认证流程建立各自主机会话,不必依赖一个广域 Cookie。这样需要额外协议与跳转,但能让应用边界更明确。对于少量个人服务,重复登录可能比共享全部会话更简单安全。选择时比较用户任务与信任范围,不以“同一个公司域名”推导所有应用具有同样安全等级。
另一个边界是请求头预算。每个功能都新增一份较大的 Cookie,浏览器后续请求会一起承担发送成本,代理也可能拒绝过大的请求头。出现间歇登录失败时,先比较成功与失败请求的实际 Cookie 名称、作用域和总长度,不打印会话值。不要一遇到失败就延长期限;应删除无用途状态,或把大块业务数据移回服务端存储。各浏览器及代理限制不同,需要按支持链路验收。
HttpOnly 限制读取,不限制所有操作
HttpOnly 阻止页面 JavaScript 直接读取 Cookie,有助于减少令牌被窃取。它不能让 XSS 无害:恶意脚本仍可能利用用户浏览器调用已登录接口、读取页面数据或修改操作。
Secure 限制通过安全连接发送,生产环境应使用 HTTPS。HSTS 可以帮助协议升级,但要确认所有相关子域都具备 HTTPS 能力再考虑 includeSubDomains,错误强制可能使尚未准备好的服务不可访问。
SameSite 必须结合登录与嵌入场景
Lax 通常适合普通站点导航,Strict 更严格但可能影响从外站进入后的体验。None 用于某些跨站场景,并要求 Secure;浏览器的第三方 Cookie 政策还可能进一步限制它。
“站点”与“来源”不是相同概念。两个不同子域可能同站但不同源,因此 SameSite 不能替代同源策略或 CORS。跨站登录回调、嵌入式应用与支付跳转需要真实流程测试,不能只在同一标签页刷新验证。
外部身份提供商返回、支付完成返回和嵌入页面读取会话,都可能经过跨站路径。Strict 可能影响从外站进入时的首次会话携带,Lax 也不是对所有方法与场景都允许;None 仍可能被第三方限制。根据具体浏览器与协议测试,不能把一个本地同站刷新结果当成全部登录流程兼容证据。
认证状态参数与 CSRF 防护仍然独立需要。SameSite 是重要层,但不同子域可能同站,受损子域也可能发起同站请求。接口验证用户意图和授权,不只检查 Cookie 是否出现。HttpOnly 降低直接窃取风险,却不能阻止注入脚本借浏览器发请求,因此输出安全、CSP 与会话属性应互补。
测试环境域名和 HTTPS 也影响结果。localhost 的特殊处理可能与生产不同,开发时能够设置的 Cookie 不代表正式子域可用。使用受控 HTTPS 预览验证真正属性组合,避免为了本地便利删除生产 Secure。环境差异写在配置中,而不是让登录代码自动根据任意请求头决定安全性。
生命周期需要服务端配合
Max-Age 控制浏览器存储时间,服务端会话过期与撤销则控制是否继续接受令牌。用户退出时应撤销服务器状态,并用匹配的 Domain 与 Path 删除 Cookie;仅让浏览器移除而保持服务器令牌有效,不足以处理被复制的凭据。
权限提升或登录成功后可以轮换会话 ID,减少会话固定风险。长时间有效的会话应考虑闲置过期、设备撤销与敏感操作重新验证。不要把一份永久 Cookie 当成所有场景都适用的登录方案。
Cookie 的到期控制浏览器何时停止正常发送,服务端到期控制何时不再接受复制值。两边可以不同,但必须有意设计。退出时删除 Cookie 的名称、Domain 和 Path 应与设置时匹配,否则可能留下另一个同名变体。重复名称出现在不同路径时,后端解析可能产生歧义,应避免这种设计并测试实际请求。
服务器撤销可以按会话、设备或账户执行。修改密码是否终止其他会话、权限降低是否即时生效、敏感操作是否要求重新验证,需要明确规则。浏览器仍保存旧 Cookie 可以触发重新登录,但服务端不能因它尚未过期就继续授权。将失效原因区分过期、撤销和无效,便于日志诊断,同时不在公开错误中泄漏账户状态。
客户端时间可能不可靠,服务端应使用一致时间源判断会话寿命。闲置过期需要可信活动定义,不让后台轮询永远续命。绝对过期与闲置过期各有用途,长会话还需要设备撤销与再次验证。永久自动登录便利背后是长期凭据风险,不能靠一条 Max-Age 把所有场景统一处理。
隐私诊断不要变成会话复制库
日志中的完整 Cookie 可以被当作身份凭据使用,截断值也可能暴露敏感信息,不应默认记录。内部会话关联 ID 或受控不可逆摘要可以帮助定位,但仍需限制访问与保留,避免长期跨系统追踪。浏览器截图中也可能出现值,分享诊断前清理秘密。一个安全属性正确的 Cookie 若被日志保存,边界仍然被绕过。
异常登录监控可以记录会话创建、轮换、撤销与授权失败,不记录每次请求完整头。比对设备活动时承认网络地址与浏览器信息并不等于真实身份,不能单凭变化自动宣称账户被盗。高风险操作使用重新验证与服务端权限判断,不把风险分数替代明确授权。
从广域 Cookie 迁移时处理同名历史对象
旧应用把 session 设置在父域和根路径,新应用改为 host-only,却继续使用相同名称。浏览器可能同时保存两个对象,并在某些请求中发送同名值。服务器读取顺序并不构成可靠迁移规则,攻击者控制的子域也可能影响广域名称。迁移应使用明确的新名称和属性,撤销旧会话,并按旧 Domain 与 Path 删除旧对象。
删除操作需要匹配原作用域。只在当前主机发送一个过期 host-only Cookie,不会自动删除父域版本;不同路径的同名值也可能留下。建立旧属性清单,验证每种历史对象的退出路径。对于无法枚举的遗留属性,服务端首先停止接受旧凭据,客户端清理作为减少困惑的后续动作。能力失效比浏览器存储看起来干净更重要。
迁移期间旧请求可能完成并重新写入旧值。审查哪些响应会续期,禁止失效版本创建新会话。测试用受控延迟请求:请求开始后执行退出或迁移,再释放旧响应,确认不会恢复认证能力。即使浏览器收到旧 Cookie,服务端也应拒绝其已撤销状态。前端跨标签通知改善视觉同步,不承担这个保证。
将会话期限拆成绝对、闲置与敏感操作期限
绝对期限限制一个会话总体寿命,闲置期限限制没有有效活动的时间,敏感操作期限决定近期认证是否足够。后台轮询不一定应该算有效活动,否则一个开着的标签可以让会话永远续命。活动定义需要服务端规则,不能信任客户端任意声明“用户刚刚操作”。期限值根据风险与使用体验选择,不套用一份所有产品相同的配置。
修改支付信息或导出私人数据时,可能要求再次验证,普通阅读不一定需要。验证成功后的短期资格也应绑定正确会话与用途,不能变成永久管理员开关。权限降低以后,服务器授权即时以当前状态判断,不等待 Cookie 到期。会话代表身份上下文,权限与业务状态仍然可能变化。
退出设备与退出全部设备是不同动作。服务器可以维护会话级与账户级撤销条件,客户端说明实际影响范围。重新登录创建新会话,不重新激活已撤销记录。测试复制旧值在到期、撤销与权限变化后的请求,确认能力边界;不只是刷新页面观察头像消失。设备管理列表中的名称与网络信息是线索,不是绝对身份凭证。
区分同站、同源与受信任应用
两个子域可以属于同站,但不同 origin。SameSite 对这种请求的判断不等同于同源策略,受损的同站子域仍然可能发起有身份请求。广域 Cookie 还会扩大发送范围。产品若把第三方托管文档放在同一父域,应同时审查会话范围与 CSRF 防护,不认为不同端口或不同子域天然形成完整安全隔离。
Path 仅控制发送匹配,同源脚本仍可能调用其他路径的接口。需要隔离不可信内容时使用独立来源、正确上传类型与执行限制。HttpOnly 不允许脚本直接读取值,但注入脚本仍可借已登录浏览器操作资源。一个属性阻断某种能力,不表示攻击者失去所有能力,威胁模型应准确写出剩余影响。
跨子域单点登录可以建立各自主机会话,但认证回跳、状态验证与账户绑定都要遵循成熟协议。它不是简单把会话 ID 作为查询传给另一个域。选择独立会话增加流程成本,却能缩小共享凭据范围。少量服务是否值得单点登录,可以依据用户频率与信任关系决定,便利不自动优先于边界。
用受控 HTTPS 场景验证浏览器存储规则
测试准备根主机、另一个子域与外站来源,分别执行正常导航、跨站回调和嵌入请求。观察 Set-Cookie 是否被接受、后续是否发送以及服务端是否授权,三项分开记录。浏览器网络面板看到响应头,不证明 Cookie 已保存;存储面板有值,也不证明当前请求匹配发送条件。
本地特殊主机行为可能与生产不同,真实 HTTPS 预览能够暴露 Secure、Domain 和第三方策略问题。不同浏览器的隐私限制要按支持范围验收,不靠降低全站属性绕过。Cookie 被限制时,可以提供重新登录或独立打开的真实路径,而不是让用户重复点击无法完成的嵌入操作。
诊断记录应关联事件而非复制凭据
记录会话创建、轮换、撤销与授权结果,使用内部关联 ID 和必要时间。不要把完整 Cookie 写进日志,也不要让公开错误页返回会话数据库状态。诊断截图与导出的网络文件可能包含凭据,分享前清理。安全扫描检查日志和测试快照,用假值证明脱敏规则有效,不为了查漏再打印真实值。
会话问题的恢复记录说明是存储规则、服务器到期还是权限变化,避免统称“Cookie 丢了”。每项属性都能对应实际任务和失败样本,生命周期又能证明旧能力失效,这才比一条看起来安全的 Set-Cookie 更接近完整契约。
多个 Set-Cookie 不能按普通列表头合并
登录响应既设置会话,又清理一个旧 Cookie。代理把两条 Set-Cookie 头合成一条逗号分隔字符串,浏览器行为可能不再符合原协议。Set-Cookie 应逐项保留,不能套用普通列表头的合并工具;Expires 本身还可能含逗号,直接 split 也不是可靠解析。使用运行环境支持的 Cookie 头接口,并核对最终发出的响应。
验收用两个不同名称的假 Cookie,一个设置短期状态,一个删除历史状态,检查原始响应头与浏览器存储结果各自符合预期。只在应用内部打印 Headers 对象可能看不到经过代理后的形态。这个测试不需要真实会话值,也不应将会话凭据写入截图或快照;需要证明的是头的多值结构与对应存储行为。
通过浏览器与服务器两侧验收
检查 Cookie 是否出现在预期主机、路径和请求中,JavaScript 是否无法直接读取,以及跨站导航与回调是否正常。测试退出后旧令牌、不同子域、HTTP 入口与到期行为。
日志不要记录完整 Cookie,诊断可使用不可逆摘要或内部关联 ID,并严格控制保留。会话设计应可解释每个属性为什么存在、会影响哪个流程,以及遗漏后会出现什么风险;从网上复制一条 Set-Cookie 只能得到形式,得不到边界。
参考:Set-Cookie、OWASP 会话管理。