跳转到主要内容

category

了解如何使用 FedCM 进行可保护隐私的身份联合。

FedCM(联合凭据管理)是一种适用于联合身份服务(例如“使用以下账号登录”)的隐私保护方法;借助该方法,用户可以登录网站,而无需与身份服务或网站共享其个人信息。

如需详细了解 FedCM 用例、用户流和 API 路线图,请参阅 FedCM API 简介

您需要在 Chrome 中的 IdP 和 RP 上都拥有安全上下文(HTTPS 或 localhost),才能使用 FedCM。

在本地设置并运行服务器以调试您的 FedCM 代码。您可以在使用具有端口转发功能的 USB 线连接的 Android 设备上,在 Chrome 中访问此服务器

按照远程调试 Android 设备中的说明,您可以使用桌面设备上的开发者工具调试 Android 版 Chrome。

将 Chrome 配置为屏蔽第三方 Cookie 来模拟逐步淘汰第三方 Cookie 的情形

在实际强制执行 FedCM 之前,您可以先在 Chrome 上测试 FedCM 如何在不使用第三方 Cookie 的情况下工作。

如需阻止第三方 Cookie,请使用无痕模式,或者在 chrome://settings/cookies 或移动设备的桌面设备设置中选择“阻止第三方 Cookie”,或在移动设备上依次转到设置 > 网站设置 > Cookie

如需与 FedCM 集成,您可以为帐号列表断言发布以及可选的客户端元数据创建知名文件配置文件和端点

FedCM 会公开 JavaScript API,RP 可使用这些 API 通过 IdP 登录

为防止跟踪器滥用 API,必须通过 IdP 的 eTLD+1 的 /.well-known/web-identity 提供一个知名文件。

例如,如果 IdP 端点在 https://accounts.idp.example/ 下提供,它们必须在 https://idp.example/.well-known/web-identity 中提供知名文件以及 IdP 配置文件。下面是一个知名文件内容示例:

{
  "provider_urls": ["https://accounts.idp.example/config.json"]
}

JSON 文件必须包含 provider_urls 属性和一组 IdP 配置文件网址,这些网址可被 RP 在 navigator.credentials.get 中指定为 configURL 的路径部分。该数组中网址字符串的数量不得超过一个,但该数量可能会随着您的反馈而发生变化。

IdP 配置文件提供了浏览器所需端点的列表。IdP 将托管此配置文件以及所需的端点和网址。所有 JSON 响应都必须使用 application/json 内容类型提供。

配置文件的网址由提供给在 RP 上执行的 navigator.credentials.get 调用提供的值确定。

const credential = await navigator.credentials.get({
  identity: {
    context: 'signup',
    providers: [{
      configURL: 'https://accounts.idp.example/config.json',
      clientId: '********',
      nonce: '******'
    }]
  }
});
const { token } = credential;

将 IdP 配置文件位置的完整网址指定为 configURL。在 RP 上调用 navigator.credentials.get() 时,浏览器会使用不带 Origin 标头或 Referer 标头的 GET 请求来提取配置文件。该请求没有 Cookie,也不会跟踪重定向。 这样可有效防止 IdP 了解发出请求的人员以及哪个 RP 正在尝试连接。例如:

GET /config.json HTTP/1.1
Host: accounts.idp.example
Accept: application/json
Sec-Fetch-Dest: webidentity

浏览器期望 IdP 返回包含以下属性的 JSON 响应:

属性 说明
accounts_endpoint(必需) 帐号端点的网址。
client_metadata_endpoint(可选) 客户端元数据端点的网址。
id_assertion_endpoint(必需) ID 断言端点的网址。
disconnect(可选) 断开连接端点的网址。
login_url(必需) 用户用于登录 IdP 的登录页面网址
branding(可选) 包含各种品牌选项的对象。
branding.background_color(可选) 品牌选项,用于设置“继续为...”按钮的背景颜色。使用相关的 CSS 语法,即 hex-colorhsl()rgb() 或 named-color
branding.color(可选) 品牌选项,用于设置“继续为...”按钮的文字颜色。使用相关的 CSS 语法,即 hex-colorhsl()rgb() 或 named-color
branding.icons(可选) 品牌选项,用于设置图标对象(显示在登录对话框中)。图标对象是一个包含两个参数的数组:
  • url(必需):图标图片的网址。不支持 SVG 图片。
  • size(可选):图标尺寸,应用假定为方形和单一分辨率。此数字必须大于或等于 25。

RP 可以通过 navigator.credentials.get() 的 identity.context 值修改 FedCM 对话框界面中的字符串,以适应预定义的身份验证上下文。可选属性可以是 "signin"(默认)、"signup""use" 或 "continue" 中的一个。

品牌如何应用于 FedCM 对话框

以下是 IdP 的响应正文示例:

{
  "accounts_endpoint": "/accounts.php",
  "client_metadata_endpoint": "/client_metadata.php",
  "id_assertion_endpoint": "/assertion.php",
  "disconnect_endpoint": "/disconnect.php",
  "login_url": "/login",
  "branding": {
    "background_color": "green",
    "color": "#FFEEAA",
    "icons": [{
      "url": "https://idp.example/icon.ico",
      "size": 25
    }]
  }
}

浏览器提取配置文件后,会向 IdP 端点发送后续请求:

IdP 端点

IdP 的帐号端点会返回用户当前在 IdP 上登录的帐号列表。如果 IdP 支持多个帐号,此端点将返回所有已登录的帐号。

浏览器发送 GET 请求,其中包含带有 SameSite=None 的 Cookie,但不带 client_id 参数、Origin 标头或 Referer 标头。这样可以有效防止 IdP 了解用户尝试登录的 RP。例如:

GET /accounts.php HTTP/1.1
Host: accounts.idp.example
Accept: application/json
Cookie: 0x23223
Sec-Fetch-Dest: webidentity

收到请求后,服务器应执行以下操作:

  1. 验证请求是否包含 Sec-Fetch-Dest: webidentity HTTP 标头。
  2. 将会话 Cookie 与已登录帐号的 ID 进行匹配。
  3. 返回帐号列表进行响应。

浏览器需要的 JSON 响应包括 accounts 属性以及一系列具有以下属性的帐号信息:

属性 说明
id(必需) 用户的唯一 ID。
name(必需) 用户的姓氏和姓氏。
email(必需) 用户的电子邮件地址。
given_name(可选) 用户的名字。
picture(可选) 用户头像图片的网址。
approved_clients(可选) 用户注册的 RP 客户端 ID 数组。
login_hints(可选) IdP 支持指定帐号的所有可能过滤条件类型的数组。RP 可以使用 loginHint 属性调用 navigator.credentials.get(),以选择性地显示指定的帐号。
domain_hints(可选) 一个数组,包含与该帐号相关联的所有网域。RP 可以使用 domainHint 属性调用 navigator.credentials.get() 来过滤帐号。

响应正文示例:

{
  "accounts": [{
    "id": "1234",
    "given_name": "John",
    "name": "John Doe",
    "email": "john_doe@idp.example",
    "picture": "https://idp.example/profile/123",
    "approved_clients": ["123", "456", "789"],
    "login_hints": ["demo1", "demo1@idp.example"]
  }, {
    "id": "5678",
    "given_name": "Johnny",
    "name": "Johnny",
    "email": "johnny@idp.example",
    "picture": "https://idp.example/profile/456",
    "approved_clients": ["abc", "def", "ghi"],
    "login_hints": ["demo2", "demo2@idp.example"],
    "domain_hints": ["corp.example"]
  }]
}

如果用户没有登录,则返回 HTTP 401 (Unauthorized)。

返回的帐号列表会被浏览器使用,并且不会提供给 RP。

IdP 的客户端元数据端点返回依赖方的元数据,例如 RP 的隐私权政策和服务条款。RP 应提前向 IdP 提供其隐私权政策和服务条款的链接。如果用户尚未通过 IdP 在 RP 上注册,登录对话框中会显示这些链接。

浏览器使用不带 Cookie 的 client_id navigator.credentials.get 发送 GET 请求。例如:

GET /client_metadata.php?client_id=1234 HTTP/1.1
Host: accounts.idp.example
Origin: https://rp.example/
Accept: application/json
Sec-Fetch-Dest: webidentity

收到请求后,服务器应执行以下操作:

  1. 确定 client_id 的 RP。
  2. 使用客户端元数据进行响应。

客户端元数据端点的属性包括:

属性 说明
privacy_policy_url(可选) RP 隐私权政策网址。
terms_of_service_url(可选) RP 服务条款网址。

浏览器期望端点返回 JSON 响应:

{
  "privacy_policy_url": "https://rp.example/privacy_policy.html",
  "terms_of_service_url": "https://rp.example/terms_of_service.html",
}

返回的客户端元数据被浏览器使用,并且不可供 RP 使用。

IdP 的 ID 断言端点会为已登录的用户返回断言。当用户使用 navigator.credentials.get() 调用登录 RP 网站时,浏览器会向此端点发送一个 POST 请求(其中包含具有 SameSite=None 的 Cookie,内容类型为 application/x-www-form-urlencoded),其中包含以下信息:

属性 说明
client_id(必需) RP 的客户端标识符。
account_id(必需) 登录用户的唯一 ID。
nonce(可选) 请求 Nonce,由 RP 提供。
disclosure_text_shown 返回 "true" 或 "false" 字符串(而不是布尔值)。如果未显示披露文字,则结果为 "false"。如果 RP 的客户端 ID 包含在帐号端点的响应的 approved_clients 属性列表中,或者浏览器在过去检测到注册时刻(缺少 approved_clients),就会发生这种情况。
is_auto_selected 如果对 RP 执行了自动重新身份验证is_auto_selected 表示 "true"。否则为 "false"。这有助于支持更多与安全相关的功能。例如,有些用户可能更喜欢更高的安全等级,这就要求在身份验证时明确指定用户中介。如果 IdP 在未使用此类中介功能的情况下收到令牌请求,他们可能会以不同的方式处理该请求。例如,返回错误代码,以便 RP 可以通过 mediation: required 再次调用 FedCM API。

HTTP 标头示例:

POST /assertion.php HTTP/1.1
Host: accounts.idp.example
Origin: https://rp.example/
Content-Type: application/x-www-form-urlencoded
Cookie: 0x23223
Sec-Fetch-Dest: webidentity

account_id=123&client_id=client1234&nonce=Ct60bD&disclosure_text_shown=true&is_auto_selected=true

收到请求后,服务器应执行以下操作:

  1. 使用 CORS(跨域资源共享)响应请求。
  2. 验证请求是否包含 Sec-Fetch-Dest: webidentity HTTP 标头。
  3. 将 Origin 标头与由 client_id 确定的 RP 源进行匹配。如果不匹配,则拒绝。
  4. 将 account_id 与已登录帐号的 ID 进行匹配。如果不匹配,则拒绝。
  5. 使用令牌进行响应。如果请求被拒绝,则使用错误响应进行响应。

令牌的颁发方式由 IdP 决定,但一般而言,它会使用帐号 ID、客户端 ID、颁发者来源、nonce 等信息进行签名,以便 RP 可以验证令牌的真实性。

浏览器需要包含以下属性的 JSON 响应:

属性 说明
token(必需) 令牌是一个字符串,其中包含有关身份验证的声明。

{
  "token": "***********"
}

返回的令牌由浏览器传递给 RP,以便 RP 可以验证身份验证。

id_assertion_endpoint 还可以返回“错误”响应,其中包含两个可选字段:

  • code:IdP 可以从 OAuth 2.0 指定的错误列表中选择一个已知错误(invalid_requestunauthorized_clientaccess_deniedserver_error 和 temporarily_unavailable),也可以使用任意字符串。如果是后者,Chrome 会呈现错误界面并显示一般错误消息,并将代码传递给 RP。
  • url:用于识别包含错误相关信息的人类可读网页,以便向用户提供与错误有关的其他信息。此字段对用户非常有用,因为浏览器无法在原生界面中提供丰富的错误消息。例如,后续步骤链接、客户服务联系信息等。如果用户想要详细了解错误详情及修正方法,可以从浏览器界面访问所提供的页面,以查看详情。该网址必须与 IdP configURL 属于同一网站。

// id_assertion_endpoint response
{
  "error" : {
     "code": "access_denied",
     "url" : "https://idp.example/error?type=access_denied"
  }
}

通过调用 IdentityCredential.disconnect(),浏览器会向此断开连接端点发送一个包含 Cookie SameSite=None、内容类型为 application/x-www-form-urlencoded 的跨源 POST 请求,其中包含以下信息:

属性 说明
account_hint IdP 账号的提示。
client_id RP 的客户端标识符。

POST /disconnect.php HTTP/1.1
Host: idp.example
Origin: rp.example
Content-Type: application/x-www-form-urlencoded
Cookie: 0x123
Sec-Fetch-Dest: webidentity

account_hint=account456&client_id=rp123

收到请求后,服务器应执行以下操作:

  1. 使用 CORS(跨域资源共享)响应请求。
  2. 验证请求是否包含 Sec-Fetch-Dest: webidentity HTTP 标头。
  3. 将 Origin 标头与由 client_id 确定的 RP 源进行匹配。如果不匹配,则拒绝。
  4. 将 account_hint 与已登录的账号的 ID 进行匹配。
  5. 断开用户帐号与 RP 的连接。
  6. 以 JSON 格式使用已识别的用户帐号信息响应浏览器。

响应 JSON 载荷示例如下所示:

{
  "account_id": "account456"
}

相反,如果 IdP 希望浏览器断开与 RP 关联的所有帐号的关联,请传递一个与任何帐号 ID 都不匹配的字符串,例如 "*"

借助 Login status API,IdP 必须将用户的登录状态告知浏览器。但是,状态可能不同步,例如当会话过期时。在这种情况下,浏览器可以动态地让用户通过 IdP 配置文件的 login_url 指定的登录页面网址登录 IdP。

FedCM 对话框会显示一条建议登录的消息,如下图所示。

一个建议登录 IdP 的 FedCM 对话框。

当用户点击 Continue 按钮时,浏览器会打开一个显示 IdP 登录页面的弹出式窗口。

点击“登录 IdP”按钮后显示的示例对话框。

该对话框是一个包含第一方 Cookie 的常规浏览器窗口。对话框中的操作由 IdP 决定,没有窗口句柄可以向 RP 页面发出跨源通信请求。用户登录后,IdP 应执行以下操作:

  • 发送 Set-Login: logged-in 标头或调用 navigator.login.setStatus("logged-in") API 以通知浏览器用户已登录。
  • 调用 IdentityProvider.close() 以关闭对话框。
用户在使用 FedCM 登录 IdP 后登录了 RP。

Login status API 是一种机制,在这种机制中,网站(尤其是 IdP)会向浏览器通知用户在 IdP 上的登录状态。借助此 API,浏览器可以减少向 IdP 发送的不必要请求并缓解潜在的计时攻击。

当用户在 IdP 上登录或退出其所有 IdP 帐号时,IdP 可以通过以下两种方式向浏览器表明用户的登录状态:发送 HTTP 标头或调用 JavaScript API。对于每个 IdP(由其配置网址标识),浏览器都会保留一个表示登录状态的三态变量,该变量可能具有 logged-inlogged-out 和 unknown 值。默认状态为 unknown

如需表明用户已登录,请在顶级导航栏中发送 Set-Login: logged-in HTTP 标头,或在 IdP 源站发送同网站子资源请求:

Set-Login: logged-in

或者,在顶级导航栏中从 IdP 来源调用 JavaScript API navigator.login.setStatus("logged-in")

navigator.login.setStatus("logged-in")

这些调用会将用户的登录状态记录为 logged-in。当用户的登录状态设置为 logged-in 时,调用 FedCM 的 RP 会向 IdP 的帐号端点发出请求,并在 FedCM 对话框中向用户显示可用帐号。

如需表明用户已退出其所有帐号,请在顶级导航栏中发送 Set-Login: logged-out HTTP 标头,或在 IdP 来源处发送同网站子资源请求:

Set-Login: logged-out

或者,在顶级导航栏中从 IdP 来源调用 JavaScript API navigator.login.setStatus("logged-out")

navigator.login.setStatus("logged-out")

这些调用会将用户的登录状态记录为 logged-out。当用户的登录状态为 logged-out 时,如果不向 IdP 的帐号端点发出请求,调用 FedCM 会静默失败。

unknown 状态是在 IdP 使用 LoginStatus API 发送信号之前设置的。引入了 Unknown 以改进过渡,因为在此 API 发布时,用户可能已经登录 IdP。IdP 可能没有机会在首次调用 FedCM 时向浏览器发出信号。在这种情况下,Chrome 会向 IdP 的帐号端点发出请求,并根据来自帐号端点的响应更新状态:

  • 如果端点返回活跃帐号列表,请将状态更新为 logged-in,并打开 FedCM 对话框以显示这些帐号。
  • 如果端点未返回任何帐号,请将状态更新为 logged-out 并失败 FedCM 调用。

即使 IdP 不断将用户的登录状态告知浏览器,它也可能不同步,例如当会话过期时。当登录状态为 logged-in 时,浏览器会尝试向帐号端点发送基于凭据的请求,但由于会话不再可用,服务器不会返回任何帐号。在这种情况下,浏览器可以动态让用户通过弹出式窗口登录 IdP

IdP 的配置和端点可用后,RP 可以调用 navigator.credentials.get() 来请求允许用户使用 IdP 登录 RP。

在调用该 API 之前,您需要确认 [FedCM 在用户的浏览器中可用]。如需检查 FedCM 是否可用,请将此代码封装在 FedCM 实现中:

if ('IdentityCredential' in window) {
  // If the feature is available, take action
}

如需请求允许用户从 RP 登录 IdP,请执行以下操作,例如:

const credential = await navigator.credentials.get({
  identity: {
    providers: [{
      configURL: 'https://accounts.idp.example/config.json',
      clientId: '********',
      nonce: '******'
    }]
  }
});
const { token } = credential;

providers 属性接受具有以下属性的 IdentityProvider 对象数组:

属性 说明
configURL(必需) IdP 配置文件的完整路径。
clientId(必需) 由 IdP 签发的 RP 的客户端标识符。
nonce(可选) 一个随机字符串,用于确保系统针对此特定请求发出响应。防止重放攻击。
loginHint(可选) 通过指定帐号端点提供的某个 login_hints 值,FedCM 对话框会选择性地显示指定的帐号。
domainHint(可选) 通过指定帐号端点提供的某个 domain_hints 值,FedCM 对话框会选择性地显示指定的帐号。

浏览器处理注册和登录用例的方式有所不同,具体取决于帐号列表端点响应中是否存在 approved_clients。如果用户已经注册 RP,浏览器不会显示披露文字“To continue with ....”。

注册状态取决于是否满足以下条件:

  • 如果 approved_clients 包含 RP 的 clientId
  • 如果浏览器记住用户已注册 RP。
文章链接

标签