凭证管理 API

    因此浏览器提供了一套凭据管理 API(Crediential Management API),可以把用户的登录信息直接存储于客户端中,这些信息不会写到 cookie 中,因此安全性很高。同时因为账号密码直接写入本地,安全性和可靠性由浏览器保证,因此就不需要额外设计 token 之类的机制进行校验,从而大大降低开发难度。

    浏览器支持情况可以查阅 Can I Use

    登录信息只有在验证用户登录成功之后才能进行存储,这样的登录信息才会有意义。为了能够获知用户登录成功,同时在登录成功之后调用凭证管理 API 进行用户登录信息存储,登录流程需要进行异步改造。

    登录流程需要完成以下三步:

    1. 实现异步登录;
    2. 登录成功后调用凭证管理 API 进行登录信息存储;
    3. 完成登录成功后页面切换或者是 UI 更新之类的操作。

    创建登录表单

    登录表单的创建比较自由,只要能够让用户输入账号密码信息并且能够让 JS 拿到相关数据即可,不一定非要构建传统的 表单,比如:

    由于传统 form 表单在数据提交时,会发生页面跳转,因此如果采用传统 form 表单的写法,需要监听表单提交事件并且阻止默认的提交行为,再改用 AJAX 进行表单提交:

    1. <form id="login-form">
    2. <input name="usr" type="text">
    3. <input name="pwd" type="password">
    4. <input type="submit" value="提交">
    5. </form>
    1. var form = document.getElementById('login-form');
    2. form.addEventListener('submit', function (e) {
    3. e.preventDefault();
    4. // 改用 AJAX 进行表单提交
    5. });

    异步表单提交

    异步登录的方式也没有太多的限制,可以根据实际项目自行选择 fetchjQuery、原生XHR 等方式实现:

    登录成功之后,就可以对用户登录信息进行存储啦。

    我们需要调用 navigator.credentials.store() 这个方法进行登录信息存储。由于仅有部分浏览器支持凭据管理 API,因此在使用前需要进行方法是否存在的判断:

    1. if (navigator.credentials) {
    2. }

    navigator.credentials.store 的方法定义如下:

    {Promise} navigator.credentials.store({Credetial} cred)

    存储凭证的方法

    返回

    • {Promise} : promise 对象,存储操作完成时,会返回所存储的 cred 的值。

    navigator.credentials.store 之所以是个异步操作,是因为在调用该方法时,浏览器会弹出提示框询问用户是否对登录信息进行存储,如图所示:

    只有当用户选择“保存”时,浏览器才会将登录信息存储起来,点击取消则 promise 将变成 reject。

    参数

    • {Credetial} cred: 凭证对象

    凭证管理 API 提供了两凭据对象: 和 FederatedCredential,这两种凭据对象均实现了 Credential 的接口,可以分别针对 账号密码登录第三方登录 两种模式的登录信息进行存储。

    1. // 账号密码登录凭证登录
    2. if (navigator.credentials) {
    3. var cred = new PasswordCredential({
    4. id: 'TEST_ID_NUMBER',
    5. password: 'TEST_PASSWORD',
    6. name: 'TEST_NICK_NAME',
    7. iconURL: 'path/to/icon'
    8. navigator.credentials.store(cred).then(cred => {
    9. // 后续操作
    10. });
    11. }
    12. /* ------------------------------------ */
    13. // 第三方登录凭证存储
    14. if (navigator.credentials) {
    15. var cred = new FederatedCredential({
    16. id: 'TEST_ID_NUMBER',
    17. provider: 'http://MOCK_PROVIDER',
    18. name: 'TEST_NICK_NAME',
    19. iconUrl: 'path/to/icon'
    20. });
    21. // 后续操作
    22. });
    23. }

    关于 PasswordCredential 凭证对象的存储,可以进一步了解。

    关于 FederatedCredential 凭证对象的存储,可以点这里进一步了解。

    通过 navigator.credentials.get() 方法,可以获取同个域名下用户存储的登录信息。

    举例代码如下:

    如果该域名事先有调用凭证管理 API 进行登录信息存储,在执行上述代码时,将会弹出账号选择器供用户进行账户选择:

    账号选择器

    如果存储的登录信息只有一个,那么还将会隐去账号选择器而直接将唯一的登录信息返回,并且在界面上产生如下提示信息:

    自动登录的触发和自动登录提示信息的显示需要满足一系列条件,详情请参阅小节:。

    navigator.credentials.get 方法的定义如下:

    {Promise} navigator.credentials.get({Object} options)

    获取凭证的方法

    返回

    • {Promise} : promise 对象,当获取凭证操作完成时,会返回所存储的凭证,如果找不到对应的凭证时,则返回 null

    参数

    options 包含以下字段:

    • password:
      {boolean} 是否支持通过密码认证登录
    • federated: 第三方登录
      {Object}
      • providers:
        {Array} 联合登录账号供应者 id 组成的数组
    • unmediated:
      是否显示账号选择器

    只有当 options.password === true 时,账号选择器才会展示类型为 PasswordCredential 的登录信息。

    关于账号密码登录凭证信息的获取,在帐号密码登录凭证管理章节中,会进行详细说明。

    只有当 federated.providers 配置了相应的第三方登录账号提供者 id 列表,账号选择器才会展示类型为 FederatedCredential,同时账号提供者在 providers 列表中的登录信息。

    获取登录信息过滤

    我们可以通过配置不同的 options 去实现账号信息的过滤,减少用户的选择。比如配置如下的时候:

    1. navigator.credentials.get({
    2. password: true,
    3. federated: {
    4. providers: ['https://www.baidu.com']
    5. }
    6. })

    所有的账号密码凭证和第三方登录凭证信息都会罗列出来:

    全部账号信息

    其中带提供方标识的属于第三方登录凭证信息。

    如果只选择获取账号密码凭证:

    1. navigator.credentials.get({password: true})

    账号选择器将只显示帐号密码凭证信息:

    只选择获取第三方登录凭证:

    账号选择器将只显示第三方凭证信息:

    第三方凭证信息

    事实上,如果站点支持多种第三方登录的话,还可以通过配置不同的 providers 数组来进一步缩小第三方登录信息的选择范围。

    有时对于用户来说,在同一个网站仅仅保存了一个账号的情况下,在登录时仍然弹出账号选择器让用户选择的这个过程会显得有些多余。因此可以将 options.unmediated 设置为 true,在调用 navigator.credentials.get(options) 时,能够直接返回一个登录信息,省去账号选择器的显示与选择,帮助用户实现自动登录。自动登录需要满足以下条件:

    • 浏览器已经显式地告知用户正在进行自动登录
    • 用户曾经通过凭证管理 API 登录了网站
    • 用户在该网站只保存了一个认证对象
    • 用户在上一次访问时没有主动退出登录

    因此不太建议将 unmediated 设为 true,而是不对其进行任何赋值操作,让浏览器自动去判断是应该显示账号选择器还是直接实现自动登录。

    在满足条件的情况下,调用 navigator.credentials.get(options) 方法时将不会显示账号选择器,而是直接将唯一的账号信息返回,同时显式地弹出如下提示:

    1. app.logout = function () {
    2. // 处理登出流程
    3. if (navigator.credentials.requireUserMediation) {
    4. navigator.credentials.requireUserMediation();
    5. }
    6. else if (navigator.credentials.preventSilentAccess) {
    7. navigator.credentials.preventSilentAccess();
    8. };

    这样调用 app.logout() 登出后,如果调用 navigator.credentials.get() 时,将不会触发自动登录。