1. 简介
本节为非规范性内容。
登录网站比应有的要困难得多。用户代理处于一个独特的位置,能够在多方面改善这种体验,大多数现代用户代理已经通过在浏览器中原生提供某种程度的 credential management 来认识到这一点。例如,用户可以为网站保存用户名和密码;那些 凭证 随后会自动填充到登录表单中,尽管成功率各不相同。
属性
autocomplete
提供了一种声明性机制,网站可以通过它与用户代理协作——通过将特定字段标记为 "username" 或
"password"——以提高后者检测并填写登录表单的能力;用户代理还实现了各种检测启发式方法,以便与那些没有花时间在标记中提供这些细节的网站配合。
虽然这种启发式与声明式检测的组合运行得相对较好,但现状仍然存在一些检测困难的重大空白。具有不常见登录机制的网站(例如通过 XMLHttpRequest
提交凭证)很难可靠检测,用户希望使用联合身份提供者进行认证的情况也越来越常见。允许网站更直接地与用户代理的 凭证管理器 交互,一方面可以使 凭证管理器 更加准确,另一方面也能帮助用户进行联合登录。
这些用例在 § 1.1 Use Cases 和 Credential Management: Use Cases and Requirements 中有更详细的探讨;本规范通过定义一个凭证管理器 API(Credential Manager API),尝试解决该文档概述的许多需求,网站可以使用该 API 为用户请求 凭证,并在用户成功登录时请求用户代理持久化这些凭证。
注意: 这里定义的 API 有意保持小而简单:它并不打算自身提供认证功能,而仅限于向现有用户代理实现的 凭证管理器 提供一个接口。该功能在 现在 就已很有价值,而不需要供应商或作者付出大量额外努力。当然,仍有许多可以做的事情。参见 § 9 Future Work,其中列出了一些我们暂时搁置、但可在未来 API 迭代中继续探讨的想法。
1.1. 使用场景
现代用户代理通常都为用户在登录网站时提供保存密码的能力,并且在用户再次访问网站时能全自动或半自动地填充这些密码。从网站的角度来看,这一行为完全不可见:网站不知道密码已被存储,也不会收到密码被填充的通知。这既有好处,也有不足。一方面,用户代理的密码管理器无论网站是否配合都能工作,这对用户来说非常好。另一方面,密码管理器的行为是一套脆弱且专有的启发式组合,旨在检测和填充登录表单、密码更改表单等。
现状中有几个突出问题值得关注:
-
用户代理在帮助用户使用联合身份提供者方面非常吃力。检测用户名/密码表单提交较为直接,但检测第三方登录则很难做到可靠。若网站能帮助用户代理理解联合登录相关的重定向意图,将会非常有益。
-
同样,用户代理在检测比普通用户名/密码表单更复杂的登录机制时也很困难。作者越来越多地通过
XMLHttpRequest或类似机制异步登录用户,以改善体验并提升展示控制。对用户来说这很好,但用户代理却难以将其集成到密码管理器中。若网站能帮助用户代理理解他们选择的登录机制,将会非常有益。 -
最后,如果网站能明确告知用户代理凭证已更改,密码更改的支持也会更好。
2. 核心 API
从开发者的角度来看,凭证是一个对象,允许开发者为特定操作做出认证决策。本节定义了一个通用且可扩展的 Credential
接口,作为本规范及其他文档中定义的凭证的基类,并提供一组挂载在 navigator.credentials.*
上的 API,供开发者获取凭证。
不同的 凭证类型 都以接口的形式呈现给 JavaScript,这些接口直接或间接地继承自 Credential
接口。本规范定义了两个此类接口:PasswordCredential
和 FederatedCredential。其他规范,如
[WEBAUTHN],定义了其他凭证类型。
如果一个凭据 在某个特定源上被接受用于身份验证,则该凭据对于该源是 有效的。即使一个凭据在某个特定 时间点是有效的,UA 也不能假定同一个凭据在未来任何时间仍然有效, 原因有以下几点:
-
如果账户持有人更改了密码,密码凭证可能会变为无效。
-
通过短信收到的令牌生成的凭证通常只在一次使用时有效。
一次性 凭证由 凭证源生成,凭证源可能是私钥、访问联合账户、或某个手机号接收短信的能力等。凭证源不会暴露给 Javascript,也不会在本规范中明确表示。为统一模型,我们把密码视为一种凭证源,通过复制生成密码凭证。
虽然 UA 不能假定一个有效凭证再次使用时仍然有效,也不能假定生成过有效凭证的凭证源未来能够再次生成有效凭证,但后者的可能性更大。通过记录(使用 store())
过去哪些凭证是有效的,UA 能更好地在将来为用户提供有效的凭证源选择。
2.1. 基础设施
凭证管理器是一种应用程序、硬件设备或服务,用于存储、组织、管理并允许选择凭证。典型的凭证管理器包括数字钱包、密码管理器,以及passkey管理器。
用户代理必须在内部提供一个凭证存储,它是一种供应商特定的不透明存储机制,用于记录哪些凭证已被激活。它为凭证的访问和持久化提供以下功能:
-
检索凭证列表。此操作接受任意筛选条件,并返回与筛选条件匹配的一组凭证。
此外,凭证存储应为源维护一个prevent silent access
标志(除非另有说明,默认设为true)。如果该标志为true,则该源需要用户介入。
Note: 关于用户介入的重要性,详见§ 5 用户介入。
Note: 凭证存储是用户代理实现本文档所述 API 的内部实现细节,不会直接暴露给 Web。其他文档可能会为特定凭证类型规定更多能力。
本文档在其算法和正文中使用的一些基础概念依赖 Infra Standard [INFRA]。
每个环境设置对象都有一个相关联的激活凭证类型,它是一个最初为空的集合。
2.1.1. 基础设施算法
2.1.1.1. 与其祖先同源
当 环境设置对象 (settings) 满足以下算法返回
true 时,
称其为
与其祖先同源:
-
如果 settings 的 相关全局对象没有 关联的 Document,返回
false。 -
令 document 为 settings 的 相关全局对象的 关联的 Document。
-
如果 document 没有 浏览上下文,返回
false。 -
令 origin 为 settings 的 源。
-
令 navigable 为 document 的 节点可导航对象。
-
当 navigable 有非空 父级 时:
-
返回
true。
2.1.2. 凭证类型注册表
该注册表将凭证类型(即 [[type]]
的值)映射为与指定凭证类型相关的各种值。例如:选项成员标识符
(正式来说,是 字典成员的标识符),用于 CredentialCreationOptions
和 CredentialRequestOptions
(即“选项字典”)的规范。
注意: 此注册表被 相关凭证接口对象算法使用。
| 凭证类型 (按字母顺序) | 选项成员标识符 | 适用接口对象 | 获取权限策略 | 创建权限策略 | 同一get()请求中允许的类型
| 规范文档 | 请求方联系信息 |
|---|---|---|---|---|---|---|---|
| digital-credential | digital | DigitalCredential
| digital-credentials-get | digital-credentials-create | empty | [DIGITAL-CREDENTIALS] | W3C |
| federated | federated | FederatedCredential
| null | null | password | 本规范:§ 4 联合凭证 | W3C |
| identity | identity | IdentityCredential
| identity-credentials-get | null | empty | [FEDCM] | W3C |
| otp | otp | OTPCredential
| otp-credentials | null | empty | [WEB-OTP] | WICG |
| password | password | PasswordCredential
| null | null | federated | 本规范:§ 3 密码凭证 | W3C |
| public-key | publicKey | PublicKeyCredential
| publickey-credentials-get | publickey-credentials-create | empty | [WEBAUTHN] | W3C |
2.1.2.1. 注册项要求与更新流程
-
每个注册表条目必须注明在凭证规范扩展的 字典成员 标识符 (在注册表中称为选项成员标识符), 用于
CredentialCreationOptions和CredentialRequestOptions。 -
每个注册表条目必须注明获取权限策略 的权限, 在对某凭证类型执行 请求
Credential时使用,若未指定权限策略则为null。 -
每个注册表条目必须注明创建权限策略 的权限, 在对某凭证类型执行 创建
Credential时使用,若未指定权限策略则为null。 -
虽然
get()请求支持在同一请求中查询多种类型, 但并非所有组合都被允许。每个注册表条目必须声明一个 集合,其中包含该 凭据类型 所对应的 允许出现在 同一 get() 请求中的类型,或者对于该 凭据类型, 如果它不能在同一请求中与任何其他类型混合使用,则为 空get()请求。 每种 凭据类型都可以与 自身一起出现在同一get()请求中。 -
每个注册表条目规范必须包含请求方的联系信息。
对本注册表的更新包括对某凭证类型条目的新增、修改或删除。 任何人都可以通过向 webappsec-credential-management 仓库提交pull request来请求更新。 Web应用安全工作组会将请求列入即将召开的会议议程, 并通知请求人。 W3C Web应用安全工作组通过共识进行讨论和决定。 主席随后会通知请求人结果,并相应更新注册表。
2.2.
Credential 接口
[Exposed =Window ,SecureContext ]interface {Credential readonly attribute USVString id ;readonly attribute DOMString type ;static Promise <boolean >isConditionalMediationAvailable (); };
id, 类型为 USVString,只读-
凭据的标识符。每种 凭据 类型的标识符要求是不同的。例如,对于用户名/密码元组,它可能代表用户名。
type, 类型为 DOMString,只读isConditionalMediationAvailable()-
返回一个
Promise,当且仅当用户代理支持conditional方式进行 凭据请求调解 且针对 凭据类型 时,该 Promise 会 解析为true,否则为false。Credential的默认isConditionalMediationAvailable()实现如下:任何支持
conditional调解的 凭据类型 规范,必须显式重写该函数,使其 解析为true。注意:如果此函数不存在,
conditional调解对于该 凭据类型 不受支持。 [[type]]-
Credential的 接口对象 有一个名为[[type]]的内部槽, 其中存有一个 表示凭据类型的字符串。除非另有说明,否则该槽的值为空字符串。详见 § 2.1.2 凭据类型注册表 列表 凭据类型。注意:
[[type]]槽的值对于实现某一接口的所有凭据是相同的,这意味着开发者可以依赖obj.type返回一个明确表示其具体Credential类型的字符串。 [[discovery]]-
Credential的 接口对象 具有一个名为[[discovery]]的内部槽, 表示用户代理用于收集给定类型凭据的机制。其值为 "credential store" 或 "remote"。前者意味着所有可用的凭据信息都存储在用户代理的 凭据存储 中,后者意味着用户代理可通过与外部设备或服务交互,在 凭据存储 外发现凭据。
和 Tobie/Dominic 讨论下接口对象相关内容,这里以及 § 2.5.1 请求凭据等处。我不确定术语是否准确。也许应该是 接口原型对象?
某些 Credential
对象是绑定至源的:这些对象包含一个名为 [[origin]]
的内部槽,用于存储该 源 ,
该 Credential
可能被 生效 的源。
PasswordCredential
只能用于存储它的站点。
如果文档在没有
allow-same-origin 的情况下被沙盒化,或者它来自
data: URL,则该文档具有不透明源。不透明源序列化为 null,并且
无法从中重新创建
任何内容,因此不存在可将凭据绑定到的源。因此,从此类文档执行的涉及源绑定凭据的操作
会被拒绝,并抛出
"SecurityError"
DOMException。
2.2.1.
Credential 内部方法
Credential
接口对象具有若干 内部方法,
用于
检索和存储 Credential
对象,其默认实现为“空操作”,
如本节下文所述。
除非另有规定,每个为 继承自
Credential
的接口创建的
接口对象
必须至少为这些内部方法之一提供实现,覆盖
Credential
的默认实现,具体应依据对应的
凭据类型。
例如,§ 3.2 PasswordCredential 接口、
§ 4.1 FederatedCredential 接口 以及
[WEBAUTHN]。
2.2.1.1. [[CollectFromCredentialStore]] 内部方法
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)
被调用时,传入一个 源 origin、一个 CredentialRequestOptions
对象,以及一个布尔值,仅当调用方的 环境设置对象与其 祖先同源时才为 true。
该算法会从用户代理的 凭据存储 中返回一组与所提供 options 匹配的 Credential
对象。
如果没有可用的匹配 Credential
对象,
返回的集合将为空。
Credential
的
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)
默认实现如下:
-
返回空集合。
2.2.1.2. [[DiscoverFromExternalSource]] 内部方法
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
会并行调用,参数为一个 origin、一个 CredentialRequestOptions
对象,以及一个布尔值,仅当调用方的 环境设置对象与其
祖先同源时为 true。
如果能够根据所提供的 options 返回一个 Credential
,则返回该对象;若没有凭据可用,则返回 null;如果发现失败(例如 options 参数错误会产生一个 TypeError),则抛出异常。
如果此类 Credential
仅对单次使用或仅在有限时间内 生效,该方法负责使用 凭据源生成新的 凭据。
Credential
的
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
默认实现如下:
-
返回
null。
2.2.1.3. [[Store]] 内部方法
[[Store]](credential, sameOriginWithAncestors)
会并行调用,参数为一个 Credential
和一个布尔值,仅当调用方的
环境设置对象与其 祖先同源时为 true。
当 Credential
被持久化到 凭据存储 后,该算法返回。
Credential
的 [[Store]](credential, sameOriginWithAncestors)
默认实现如下:
-
抛出
NotSupportedError。
2.2.1.4. [[Create]] 内部方法
[[Create]](origin, options, sameOriginWithAncestors)
会并行调用,参数为一个 origin,一个 CredentialCreationOptions
,以及一个布尔值,仅当调用方的
环境设置对象
与其祖先同源时为 true。
该算法会:
-
创建一个
Credential,或 -
未创建凭据并返回
null,或 -
在因异常情况(如参数错误会导致
TypeError)创建失败时抛出异常。
当创建 Credential
时,会返回一个算法,该算法接收一个 全局对象
并返回一个继承自 Credential
的 接口对象。该算法必须从一个任务中调用。
注意:该算法的步骤由每种凭据类型单独定义。
Credential
的 [[Create]](origin, options, sameOriginWithAncestors)
默认实现如下:
-
返回
null。
2.2.2.
CredentialUserData 混入
某些 Credential
对象包含旨在为用户在 凭据选择器
中提供友好名称和图标,以实现人类可读的区分机制的数据:
[SecureContext ]interface mixin {CredentialUserData readonly attribute USVString name ;readonly attribute USVString iconURL ; };
2.3.
navigator.credentials
开发者通过暴露在
CredentialsContainer
接口上的方法获取 Credential
并与用户代理的 凭据存储 交互,该接口作为
Navigator
对象的一个属性挂载在
navigator.credentials 上。
partial interface Navigator { [SecureContext ,SameObject ]readonly attribute CredentialsContainer credentials ; };
credentials 属性必须返回与 活动文档 的 浏览上下文 关联的
CredentialsContainer。
注意: 如 § 6.3 不安全站点 所述,凭据管理 API 仅暴露在 安全上下文(Secure Context) 中。
[Exposed =Window ,SecureContext ]interface {CredentialsContainer Promise <Credential ?>get (optional CredentialRequestOptions options = {});Promise <undefined >store (Credential credential );Promise <Credential ?>create (optional CredentialCreationOptions options = {});Promise <undefined >preventSilentAccess (); };dictionary {CredentialData required USVString ; };id
get(options)-
当调用
get()时,用户代理必须返回 执行请求Credential的结果,参数为options。CredentialsContainer.get(options) 方法参数。 参数 类型 可为 null 可选 描述 optionsCredentialRequestOptions✘ ✔ 控制请求范围的属性集。 store(credential)-
当调用
store()时,用户代理必须返回 执行存储Credential的结果,参数为credential。CredentialsContainer.store(credential) 方法参数。 参数 类型 可为 null 可选 描述 credentialCredential✘ ✘ 要存储的凭证。 create(options)-
当调用
create()时,用户代理必须返回 执行创建Credential的结果,参数为options。CredentialsContainer.create(options) 方法参数。 参数 类型 可为 null 可选 描述 optionsCredentialCreationOptions✘ ✔ 用于创建 Credential的选项。 preventSilentAccess()-
当调用
preventSilentAccess()时,用户代理必须返回 执行禁止静默访问算法,传入当前设置对象。注意: 这里的意图是在于由来源发出用户已登出的信号。 也就是说,在用户点击“登出”按钮后,站点会更新用户会话信息, 并调用
navigator.credentials.preventSilentAccess()。 这会设置禁止静默访问标志, 表示下次用户访问页面时不会自动将凭证返回给页面。注意: 该方法之前叫
requireUserMediation(),现已弃用。
Navigator
对象(navigator)时,用户代理必须创建一个
新的CredentialsContainer
对象,使用navigator的相关 Realm,并将其关联到navigator。
2.3.1. CredentialRequestOptions 字典
为了通过get()
获取一个Credential,
调用方需要在一个CredentialRequestOptions
对象中指定一些参数。
注意:CredentialRequestOptions
字典是一个扩展点。如果将来引入了需要选项的新凭证类型,其字典类型也会添加到此字典中,进而可以传递到请求中。参见§ 8.2
扩展点。
dictionary {CredentialRequestOptions CredentialMediationRequirement mediation = "optional";DOMString uiMode ;AbortSignal signal ; };
mediation, 类型为 CredentialMediationRequirement, 默认值为"optional"-
此属性指定特定凭证请求的调解(mediation)要求。每个枚举值的含义见下文
CredentialMediationRequirement。 处理细节定义见§ 2.5.1 请求Credential。 uiMode, 类型为 DOMString-
此属性指定在用户代理执行用户介入(user mediation)时, 对于特定凭证请求采用哪种界面模式。每个值的含义见下文
CredentialUiMode。 处理细节见§ 2.5.1 请求 Credential。 signal, 类型为 AbortSignal-
此属性允许开发者中止正在进行的
get()操作。 被中止的操作可能会正常完成(通常指在操作结束后才收到中止请求的情况)或者以中止原因拒绝(reject)。
unmediated成员。将其设为true等价于设置
mediation
为
"silent",
设为false则等价于
设置mediation
为"optional"。
unmediated应视为已废弃;新代码应改用
mediation。
CredentialCreationOptions
或CredentialRequestOptions
(options)的
相关凭证接口对象
是按如下方式收集的一组接口对象:
注意:本算法使用凭证类型注册表。
CredentialRequestOptions
(options)
a priori 可匹配(matchable a
priori),
如果通过如下步骤得到true:
-
对于options的 相关凭证接口对象 中的每个interface:
-
如果interface的
[[discovery]]槽的值不是 "credential store", 返回false。
-
-
返回
true。
注意: 执行get(options)
时,只有在给定的CredentialRequestOptions
为a priori 可匹配时,
我们才返回无需用户介入的凭证。如果请求了可能需要来自外部服务发现的凭证类型
(如OAuth令牌、安全密钥认证器等),则需要用户介入以引导发现流程(如选择联合身份提供者、BTLE 设备等)。
2.3.2. 调解要求
通过get(options)
或create(options)
进行请求时,开发者可通过选择合适的
CredentialMediationRequirement
枚举值,逐个请求地设置是否需要
用户介入。
注意: § 5 用户介入一节将在整体上进行详细说明, 并介绍其对用户代理如何处理某个源的独立请求的影响。
enum {CredentialMediationRequirement "silent" ,"optional" ,"conditional" ,"required" };
silent-
对于该操作,抑制用户介入。如果操作无需用户参与即可完成,则顺利进行。 如果必须用户参与,则该操作会返回
null,而不是打扰用户。注意: 用于支持 “保持我登录状态”场景, 即开发者希望在用户可自动登录时静默获取凭证,只有在用户主动发起登录时才弹出提示。
optional-
如果可以无需用户介入就交付凭证,则会这样交付。若 需要用户介入,则用户代理会让用户参与决策。
注意: 这是
get()的默认行为,适用于开发者有合理信心用户希望开始登录操作的情形。 例如用户刚点击“登录”按钮时,看到 凭证选择器 也不会感到奇怪或困惑。 conditional-
对于
get(), 发现的凭据会连同请求凭据的 源的指示一起显示在非模态对话框中。如果用户 在对话框 外做出手势,对话框将关闭,而不会兑现或拒绝由Promiseget()方法返回的内容,也不会导致用户可见的 错误状态。如果用户做出选择某个凭据的手势,则该凭据会 返回给调用者。阻止静默访问标志会 被视为true, 无论其实际值如何:conditional行为 如果发现适用的凭据,总是涉及某种形式的 用户中介 。如果没有发现凭证,用户代理可以根据凭证类型提示用户采取操作 (例如插入包含凭证的设备)。 无论哪种情况,
get()方法都不得立刻以null返回, 以避免向网站暴露缺少可用凭证的事实。网站只有在所有涉及的凭证接口 都覆盖了
isConditionalMediationAvailable(), 并返回 resolve为true的Promise时, 才能把conditional传入get()方法。对于
create(), 如果用户曾同意凭证创建且用户代理最近完成过一次身份认证, 则create()调用可在无需额外的模态交互的情况下完成。 如果用户代理最近未完成身份认证或没有凭证创建的同意,则该调用必须抛出 "NotAllowedError"DOMException异常。 required-
用户代理若无用户介入,不会交付凭证, 即使该源的 禁止静默访问标志 未设置。
注意: 此要求用于支持重新认证 或切换用户等场景。 该要求仅影响本次操作,不会影响该源的 禁止静默访问标志。 若要设置该标志,开发者应调用
preventSilentAccess()。
2.3.3. 界面模式
通过get(options)
发起请求时,开发者可以选择合适的
CredentialUiMode
枚举值来指明期望的用户介入模式。
UI Mode 字段没有默认值。如果未指定,
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)方法必须仅依据
CredentialMediationRequirement
的值来决定浏览器行为。
注: 这允许调用者区分
不同可用形式的 用户中介,当发生 用户中介时。
是否显示 UI 取决于请求的 CredentialMediationRequirement
值。并非
CredentialUiMode
的所有值与 CredentialMediationRequirement
的所有值组合时都合理,
而这种
交互取决于
支持该功能的凭据
类型的 [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) 方法。
enum {CredentialUiMode "immediate" };
immediate-
调用
get()时, 用户代理要么向用户显示一个模态的凭证选择对话框, 要么立即完成操作且不返回凭证。这可以让用户看到精简的登录界面,或者如果没有可用凭证,网站可采取其它操作,比如显示自定义登录页或让用户以未登录状态继续后续流程。
如果没有凭证可在选择对话框中展示,则会跳过用户介入, 例如不会提示用户手动输入凭证。
注意: 本规范未提供类似
isConditionalMediationAvailable()这样用于检测immediate能力的静态方法。 将来如果该调解模式扩展到更多凭证类型,则需要一种更通用的特性检测方法,以指明某个CredentialMediationRequirement和CredentialUiMode组合下支持哪些凭证类型。
2.3.3.1. 示例
get(),
并传入一个mediation
成员,设为
"silent"。
这确保那些已选择
去除用户介入要求(如§ 5.2 要求用户介入所述)的用户会自动登录,
而未选择此行为的用户则不会莫名被弹出
凭证选择器打扰:
window.addEventListener('load', async () => {
const credentials = await navigator.credentials.get({
...,
mediation: 'silent'
});
if (credentials) {
// 哇!我们可以用这些凭证直接让用户登录!
}
});
document.querySelector('#sign-in').addEventListener('click', async () => {
const credentials = await navigator.credentials.get({
...,
mediation: 'optional'
});
if (credentials) {
// 哇!我们可以用这些凭证直接让用户登录!
}
});
get()
并将mediation
字段设为
"required",确保用户代理一定要求介入:
注意: 根据浏览器或凭证类型的安全模型,可能需要用户用某种方式认证,例如输入主密码、指纹扫描等,才能把凭证交给网站。
document.querySelector('#important-form').addEventListener('submit', async () => {
const credentials = await navigator.credentials.get({
...,
mediation: 'required'
});
if (credentials) {
// 检查 |credentials| 是否有权限访问,没有权限就取消提交。
} else {
e.preventDefault();
}
});
get(),
并将mediation
字段设为
"required",
这样点击“添加账号”按钮时不会自动返回凭证:
document.querySelector('#switch-button').addEventListener('click', e => {
var c = await navigator.credentials.get({
...,
mediation: 'required'
});
if (c) {
// 用 |c| 让用户登录。
}
});
2.4. CredentialCreationOptions 字典
为了通过 create()
创建 Credential
,调解者需要在 CredentialCreationOptions
对象中指定一些参数。
注意:
CredentialCreationOptions
字典是一个扩展点。若未来引入新的凭据类型,将会扩展该字典,这样这些新参数可被传递给创建方法。详见 § 8.2
扩展点,以及本文档的扩展接口:
§ 3.2 PasswordCredential 接口 和 § 4.1 FederatedCredential 接口。
dictionary {CredentialCreationOptions CredentialMediationRequirement = "optional";mediation AbortSignal signal ; };
signal, 类型为 AbortSignal-
该属性允许开发者中止正在进行的
create()操作。被中止的操作可能会正常完成(如在操作结束后才收到中止),也可能因 中止原因被拒绝。
2.5. 算法
2.5.1. 请求
Credential
请求 Credential 算法
接收一个 CredentialRequestOptions
(options)参数,并返回一个
Promise
:能唯一获取凭据时 resolve 为 Credential,
否则 resolve 为 null。
-
令 settings 为当前设置对象。
-
断言:settings 是一个安全上下文。
-
令 document 为 settings 的相关全局对象的关联 Document。
-
如果 document 不是完全活动的,则返回一个以 "
InvalidStateError"DOMException拒绝的 promise。 -
如果
options.已中止, 则返回一个以signaloptions.的 中止原因拒绝的 promise。signal -
令 interfaces 为 options 的相关 凭据接口对象。
-
如果 interfaces 为空,则 返回一个以 "
NotSupportedError"DOMException拒绝的 promise。 -
对于 interfaces 中的每个 interface1:
-
令 type1 为 interface1 的
[[type]]。 -
对于 interfaces 中的每个 interface2:
-
令 type2 为 interface2 的
[[type]]。 -
如果 type1 等于 type2,则继续。
-
如果 type1 的同一个 get() 请求中 允许的类型不包含 type2, 则返回一个以 "
NotSupportedError"DOMException拒绝的 promise。
-
-
-
令 origin 为 settings 的源。
-
对于 interfaces 中的每个 interface:
-
如果 interface 的实例是源 绑定的,并且 origin 是 一个不透明源,则返回一个以 "
SecurityError"DOMException拒绝的 promise。
-
-
对于 interfaces 中的每个 interface:
-
如果 options.
mediation是conditional并且 interface 不 支持conditional用户 中介,则返回一个以TypeError拒绝的 promise。 -
如果 options.
uiMode是immediate并且 interface 不 支持immediate用户 中介,则返回一个以TypeError拒绝的 promise。 -
如果 settings 的活动凭据类型包含 interface 的
[[type]], 则返回一个以 "NotAllowedError"DOMException拒绝的 promise。
-
-
如果 settings 与其祖先同源,则令 sameOriginWithAncestors 为
true,否则为false。 -
对于 options 的相关 凭据接口对象中的每个 interface:
-
如果 permission 为 null,则继续。
-
如果 document 不被允许使用 permission,则返回 一个以 "
NotAllowedError"DOMException拒绝的 promise。
-
令 p 为一个新的 promise。
-
并行运行以下步骤:
-
令 credentials 为从凭据存储中收集
Credential的结果,给定 origin、 options 和 sameOriginWithAncestors。 -
如果以下所有陈述均为真,则以 credentials[0] 解决 p,并 跳过剩余步骤:
-
credentials 的大小为 1
-
origin 不需要用户中介
-
options 是可先验匹配的。
-
options.
mediation不是 "conditional"。
这 可能是错误的模型。如果能支持这样一种站点将会很好:它希望 同时接受用户名/密码或 WebAuthn 风格的凭据,而对于使用前者且希望保持登录状态的用户, 不必强制他们使用选择器。
-
-
令 result 为要求用户 选择一个
Credential的结果,给定 options 和 credentials。 -
如果 result 是一个接口对象:
-
将 result 设置为执行 result 的
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)的结果,给定 origin、options 和 sameOriginWithAncestors。如果这抛出了一个异常:
-
令 e 为抛出的异常。
-
在 global 的DOM 操作任务 源上排入一个任务 以运行以下子步骤:
-
以 e 拒绝 p。
-
-
终止这些子步骤。
-
-
-
断言:result 是
null,或者是一个Credential。 -
如果 result 是一个
Credential, 则以 result 解决 p。 -
如果 result 是
null,且 options.mediation不是conditional, 则以 result 解决 p。注意:如果 options.
mediation是conditional并且发现了一个null凭据, 则不会解决 promise p。
-
-
响应 p:
-
返回 p。
2.5.2. 从凭证存储收集
Credential
给定 源(origin)
(origin)、CredentialRequestOptions
(options)、以及一个布尔值,只有在调用上下文与祖先同源
时为 true(sameOriginWithAncestors),
用户代理可以
从凭据存储收集
Credential
,返回本地存储并匹配 options 过滤条件的一组 Credential
对象。如果本地没有这样的 Credential
,返回集合为空:
-
令 possible matches 为一个空集合。
-
遍历 options 的 相关凭据接口对象 interface:
-
令 r 为执行 interface 的
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)内部方法后的结果,参数为 origin、options 和 sameOriginWithAncestors。如抛出 异常,则重新抛出该异常。 -
断言:r 为 接口对象列表。
-
遍历 r 中的每一项 c:
-
将 c 添加到 possible matches。
-
-
-
返回 possible matches。
2.5.3. 存储
Credential
存储 Credential 算法接收一个
Credential
(credential),返回一个 Promise,
当对象持久化到凭证存储后 resolve。
-
令 settings 为当前设置对象。
-
断言:settings 是一个安全上下文。
-
如果 settings 的相关全局对象的关联 Document不是完全活动的, 则返回一个以 "
InvalidStateError"DOMException拒绝的 promise。 -
如果 credential 是源绑定的,并且 settings 的源是一个不透明源,则返回一个以 "
SecurityError"DOMException拒绝的 promise。 -
令 p 为一个新的 promise。
-
如果 settings 的活动凭据类型包含 credential 的
[[type]], 则返回一个以 "NotAllowedError"DOMException拒绝的 promise。 -
并行运行以下步骤:
-
响应 p:
-
返回 p。
2.5.4. 创建
Credential
创建 Credential 算法接受一个
CredentialCreationOptions
(options),并返回一个 Promise,
如果可以使用所提供的选项创建,则该 Promise 会兑现为一个 Credential
,如果无法创建任何 null,则为 Credential
。在异常
情况下,该 Promise
可能会以适当的异常拒绝:
-
令 settings 为当前设置对象。
-
断言:settings 是一个安全上下文。
-
令 global 为 settings 的全局对象。
-
令 document 为相关全局对象的关联 Document。
-
如果 document 不是完全活动的,则返回 一个以 "
InvalidStateError"DOMException拒绝的 promise。 -
如果以下任一陈述 为真,则返回一个以
NotSupportedError拒绝的 promise:-
global 没有关联 Document。
-
interfaces 的大小 大于 1。
注意:将来的某个时候,放宽此限制或许是合理的,并 允许用户代理帮助用户从多种潜在凭据 类型中选择一种,以支持“注册”用例。不过目前,我们通过 将该字典限制为单个条目来暂不处理这一问题。
-
-
对于 interfaces 中的每个 interface:
-
如果 permission 为 null,则继续。
-
如果 document 不被允许使用 permission,则返回 一个以 "
NotAllowedError"DOMException拒绝的 promise。
-
如果
options.已中止, 则返回一个以signaloptions.的 中止原因拒绝的 promise。signal -
令 origin 为 settings 的源。
-
对于 interfaces 中的每个 interface:
-
如果 interface 的实例是源 绑定的,并且 origin 是 一个不透明源,则返回一个以 "
SecurityError"DOMException拒绝的 promise。
-
-
令 type 为 interfaces[0] 的
[[type]]。 -
如果 settings 的活动凭据类型包含 type,则返回 一个以 "
NotAllowedError"DOMException拒绝的 promise。 -
令 p 为一个新的 promise。
-
并行运行以下步骤:
-
令 r 为在 origin、options 和 sameOriginWithAncestors 上执行 interfaces[0] 的
[[Create]](origin, options, sameOriginWithAncestors)内部方法的结果。如果这抛出了一个异常:
-
如果 r 是一个
Credential或null,则以 r 解决 p,并终止这些子步骤。 -
断言:r 是一个算法(定义见§ 2.2.1.4 [[Create]] 内部方法)。
-
在 global 的DOM 操作任务源上排入一个任务,以运行 以下子步骤:
-
以给定 global 对 r 进行promise 调用所得的结果解决 p。
-
-
-
响应 p:
-
返回 p。
2.5.5. 防止静默访问
禁止静默访问算法
接收一个环境设置对象(settings),
返回一个 Promise
:一旦 prevent silent access
标志被持久化到 凭据存储 后即 resolve。
-
令 origin 为 settings 的源。
-
如果 settings 的相关全局对象的关联 Document不是完全活动的, 则返回一个以 "
InvalidStateError"DOMException拒绝的 promise。 -
令 p 为一个新的 promise。
-
并行运行以下步骤:
-
返回 p。
3. 密码凭证
无论好坏,许多网站都依赖用户名/密码对作为认证机制。
PasswordCredential
接口是一种凭证,用于实现该场景,既可以存储用户名和密码,也可以存储有助于用户在凭证选择器中选中正确账号的元数据。
3.1. 示例
3.1.1. 基于密码的登录
navigator.credentials.get()
从用户的凭证存储中获取
用户名/密码对:
navigator.credentials .get({ 'password': true }) .then(credential => { if (!credential) { // 用户要么没有此站点的凭证,要么 // 拒绝共享。此处可以插入代码降级为 // 普通登录表单。 return; } if (credential.type == 'password') { var form = new FormData(); form.append('username_field', credential.id); form.append('password_field', credential.password); var opt = { method: 'POST', body: form, credentials: 'include' // 携带 cookie。 }; fetch('https://example.com/loginEndpoint', opt) .then(function (response) { if (/* |response| 表示登录成功 */) { // 记录凭证已生效。见下方注释。 navigator.credentials.store(credential); // 通知用户登录成功!可以进行登录后的操作! // 可以通过 location.href = '/signed-in-experience' 跳转到新页面 } else { // 此处可以插入代码降级为基础登录表单。 } }); } });
另外,网站也可以直接将凭证数据写入
form
并调用
submit()
进行提交:
navigator.credentials .get({ 'password': true }) .then(credential => { if (!credential) { return; // 同上... } if (credential.type === 'password') { document.querySelector('input[name=username_field]').value = credential.id; document.querySelector('input[name=password_field]').value = credential.password; document.getElementById('myform').submit(); } });
需要注意,前一种方法更加推荐,因为它显式调用了
store()
并保存凭证。
基于form的机制
则依赖于表单提交,这会导致浏览上下文跳转,不易保证只有在登录成功后调用
store()。
注意: 用户代理展示的
凭证选择器
可能允许用户选择实际并未存储在当前源下的凭证。
例如,在登录https://www.example.com时,可能会提供来自https://m.example.com的凭证
(见§ 6.1 跨域凭证访问),
或允许用户临时新建一个凭证。
开发者可以通过在每次凭证被成功使用时都调用
store()
(即便刚通过
get()
获取)来优雅地处理这种不确定性:如果凭证尚未存储在该源下,用户将有机会保存,
若已存储,则不会再提示。
3.1.2. 登录后确认
为了确保用户在成功登录后能够保存新凭证,可以将凭证传递给 store()。
fetch() 将凭据提交到登录端点来登录,我们可以检查响应以确定
用户是否
已成功登录,并相应地通知用户代理。给定如下登录表单:
<form action="https://example.com/login" method="POST" id="theForm"> <label for="username">用户名</label> <input type="text" id="username" name="username" autocomplete="username"> <label for="password">密码</label> <input type="password" id="password" name="password" autocomplete="current-password"> <input type="submit"> </form>
然后开发者可以使用类似以下的处理程序来处理表单提交:
document.querySelector('#theForm').addEventListener('submit', e => {
if (window.PasswordCredential) {
e.preventDefault();
// 从触发 "submit" 事件的 HTMLFormElement 构造一个新的 PasswordCredential:
// 这会获取标记有 "username" 和 "current-password" autocomplete
// 属性的字段值:
var c = new PasswordCredential(e.target);
// 获取表单的 action URL,并将该新凭据对象作为
// FormData 对象传入。如果响应表示成功,则告知用户代理,
// 以便它可以询问用户是否存储密码供将来使用:
var opt = {
method: 'POST',
body: new FormData(e.target),
credentials: 'include' // 发送 Cookie。
};
fetch(e.target.action, opt).then(r => {
if (/* |r| 是一个“成功的” Response */)
navigator.credentials.store(c);
});
}
});
3.1.3. 修改密码
同样的存储机制可用于“修改密码”,无需任何修改:当用户更改凭证时,网站可通知用户代理用户已用新凭证成功登录。用户代理随后可以更新其存储的凭证:
store()
来更新用户的凭据。
给定如下密码更改表单:
<form action="https://example.com/changePassword" method="POST" id="theForm"> <input type="hidden" name="username" autocomplete="username" value="user"> <label for="password">新密码</label> <input type="password" id="password" name="password" autocomplete="new-password"> <input type="submit"> </form>
开发者可以使用类似以下的处理程序来处理表单提交:
document.querySelector('#theForm').addEventListener('submit', e => {
if (window.PasswordCredential) {
e.preventDefault();
// 从触发 "submit" 事件的 HTMLFormElement 构造一个新的 PasswordCredential
// 这会获取标记有 "username" 和 "new-password" autocomplete
// 属性的字段值:
var c = new PasswordCredential(e.target);
// 获取表单的 action URL,并将该新凭据对象作为
// FormData 对象传入。如果响应表示成功,则告知用户代理,
// 以便它可以询问用户是否存储密码供将来使用:
var opt = {
method: 'POST',
body: new FormData(e.target),
credentials: 'include' // 发送 Cookie。
};
fetch(e.target.action, opt).then(r => {
if (/* |r| 是一个“成功的” Response */)
navigator.credentials.store(c);
});
}
});
3.2.
PasswordCredential 接口
[Exposed =Window ,SecureContext ]interface :PasswordCredential Credential {constructor (HTMLFormElement );form constructor (PasswordCredentialData );data readonly attribute USVString password ; };PasswordCredential includes CredentialUserData ;partial dictionary CredentialRequestOptions {boolean =password false ; };
password, 类型为 USVString,只读-
此属性表示凭据的密码。
[[type]]-
PasswordCredential接口对象有一个名为[[type]]的内部槽,其 值为 "password"。 [[discovery]]-
PasswordCredential接口对象有一个名为[[discovery]]的内部槽,其值为 "凭据存储"。 PasswordCredential(form)-
此构造函数接受一个
HTMLFormElement(form),并运行以下步骤:-
令 r 为给定 form 和 origin 执行从
HTMLFormElement创建PasswordCredential的结果。 -
否则,返回 r。
PasswordCredential(data)-
此构造函数接受一个
PasswordCredentialData(data),并运行以下步骤:-
令 r 为在 data 上执行从
PasswordCredentialData创建PasswordCredential的结果。 -
否则,返回 r。
-
PasswordCredential
对象可以通过
navigator.credentials.create()
创建,既可以通过显式传入一个 PasswordCredentialData
字典,也可以基于
一个 HTMLFormElement
的
可提交元素的内容。
dictionary :PasswordCredentialData CredentialData {USVString ;name USVString ;iconURL required USVString ;origin required USVString ; };password typedef (PasswordCredentialData or HTMLFormElement );PasswordCredentialInit partial dictionary CredentialCreationOptions {PasswordCredentialInit ; };password
PasswordCredential
对象是源绑定的。
PasswordCredential
的
接口对象继承 Credential
的
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
实现,并定义
自己的
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)、
[[Create]](origin, options, sameOriginWithAncestors)
和
[[Store]](credential, sameOriginWithAncestors)
的实现。
3.3. 算法
3.3.1.
PasswordCredential 的
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)
使用一个源(origin)、一个 CredentialRequestOptions
(options),
以及一个布尔值调用,当且仅当调用上下文与其祖先
同源时,该布尔值为 true
(sameOriginWithAncestors)。
该算法从
凭据存储中返回一组 Credential
对象。如果没有可用的匹配 Credential
对象,则返回的集合
将为空。
如果 sameOriginWithAncestors 不是
true,该算法将抛出 NotAllowedError。
-
如果 sameOriginWithAncestors 为
false,则抛出一个 "NotAllowedError"DOMException。注意:此限制旨在解决 § 6.4 源混淆中提出的问题。
-
如果 options["
password"] 不是true,则返回空集合。 -
-
该凭据是一个
PasswordCredential -
该凭据的
[[origin]]与 origin 同源。
-
3.3.2. PasswordCredential 的
[[Create]](origin, options, sameOriginWithAncestors)
[[Create]](origin, options, sameOriginWithAncestors)
使用一个源(origin)、一个
CredentialCreationOptions
(options),以及一个布尔值调用,当且仅当调用
上下文与其祖先同源时,该布尔值为 true
(sameOriginWithAncestors)。
如果能够创建一个 PasswordCredential,
则该算法返回它,
否则返回 null。CredentialCreationOptions
字典必须有一个 password 成员,该成员
保存一个 HTMLFormElement
或一个 PasswordCredentialData。
如果该成员的值无法
用于创建一个 PasswordCredential,
则此算法将抛出一个 TypeError
异常。
-
如果 options["
password"] 是HTMLFormElement, 返回执行“从 HTMLFormElement 创建 PasswordCredential” 且以 options["password"] 和 origin 作为参数的结果。 捕获并重新抛出任何异常。 -
如果 options["
password"] 是PasswordCredentialData, 返回 执行“从 PasswordCredentialData 创建 PasswordCredential” 并以 options["password"] 作为参数的结果。 捕获并重新抛出任何异常。
3.3.3.
PasswordCredential 的 [[Store]](credential, sameOriginWithAncestors)
[[Store]](credential, sameOriginWithAncestors)
使用一个 PasswordCredential
(credential)以及一个布尔值调用,当且仅当调用
上下文与其祖先同源时,该布尔值为 true
(sameOriginWithAncestors)。一旦 credential 被持久化到凭据存储中,该算法就返回
undefined。
如果 sameOriginWithAncestors 不是
true,该算法将返回一个 NotAllowedError。
-
如果 sameOriginWithAncestors 为
false,则在不更改用户代理的 凭据存储的情况下抛出一个 "NotAllowedError"DOMException。注意:此限制旨在解决 § 6.4 源混淆中提出的问题。
-
如果用户代理的凭据存储包含一个
PasswordCredential(stored), 其id属性是 credential 的id并且其[[origin]]槽与 credential 的[[origin]]同源, 则:-
如果用户授予更新凭据的权限(如定义 用户 中介时所讨论的),则:
否则,如果用户授予存储凭据的权限(如定义 用户 中介时所讨论的,则:
-
在凭据存储中存储一个具有以下 属性的
PasswordCredential:id-
credential 的
id name,-
credential 的
name iconURL-
credential 的
iconURL [[origin]]-
credential 的
[[origin]] password-
credential 的
password
-
3.3.4. 通过 HTMLFormElement 创建 PasswordCredential
要在给定一个
HTMLFormElement
(form)和一个源(origin)的情况下,从
HTMLFormElement 创建一个 PasswordCredential,运行以下步骤。
注意:§ 3.1.2 登录后确认和§ 3.1.3 更改密码 提供了预期 用法的示例。
-
令 data 为一个新的
PasswordCredentialData字典。 -
将 data 的
origin成员的值设置为 origin 的值。 -
令 formData 为在 form 上执行
FormData构造函数 的结果。 -
令 newPasswordObserved 为
false。 -
对于 elements 中的每个 field,运行以下步骤:
-
如果 field 没有
autocomplete属性,则跳到下一个 field。 -
令 name 为 field 的
name属性的值。 -
如果在 name 上执行 formData 的
has()方法返回false,则 跳到下一个 field。 -
如果 field 的
autocomplete属性的值包含一个或多个自动填充详细信息令牌(tokens), 则:-
对于 tokens 中的每个 token:
-
如果 token 与以下字符串之一进行ASCII 不区分大小写匹配, 则运行 相应步骤:
- "
new-password" -
将 data 的
password成员的 值设置为在 name 上执行 formData 的get()方法的结果,并将 newPasswordObserved 设置为true。 - "
current-password" -
如果 newPasswordObserved 为
false, 则将 data 的password成员的 值设置为在 name 上执行 formData 的get()方法的结果。注意:通过 检查 newPasswordObserved 是否为
false,new-password字段优先于current-password字段。 - "
photo" - "
name"- "
nickname" - "
- "
username"
- "
-
-
-
-
令 c 为在 data 上执行从
PasswordCredentialData创建一个PasswordCredential的结果。如果这抛出了一个异常,则重新抛出该 异常。 -
断言:c 是一个
PasswordCredential。 -
返回 c。
3.3.5. 通过
PasswordCredentialData 创建 PasswordCredential
要利用
PasswordCredentialData 创建 PasswordCredential,给定一个
PasswordCredentialData
(data),按如下步骤操作。
-
令 c 为一个新的
PasswordCredential对象。 -
设置 c 的属性如下:
-
返回 c。
3.3.6.
CredentialRequestOptions 与 PasswordCredential 的匹配
给定一个 CredentialRequestOptions
(options),如果
PasswordCredential
应可作为 get()
请求的响应,则以下算法返回 "Matches",否则返回 "Does Not Match"。
-
如果 options 有一个
password成员,其值为true,则 返回 "Matches"。 -
返回 "
Does Not Match"。
4. 联合凭证
4.1.
FederatedCredential 接口
[Exposed =Window ,SecureContext ]interface :FederatedCredential Credential {constructor (FederatedCredentialInit );data readonly attribute USVString provider ;readonly attribute DOMString ?protocol ; };FederatedCredential includes CredentialUserData ;dictionary {FederatedCredentialRequestOptions sequence <USVString >;providers sequence <DOMString >; };protocols partial dictionary CredentialRequestOptions {FederatedCredentialRequestOptions ; };federated
provider, 类型为 USVString,只读-
凭据的联合身份提供者。关于有效格式详见 § 4.1.1 标识 Provider。
protocol, 类型为 DOMString,只读,允许为 null-
凭据的联合身份提供者协议(例如 "
openidconnect")。如果该值为null, 可根据provider推断协议。 [[type]]-
FederatedCredential接口对象具有名为[[type]]的内部槽, 其值为 "federated"。 [[discovery]]-
FederatedCredential接口对象具有名为[[discovery]]的内部槽, 其值为 "credential store"。 FederatedCredential(data)-
该构造函数接受
FederatedCredentialInit(data),并执行下列步骤:-
令 r 为执行 “从 FederatedCredentialInit 创建 FederatedCredential” 的结果。如果抛出 异常,则重新抛出。
-
返回 r。
-
FederatedCredential
对象可以通过将 FederatedCredentialInit
字典
传递给 navigator.credentials.create()
来创建。
dictionary :FederatedCredentialInit CredentialData {USVString ;name USVString ;iconURL required USVString ;origin required USVString ;provider DOMString ; };protocol partial dictionary CredentialCreationOptions {FederatedCredentialInit ; };federated
FederatedCredential
对象是与源绑定的。
FederatedCredential
的
接口对象继承自 Credential
的
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
实现,
并自行定义
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)、
[[Create]](origin, options, sameOriginWithAncestors)
以及
[[Store]](credential, sameOriginWithAncestors)
的实现。
注意: 如果将来用户代理支持代表用户获取认证令牌,可通过实现
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
来实现此能力。
4.1.1. 身份提供者标识
每个网站在引用某个特定联合身份提供者时应使用相同的标识符。例如,Facebook Login 不应被称为 "Facebook"、"Facebook Login"、"FB"、"FBL"、"Facebook.com" 等等。应有一个规范标识符供所有人使用,因为一致的标识有助于用户代理提供帮助。
为保持一致,传递到本文档定义的 API(如 FederatedCredentialRequestOptions
的
providers
数组或 FederatedCredential
的
provider
属性)必须用该提供者用于登录的 origin 的 ASCII 序列化来标识。也就是说,Facebook 用
https://www.facebook.com,Google 用 https://accounts.google.com。
这种 origin 的序列化不包括结尾的 U+002F
斜杠("/"),但用户代理应默默接受它:https://accounts.google.com/ 显然与
https://accounts.google.com 相同。
4.2. 算法
4.2.1.
FederatedCredential 的
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)
被调用时,参数为 origin(origin),CredentialRequestOptions
(options),
以及仅当调用上下文与祖先同源
时为 true 的布尔值(sameOriginWithAncestors)。
该算法返回来自凭据存储的一组 Credential
对象。如果无匹配的 Credential
,则返回集合为空。
-
如果 sameOriginWithAncestors 为
false,抛出 "NotAllowedError"DOMException。注意: 此限制旨在解决 § 6.4 源混淆 中提出的问题。
-
如果 options["
federated"] 不是true,则返回空集合。
4.2.2. FederatedCredential 的
[[Create]](origin, options, sameOriginWithAncestors)
[[Create]](origin, options, sameOriginWithAncestors)
被调用时,参数为 origin(origin)、CredentialCreationOptions
(options),以及一个仅当调用上下文与其祖先同源时为 true
的布尔值(sameOriginWithAncestors)。
该算法在可以创建时返回一个 FederatedCredential,否则返回
null,异常情况下抛出 异常:
-
返回执行 通过 FederatedCredentialInit 创建 FederatedCredential, 参数为 options["
federated"] 的结果。如果抛出 异常,则重新抛出该异常。
4.2.3.
FederatedCredential 的 [[Store]](credential, sameOriginWithAncestors)
[[Store]](credential, sameOriginWithAncestors)
被调用时,传入一个 FederatedCredential
(credential),以及一个仅当调用上下文与其祖先同源时为 true 的布尔值
(sameOriginWithAncestors)。当 credential 持久化到 凭证存储后,该算法返回
undefined。
如果 sameOriginWithAncestors 不是 true,算法将返回 NotAllowedError。
-
如果 sameOriginWithAncestors 为
false,则抛出 "NotAllowedError"DOMException, 并且不修改用户代理的 凭证存储。注意:此限制旨在解决 § 6.4 源混淆中提出的问题。
-
如果用户代理的 凭证存储中已包含一个
FederatedCredential, 其id属性等于 credential 的id, 且其[[origin]]槽与 credential 的[[origin]]同源, 且其provider等于 credential 的provider, 则直接返回。 -
如果用户允许存储凭证(见 用户介入 的定义),则在 凭证存储中存储一个
FederatedCredential, 其属性如下:
4.2.4. 通过
FederatedCredentialInit 创建 FederatedCredential
要 通过
FederatedCredentialInit 创建 FederatedCredential,给定一个 FederatedCredentialInit
(init),运行如下步骤。
5. 用户介入
通过 API 向 Web 暴露凭据信息可能会对用户隐私产生多种影响。因此,用户代理**必须**在多种场景下让用户参与,以确保用户明白发生了什么、他们的凭据将与谁共享。
我们称某个操作为用户调解,如果它是在获得用户明确同意后发生的。例如,同意可能通过用户在 凭据选择器界面上的直接交互来表达。通常,用户调解操作会涉及向用户展示某种界面,并请其做出决策。
未调解的(unmediated)操作则是在没有用户明确同意的情况下静默发生。例如,如果用户设置浏览器给予某个源持久的凭据访问权限,则可在不弹出决策界面的情况下提供凭据。
下面我们将阐述适用于所有凭据类型的一些通用要求,但请注意,用户代理具有较大自由度(它处于帮助用户的特权位置)。此外,特定凭据类型可能还有超出本文一般性要求的特殊规定。
5.1. 存储和更新凭证
凭据信息属于敏感数据,用户**必须**始终掌控这类信息的存储。无意间的凭据存储,例如,可能会让用户在某台设备上的本地资料与某个特定网络身份意外关联起来。为了降低此类意外风险:
-
未经用户调解,不应存储或更新凭据信息。 例如,用户代理可以在每次调用
store()时,弹出“是否保存此凭据?”对话框。如果用户代理希望提供持久授权(如“始终保存密码”选项),可推断用户已同意(不过我们建议用户代理把范围尽量收窄——比如“始终保存_生成的_密码”或“始终保存本站密码”)。
-
用户代理在存储凭据时应该通知用户,例如通过在地址栏或类似位置显示图标等形式。
-
用户代理**必须**允许用户手动移除已存储的凭据。该功能可作为设置页面提供,或通过如上所述的通知界面实现。
5.2. 强制用户介入
默认情况下,所有用户调解对
所有源都是必需的,因为 防止静默访问标志
在凭据存储中被设置为 true。用户可以选择为某个源授予凭据的持续访问权
(例如“保持登录本站”选项),此时会将该标志设为 false。这样,用户总会自动登录该站点,
从易用性和便捷性角度来看很理想,但也可能带来意外影响(例如,有的用户代理会在多设备间同步这个标志状态)。
为了避免这种意外风险:
-
用户代理必须允许用户为某一源或所有源要求用户调解。该功能可作为全局开关实现,覆盖每个源的 防止静默访问标志,让其都返回
false, 也可以为指定源(或特定源的具体凭据)做更细粒度的设置。 -
用户代理不得在没有用户调解的情况下,将某个源的防止静默访问标志 设为
false。例如,凭据选择器 (见§ 5.3 凭据选择)可带有一个复选框,允许用户勾选后为该源在无调解时开放凭据, 或用户代理可在其凭据管理器引导流程中让用户设置默认选项。 -
用户代理在为某个源提供凭据时必须通知用户,比如可以在地址栏或类似位置显示图标。
-
若用户清除某个源的浏览数据(如 cookies、localStorage 等),用户代理必须将该源的 防止静默访问标志 设为
true。
5.3. 凭证选择
当对某个需要
用户调解的来源调用 get()
时,用户代理**必须**征求用户同意后方可共享凭据信息。
这通常应以凭据选择器的形式呈现:向用户展示可用于该站点的凭据列表,
让他们选择要提供给网站的凭据,或者完全取消此次请求。
选择器 的用户界面应以某种方式易于与网站自身产生的界面区分。例如,选择器可以以用户代理界面重叠且不可伪造的方式显示。
选择器 的界面**必须**包含请求凭据的来源信息。
选择器
的界面应包含关联于请求凭据来源的所有 Credential
对象。
用户代理可以为每个 Credential
对象内部关联额外信息(超出本文档规定属性),
以提升选择器的实用性。例如,可以通过 Favicons 区分不同身份提供方等。但所有这种附加信息**不得**直接暴露给 Web。
选择器 的交互细节未在此规定:鼓励用户代理探索不同 UI 设计,以帮助用户理解认证选项,并引导用户流程做出凭据选择。 不过选择器的接口如下:
Credential,参数包括 CredentialRequestOptions
(options) 及从 凭据存储中发现的一组 Credential
对象(locally discovered
credentials)。
此算法返回值可以是 null(用户拒绝与站点共享凭据时),
也可以是用户所选的 Credential
对象,
或者如果用户选择了某类型凭据,则为相关 Credential
接口对象。
6. 安全注意事项
以下各节为各种安全和隐私注意事项的指导原则。不同凭证类型可能会强制执行更严格或更宽松的版本。
6.1. 跨域凭证访问
凭据属于敏感信息,用户代理在判断何时可以安全地与网站共享时必须格外谨慎。最安全的做法是仅允许凭据分享到其被保存时的确切来源(origin)。然而,这对于 Web 来说往往过于严格:比如功能被分在
example.com 和 admin.example.com 等子域名中的网站。
为在减少用户困扰和保障凭据安全之间取得平衡,用户代理:
-
绝不能在 scheme 组件代表安全性降级的两个源之间共享凭证。也就是说, 可以考虑允许保存在
http://example.com/上的凭证对https://example.com/可用 (鼓励开发者迁移到安全传输),但反过来则很危险。 -
可以使用公共后缀列表 [PSL] 通过比较凭证
[[origin]]的可注册域名 与调用get()时的 origin,来决定一个凭证的有效作用域。即: 保存在https://admin.example.com/和https://example.com/的凭证可以在get()从https://www.example.com/调用时提供给用户,反过来也可以。 -
如果凭证的 origin 不是调用 origin 的精确匹配, 绝不能在没有用户介入 的情况下,响应
get()调用而向一个 origin 提供凭证。也就是说,Credential对象如果属于https://example.com, 不会直接返回给https://www.example.com, 只能通过选择器供用户选择。
6.2. 凭证泄露
开发者应当采取一些预防措施,通过设置合理的内容安全策略(Content Security Policy,[CSP]),限制数据可以发送到的端点,从而降低跨站脚本攻击演变为对用户账户的持久访问的风险。特别是,开发者应确保在页面策略中显式或隐式地设置以下指令:
-
script-src 和 object-src 都限制了页面上的脚本执行,从而降低跨站脚本攻击最初成功的可能性。如果站点正在填充
form元素,还应设置 form-action 指令。 -
connect-src 限制
fetch()可以提交数据的源(这有助于防止凭证被泄露到evil.com)。 -
child-src 限制可以嵌入到页面中的嵌套浏览上下文,从而使得注入恶意
postMessage()目标更加困难。[HTML]
当然,开发者还应正确转义输入输出,并考虑使用其他防护措施,比如 Subresource Integrity [SRI] 进一步降低风险。
定义具体凭证类型时,应充分考虑凭证数据的传输方式。例如,可以定义仅限同源端点的传输机制。
6.3. 不安全站点
用户代理不得在非安全环境中暴露此处定义的 API。用户代理可能实现自动填充机制,在非潜在可信URL上存储用户凭证并填充登录表单,但这些站点无法被信任以任何有意义的方式直接与凭证管理器交互,并且这些站点不得访问存储在安全环境中的凭证。
6.4. 源混淆
如果被嵌入的页面能够访问本规范定义的 API,可能会导致用户被误导,将凭证授权给非顶级浏览上下文的源,而用户只能合理理解顶级上下文的安全源。
本规范将凭证管理 API 暴露给这些上下文,因为某些凭证类型在用户代理给予足够 UI 支持和上下文提示后,可以安全地暴露。
但某些具体凭证类型很难在这些上下文安全暴露,因此通过在 [[Create]](origin, options, sameOriginWithAncestors)、
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)、
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
和 [[Store]](credential, sameOriginWithAncestors)
方法中进行检查来加以限制。
例如,PasswordCredential
的
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)
方法如果在 Worker
或非 顶级浏览上下文中调用,会立即返回空集合。
6.5. 注销
如 § 5.2 强制用户介入 所述,如果用户选择自动登录网站,则用户代理会在网站请求时提供凭证。网站可通过调用 CredentialsContainer
的
preventSilentAccess()
方法,关闭某源的自动登录行为。
用户代理依赖网站正确操作;若网站疏忽或恶意,未调用此方法,用户代理仍会继续提供凭证,这比网站在用户点击“注销”时不清理凭证更糟,因为用户代理也参与了认证过程。
用户必须能够控制此行为。如 § 5.2 强制用户介入 所述,清除某源的 Cookie 也会将该源在 凭证存储中的防止静默访问标志重置为
true。此外,用户代理应为禁用某源自动登录提供 UI 控件,比如与凭证已被提供通知绑定的选项。
7. 隐私注意事项
7.1. 计时攻击
如果用户没有针对某个源的凭证,调用 get()
时会非常快速地返回结果。恶意网站可能据此区分“没有凭证”与“有凭证但用户选择不分享”的用户。
用户代理也应对凭证请求进行速率限制。短时间内多次请求凭证几乎肯定属于滥用行为。
7.2. 选择器泄露
如果用户代理的凭据选择器会展示来源提供的图片(例如
Credential
显示站点 favicon),那么对这些图片的请求**不得**直接与选择器的实例化关联,以避免泄露选择器的使用情况。一种做法是在保存或更新 Credential
时后台获取图片,
并在Credential生命周期内缓存。
这些图片**必须**使用如下 fetch 设置获取:credentials mode 设为 "omit",
service-workers mode 设为 "none",
client 设为 null,
initiator 设为空字符串,
destination 设为 "subresource"。
此外,如果用户代理允许用户更改凭据关联的名称或图标,则这种数据变动**不应**暴露给网站 (考虑某用户给同一来源的两个凭据命名为“我的小号”和“我的主号”等情况)。
7.3. 本地存储数据
该 API 允许某个 源将数据与用户的个人资料持久存储。由于大多数用户代理对凭证数据和“浏览数据”(如 cookie 等)处理方式不同,这可能导致用户在清除 cookie 时以为所有来源痕迹都被清除,实际仍有凭证数据保留而感到意外。
用户代理应提供 UI,清楚地向用户展示某个源已存储凭证数据,并应让用户易于移除这些数据。
8. 实现注意事项
本节为非规范内容。
8.1. 网站作者
此处应补充关于何时及如何使用该 API
的一些思考,尤其是关于 mediation
的相关内容。
[w3c/webappsec Issue #290]
说明通过
fetch() 携带 FormData
body 提交凭据时的编码限制。
为某一凭据类型做特性检测时,建议开发者验证相关 Credential
的具体实现是否存在,而不要只依赖
navigator.credentials 是否存在。后者只能判断 API 是否提供,但不能确保给定站点需要的那种凭据类型已获支持。例如,若某站点需要密码型凭据,检测
if (window.PasswordCredential) 是最有效的支持性判断方式。
8.2. 扩展点
本文档提供了一个通用的、高层的API,设计之初就支持通过具体的凭据类型扩展,以满足不同认证场景的需求。实现过程希望应当很直观:
-
定义一个继承自
Credential的新接口: -
在
ExampleCredential的 接口对象上定义合适的[[Create]](origin, options, sameOriginWithAncestors)、[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)、[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)和[[Store]](credential, sameOriginWithAncestors)方法。[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)适用于永远 有效 并能直接从凭证存储 取出的凭证, 而[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)则适用于需从凭证源 重新生成的凭证。对于类似
PublicKeyCredential的长时间操作, 包括[[Create]](origin, options, sameOriginWithAncestors)与[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors),推荐使用options.signal以允许开发者中止操作。详细说明见 DOM § 3.3 在 API 中使用 AbortController 和 AbortSignal 对象。ExampleCredential的[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)内部方法会被传入 origin(origin), CredentialRequestOptions 对象(options),以及一个仅调用上下文 与祖先同源时为true的布尔值。 算法返回与选项匹配的一组Credential对象。如没有匹配的Credential,则返回空集。-
断言:
options[example] 存在。 -
如果
options[example] 不为真,返回空集。 -
遍历凭证存储中的每个credential:
-
...
-
-
-
定义
ExampleCredential接口对象 的[[discovery]]槽的值: -
扩展
CredentialRequestOptions,加入新凭证类型需要的选项以便能合理响应get(): -
扩展
CredentialCreationOptions,加入新凭证类型用于响应create()时所需的创建数据: -
若新凭证类型支持
conditional用户介入, 则定义ExampleCredential/isConditionalMediationAvailable()以 返回 resolve 为true的 promise。 -
依照§ 2.1.2.1 注册表项要求与更新流程,为新的 "example" 凭证类型及其内容 在 凭证类型注册表中添加一个条目,包括:
-
CredentialCreationOptions和CredentialRequestOptions的选项成员标识符(本例是 "example"),以及
注: 凭据类型的选项 字典的 选项成员 标识符在
CredentialCreationOptions和CredentialRequestOptions中必须相同, 并且应当 与Credential接口对象的[[type]]槽中的 凭据类型值相同。 -
你可能还会发现有必要增加新原语。例如在某些复杂的多因子登录流程中需要一次性返回多个 Credential
对象。可以以通用方式为
CredentialsContainer
增设 getAll() 方法,返回一个 sequence<Credential>,并定义合理的机制来支持请求不同类型的凭据。
如有此类扩展,建议联系 public-webappsec@ 咨询和评审。
8.3. 浏览器扩展
理想情况下,支持扩展系统的用户代理会允许第三方钩入这些 API 端点,以改善第三方凭证管理软件的行为,就像用户代理通过命令式方式改善自身一样。
9. 未来工作
本节为非规范内容。
此处定义的 API 仅以极简方式将用户代理的凭证管理器暴露给 Web,并允许 Web 协助这些凭证管理器识别何时正在使用联合身份提供者。下一步的合理方向,将会类似于[WEB-LOGIN]等文档中描绘的内容(以及在某种程度上,Mozilla 的 BrowserID [BROWSERID])。
用户代理处于一个独特位置,可以有效调解用户、身份提供者和网站之间的关系。如果用户代理能减少典型认证流程中的风险和困惑,用户的处境将比现在大为改善。
一种自然的信息暴露方式可能是扩展 FederatedCredential
接口,增加如认证令牌等属性,或添加某种声明提供者支持认证类型的 manifest 格式属性。
此处描述的 API 设计为足够可扩展,以支持需要用户交互的用例,甚至可能涉及除了请求凭证的网站之外的其他网站。我们希望我们采用的基于 Promise
的系统,足够扩展以支持此类异步流程,这些流程可能需要多个浏览上下文之间的某种交互(例如 idp.com 上的调解活动可解析传回给 rp.com 的
Promise),或者未来支持设备与用户代理之间的交互(例如 [WEBAUTHN]),而无需从零重新设计
API。
循序渐进。
