iframe 中的通行密钥

为了在多个网域中提供顺畅的上下文相关身份验证,组织通常会将登录页面嵌入到 iframe 中。不过,在第三方框架内加载身份验证上下文会使用户面临严重的威胁,例如点击劫持(界面伪装)和未经授权的凭据创建。为缓解这些风险,浏览器默认会在跨源 iframe 中停用 WebAuthn。若要安全地解除此限制,需要采用主动的纵深防御协议。

确定威胁模型

在子框架内启用通行密钥 (WebAuthn) 之前,请了解您要防范的滥用情形:

  • 使用隐藏的 iframe 插播进行跟踪:攻击者通过可信网站上的广告或 widget 从自己的网域触发 WebAuthn 提示,诱骗用户在看不到上下文的情况下授权通行密钥。这会将用户的身份与攻击者控制的账号相关联,以窃取数据。
  • 视觉叠加层和点击劫持(界面伪装):恶意父网页使用标准 CSS 将身份验证 iframe 呈现为不可见,并叠加一个虚假界面元素来窃取触发身份验证流程的点击。如果用户不慎完成提示,则可能会导致会话劫持或强制执行未经授权的操作。

为应对这些威胁,请遵循以下最佳实践:

对于顶级文档(顶部框架):

对于嵌入式文档 (iframe):

对于这两个文档:

使用权限政策启用委托

浏览器默认会阻止对跨源 iframe 中的 WebAuthn 的访问。权限政策是一种统一的 Web 平台机制,可让顶级文档将这些强大的功能明确委托给特定的可信第三方来源。

功能令牌

WebAuthn 使用两种不同的令牌:

  • publickey-credentials-get:授予通行密钥登录流程的授权 (navigator.credentials.get())。
  • publickey-credentials-create:授予通行密钥注册流程 (navigator.credentials.create()) 的授权。

启用要求

若要启用这些功能,父服务器响应和客户端标记必须保持一致:

Permissions-Policy: publickey-credentials-get=(self "https://embedded-auth.example.com")

权限政策:publickey-credentials-get 兼容性

Browser Support

  • Chrome: 88.
  • Edge: 88.
  • Firefox: not supported.
  • Safari: not supported.

Source

权限政策:publickey-credentials-create 兼容性

Browser Support

  • Chrome: 88.
  • Edge: 88.
  • Firefox: not supported.
  • Safari: not supported.

Source

  • HTML allow 属性:在 HTML 标记中,<iframe> 元素还必须声明它启用了该功能。
<iframe src="https://embedded-auth.example.com?nonce=deadbeef12345678&client=https%3A%2F%2Fembedded-auth.example.com" allow="publickey-credentials-get"></iframe>

iframe allow="publickey-credentials-get" 兼容性

Browser Support

  • Chrome: 84.
  • Edge: 84.
  • Firefox: 118.
  • Safari: not supported.

iframe allow="publickey-credentials-create" 兼容性

Browser Support

  • Chrome: not supported.
  • Edge: not supported.
  • Firefox: 123.
  • Safari: not supported.

启用分区第三方 Cookie

为确保身份验证流程可靠,必须在嵌入式跨源 iframe 中建立并维护会话。随着现代浏览器过渡到严格的第三方 Cookie 限制,标准持久性机制通常默认会被阻止,可能需要调用 Storage Access API 才能获得访问权限。

为了缓解这些障碍,请使用 SameSite: NoneSecurePartitioned 属性配置会话 Cookie。这种统一的平台机制可确保 iframe 内的状态保持不变,同时遵守浏览器级隐私权控制。

设置 SameSite: None

SameSite: None 明确标记了用于跨网站访问的 Cookie,允许在从第三方上下文(例如 iframe)发出的请求中发送该 Cookie。此属性是 Cookie 在跨源场景中正常运行的前提条件,但必须与 Secure 属性结合使用才能被现代浏览器接受。

设置 Partitioned

Partitioned 属性可让 Cookie 选择加入 CHIPS(Cookies Having Independent Partitioned State,即具有独立分区状态的 Cookie),从而让 Cookie 能够针对每个顶级网站单独存储。这样可确保 Cookie 在特定的第三方 iframe 上下文中保持可访问状态,从而实现持久的会话状态,而无需启用跨网站跟踪。用户必须在每个不同网站上重新登录才能使用嵌入内容。

使用内容安全政策保护端点

权限政策决定了您的 iframe 是否可以运行 WebAuthn,而内容安全政策 (CSP) 则决定了可以托管您的 iframe。

对于身份验证端点,至关重要的是确保只有授权的合作伙伴网站或您自己的媒体资源可以加载登录子框架,从而在未经授权的点击劫持尝试甚至加载界面之前就将其关闭。

使用 frame-ancestors

frame-ancestors 指令用于定义可嵌入您网站的有效父网页。通过向此指令添加网域,您可以允许允许嵌入登录子框架的网域。

Content-Security-Policy: frame-ancestors 'self' https://parent-site.example.com;

内容安全政策:frame-ancestors 兼容性

Browser Support

  • Chrome: 40.
  • Edge: 15.
  • Firefox: 58.
  • Safari: 10.

Source

设置 X-Frame-Options

旧版 X-Frame-Options 标头支持类似功能,但仅支持二进制选项(DENYSAMEORIGIN)。如果浏览器不支持 CSP,请同时设置 CSP frame-ancestorsX-Frame-Options: DENY。在支持 CSP 的情况下,系统始终会优先考虑 CSP。

X-Frame-Options: DENY

X-Frame-Options 兼容性

Browser Support

  • Chrome: 4.
  • Edge: 12.
  • Firefox: 4.
  • Safari: 4.

Source

信任,但要进行服务器端验证

浏览器的客户端检查会评估意图和权限,但服务器才是信任的最终仲裁者。在信赖方 (RP) 服务器上验证响应,以确保上下文有效且已签名。

客户端数据载荷

WebAuthn 客户端数据包含专门设计的参数,可帮助您验证在 iframe 中发出的请求的上下文:

  • crossOrigin(布尔值):指示 WebAuthn API 是否在跨源 iframe 内调用。如果您的架构依赖于 iframe,则服务器必须强制执行此标志为 true
  • topOrigin(字符串):顶级浏览上下文的来源(浏览器地址栏中显示的内容)。服务器必须根据已知且已获授权的父来源列表对此进行验证。

验证核对清单

如需在服务器上验证身份验证器响应,请执行以下步骤:

  1. 解析并解码来自身份验证器响应的已签名 collectedClientData
  2. 确保 type 与典礼(webauthn.getwebauthn.create)相匹配。
  3. 验证用户存在性和签名。
  4. 如果请求原本应来自 iframe 结构:
    • 强制执行 crossOrigin === true
    • 强制要求 topOrigin 与您已获授权的父源站列表相匹配。

使用 postMessage() 安全地建立会话

为了可靠地建立会话,iframe 必须使用 postMessage() 将身份验证令牌传递回父网页,以便父网页在其自己的第一方环境中管理会话状态。

安全工作流

如需建立安全会话,请按以下工作流程操作:

  1. 确保 iframe src 网址包含 nonceorigin 查询参数:
    • nonce 使用随机值。nonce 用作安全验证令牌,以确保从 iframe 收到的身份验证令牌与父网页发起的特定会话合法匹配。
    • 使用父框架网域作为 originorigin 参数用于指定父网页的来源,使 iframe 能够安全地识别其嵌入的授权上下文。
  2. iframe 通过其自己的服务器完成 WebAuthn 身份验证。
  3. iframe 服务器会签发包含 nonce 的令牌(例如 JWT),并将其转发给父网页。

    // Extract nonce and origin from the URL params
    const urlParams = new URLSearchParams(window.location.search);
    const nonce = urlParams.get('nonce');
    const origin = urlParams.get('origin');
    if (!nonce || !origin) {
      alert('Nonce or origin is missing in the URL');
      return;
    }
    
    // Create a JWT
    const response = await post('/createToken', { nonce, origin });
    const token = response.token;
    
    // Post the JWT to the parent frame
    window.parent.postMessage({ token }, origin);
    
  4. 父网页会监听 message 事件,验证发送者来源,并验证令牌。

    window.addEventListener("message", (event) => {
      if (event.origin !== "https://embedded-auth.example.com") return;
      // Verify the received JWT
      const result = await post('/verifyIdToken', {
        token: event.data.token,
        origin: provider.origin,
      });
    });
    
  5. 如果 JWT 验证成功,父网页会保留会话。

发件人和收件人共同承担安全责任:

  • 发送方 (iframe):发送消息时,请务必指定严格的目标来源(切勿使用 "*")。
  • 接收方(父级):接收消息时,始终验证 event.origin 以防止来源欺骗。

总结

安全使用 iframe 的关键在于:使用权限政策进行启用、使用 CSP 进行限制、使用分区第三方 Cookie 实现会话持久性、对客户端上下文进行服务器端验证,以及使用 postMessage() 实现情境感知会话切换。

如需详细了解相关主题,请关注 Google 的 Chrome 开发者博客,并浏览 Chrome 开发者身份文档中的更多资源。