账号密码凭证管理

网站通常会自己实现一套登录系统,需要账号和密码作为登录信息。使用凭证管理 API 进行账号密码凭证管理,即可方便快捷地实现自动登录,提升用户体验。

凭证管理 API 提供了 PasswordCredential 凭据对象,需要将账号密码信息转换为凭证之后再进行存取操作。

PasswordCredential 实现了 Credetial 的接口。PasswordCredential 初始化传入的对象需要包含以下信息:

  • id: 必须 账号
  • password: 必须 密码
  • name: 非必需 用户名
  • iconURL: 非必需 用户头像,注意 URL 三个字母均为大写

如:

  1. let cred = new PasswordCredential({
  2. id: profile.id,
  3. password: profile.password,
  4. name: profile.name,
  5. iconURL: profile.iconUrl
  6. });

其中 nameiconURL 是用于账号选择器的显示,因为相比于不易阅读的账号,使用用户名和头像进行账号区分,会显得更加友好。可以在用户登录成功时,从服务端返回相应的信息供存储。

存储登录信息

调用 navigator.credentials.store() 这个方法进行登录信息存储,如:

  1. if (navigator.credentials) {
  2. var cred = new PasswordCredential({
  3. id: formData.get('usr'),
  4. password: formData.get('pwd'),
  5. // name: nickName,
  6. // iconURL: iconUrl
  7. });
  8. var promise = navigator.credentials.store(cred);
  9. // 后续操作
  10. }

此时会弹出对话框询问用户是否对登录信息进行存储:

保存登录信息对话框

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

获取用户登录信息

在调用 navigator.credentials.get() 方法时,需要传入参数 password: true 才能够返回类型为 PasswordCredential 的登录信息。

举例代码如下:

  1. if (navigator.credentials) {
  2. navigator.credentials.get({
  3. password: true
  4. });
  5. }

在获取到登录信息之后,可以通过判断 cred.type === 'password' 来进一步确认获取到的登录信息属于 PasswordCredential 类型:

  1. // 在这样的配置下,账号选择器可能会返回
  2. // `PasswordCredential` 或 `FederatedCredential` 的凭证
  3. navigator.credentials.get({
  4. password: true,
  5. federated: {
  6. providers: ['https://www.baidu.com']
  7. }
  8. })
  9. .then(cred => {
  10. // cred 可能为 undefined
  11. if (cred) {
  12. switch (cred.type) {
  13. case 'password':
  14. // 对 PasswordCredential 凭证进行处理
  15. // ...
  16. }
  17. }
  18. });

示例

完整的示例代码可以 戳这里

首先在登录页填写用户名和密码:

登录页填写登录信息

点击登录按钮之后,采用 AJAX 方式提交登录信息。AJAX 请求返回如下:

  1. {
  2. "name": "测试名",
  3. "icon": "https://lavas-project.github.io/pwa-demo/credential-demo/images/logo-48x48.png"
  4. }

然后,调用凭证管理 API 进行登录信息存储后跳转至登录成功页,代码如下:

  1. // fetch('./login.json') 为假装登录并获取登录信息
  2. fetch('./login.json')
  3. .then(res => {
  4. if (res.status === 200) {
  5. return res.json();
  6. }
  7. return Promise.reject(res.status);
  8. })
  9. // 此处假装登录成功
  10. .then(data => {
  11. // 此处调用凭证管理 API 进行登录信息存储
  12. if (navigator.credentials) {
  13. // 生成密码凭据
  14. let cred = new PasswordCredential({
  15. id: usr.value,
  16. password: pwd.value,
  17. name: data.name,
  18. iconURL: data.icon
  19. });
  20. // 登录信息存储
  21. return navigator.credentials.store(cred)
  22. .then(() => {
  23. return data;
  24. });
  25. }
  26. return Promise.resolve(data);
  27. })
  28. // 存储完成后再跳转至登录成功页
  29. .then(data => {
  30. window.location.href = './main.html?from=login&username=' + data.name;
  31. });

在存储至少一个登录信息的情况下重新打开登录页,将自动弹出账户选择器:

账户选择器

如果有多个账户的时候,则列表显示多个账户:

存在多个账户的选择器

点击对应的账户,会自动将账户信息填充至登录表单。这个填充过程实际上是开发者自己控制的,我们也可以拿到账户信息之后,直接去自动登录。对应账户信息获取及填充表单的代码如下:

  1. // 获取登录凭证
  2. if (navigator.credentials) {
  3. navigator.credentials.get({
  4. password: true
  5. })
  6. .then(cred => {
  7. if (cred) {
  8. switch (cred.type) {
  9. case 'password':
  10. // 此处为自动填充表单的代码
  11. // 开发者可以根据实际需要对账户信息进行其他处理
  12. usr.value = cred.id;
  13. pwd.value = cred.password;
  14. break;
  15. default:
  16. break;
  17. }
  18. }
  19. });
  20. }

只有一个登录信息的情况下再次打开登录页,将出现自动登录提示信息:

自动登录提示

在登录成功页点击退出登录按钮,则自动注销当前凭证,在下次打开登录页时,将会重新弹出账号选择器,而不会自动登录了。注销凭证的代码如下:

  1. // 点击按钮触发注销凭证事件
  2. $btn.addEventListener('click', () => {
  3. // 注销凭证
  4. navigator.credentials.requireUserMediation()
  5. .then(afterLogout)
  6. .catch(afterLogout);
  7. });