一个没有密码的世界 一个残酷的事实 有更好的方法 工作原理 进一步阅读和资源 摘要

2025-06-07

一个没有密码的世界

一个痛苦的事实

有更好的方法

工作原理

进一步阅读和资源

概括

面对现实吧,如今我们的生活已经离不开密码。事实上,我们的整个网络生活都依赖于密码。但密码不仅会给用户带来诸多问题,也会给我们开发者带来诸多问题。

一个痛苦的事实

大多数人在访问的多个网站上都会使用弱密码(而且可能都是一样的)。社交媒体、电子邮件、生产力工具等等,它们都有不同的密码策略,大多数用户都会想方设法绕过这些策略,比如使用弱密码,或者在便条上写一个强密码🤷🏽‍♂️。

事实上,由于黑客攻击而发生的所有数据泄露事件中有 81% 都是利用被盗或弱密码造成的

有更好的方法

但由于W3CFIDO的巨大努力,我们不必事事都使用密码。Web 身份验证 API,又称 Web 身份验证 API,WebAuthn是一个相当新的规范,它引入了一种无需密码的全新身份验证方式,并且与密码同时进行。

谷歌、微软、Yubico 等业内巨头都参与了该规范的推广。该 API 允许服务器使用公钥加密技术(而非密码)来注册和验证用户身份。

Windows Hello、Apple 的 Touch ID、Yubikey 设备都是另一种无需密码的身份验证机制的例子。它们使用生物识别技术或硬件设备来实现这一点。

工作原理

从 1000ft 开始,您将不再使用密码,而是使用为网站生成的密钥对(公钥和私钥) 。

私钥安全地存储在用户的设备上,而公钥连同随机生成的凭证 ID 一起发送到 Web 服务器进行存储,以便稍后用于验证该特定用户。

总体而言,WebAuthn 依赖于三个主要方面:

  • 认证器(进行认证和验证的设备)
  • 依赖方(Web 应用程序所有者)
  • 浏览器的 Web 身份验证 API

流程相当简单,第一步是注册,然后是身份验证。

注册凭证

与密码验证流程相同,其中 Web 服务器通常提供一个表单供用户输入密码,然后将其发送回服务器进行验证。

WebAuthn 与之非常相似,但依赖方不是要求输入密码,而是要求输入公钥。

使用 WebAuthn 注册
图片来自 WebAuthn.io

当提示用户输入凭证时,navigator.credentials.create()使用:

const credential = await navigator.credentials.create(
  {
    publicKey: publicKeyCredentialCreationOptions,
  }
)
Enter fullscreen mode Exit fullscreen mode

如果你想知道它是什么publicKeyCredentialCreationOptions样子的,它包含一堆必填和可选字段:

const publicKeyCredentialCreationOptions = {
  challenge: Uint8Array.from(
    randomStringFromServer,
    c => c.charCodeAt(0)
  ),
  rp: {
    name: 'Google',
    id: 'accounts.google.com',
  },
  user: {
    id: Uint8Array.from('5T9AFCUZSL8', c =>
      c.charCodeAt(0)
    ),
    name: 'me@yashints.dev',
    displayName: 'Yaser',
  },
  pubKeyCredParams: [
    { alg: -7, type: 'public-key' },
  ],
  authenticatorSelection: {
    authenticatorAttachment: 'cross-platform',
  },
  timeout: 60000,
  attestation: 'direct',
}
Enter fullscreen mode Exit fullscreen mode

challenge:选项中最重要的部分是挑战。它是服务器上随机生成的字节,用于防止回复攻击。

rp:代表依赖方,是用户尝试注册的 Web 服务器。

user:这是注册服务的最终用户。id用于将公钥与该用户关联,但建议不要将其用作个人身份信息。

pubKeyCredPrams:指定服务器可以接受哪些类型的公钥。

authenticatorSelection:可选字段,用于帮助依赖方对允许注册的身份验证器进行进一步的限制。其可能的值(platform例如 Windows Hello 或cross-platformYubikey)可在规范中找到。

timeout:用户必须响应注册提示的时间(以毫秒为单位)。

attestation:此参数由身份验证器返回,包含可用于追踪用户的信息。它表明认证对本次注册事件的重要性。例如,如果none使用 ,则表示服务器不关心此认证。indirect表示服务器将允许匿名认证数据。direct表示服务器需要来自身份验证器的数据。通常,这涉及注册过程中的用户隐私。

从调用返回的对象create()包含一个公钥和用于验证用户的其他属性:

console.log(credential);

PublicKeyCredential {
    id: 'ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9...',
    rawId: ArrayBuffer(59),
    response: AuthenticatorAttestationResponse {
        clientDataJSON: ArrayBuffer(121),
        attestationObject: ArrayBuffer(306),
    },
    type: 'public-key'
}
Enter fullscreen mode Exit fullscreen mode

依赖方验证注册数据

一旦服务器接收到公钥以及注册数据的其他属性,就会有一个程序来验证该数据。

当然,具体实现取决于你使用的语言,但如果你想了解如何实现这些步骤,可以参考一些示例。Duo Labs 已经提供了GoPython 的完整实现。

使用 WebAuthn 进行身份验证

完成注册后,用户可以向网站进行身份验证。注册过程中,用户会收到一个断言,表明他们拥有私钥。该断言包含使用私钥签名的签名。Web 服务器在注册过程中使用公钥来验证这一点。

身份验证流程如下所示:

身份验证过程
图片来自 WebAuthn.io

调用该navigator.credentials.get()方法生成断言,证明用户拥有私钥。这将检索注册期间生成的包含签名的凭证。

const credential = await navigator.credentials.get(
  {
    publicKey: publicKeyCredentialRequestOptions,
  }
)
Enter fullscreen mode Exit fullscreen mode

publicKeyCredentialRequestOptions对象包含许多由服务器指定的强制和可选属性:

const publicKeyCredentialRequestOptions = {
  challenge: Uint8Array.from(
    randomStringFromServer,
    c => c.charCodeAt(0)
  ),
  allowCredentials: [
    {
      id: Uint8Array.from(credentialId, c =>
        c.charCodeAt(0)
      ),
      type: 'public-key',
      transports: ['usb', 'ble', 'nfc'],
    },
  ],
  timeout: 60000,
}
Enter fullscreen mode Exit fullscreen mode

这个对象中最有趣的部分是 transport 属性。服务器可以选择指定其偏好的传输方式,例如 USB、NFC 和蓝牙。

返回的对象是一个PublicKeyCredential对象,但与之前注册时的对象稍有不同。

console.log(assertion);

PublicKeyCredential {
    id: 'SSX9lKQmbqdGtbcHDTBsvpu4sjseh4cg2TxSvr4ADSUlN...',
    rawId: ArrayBuffer(59),
    response: AuthenticatorAssertionResponse {
        authenticatorData: ArrayBuffer(191),
        clientDataJSON: ArrayBuffer(118),
        signature: ArrayBuffer(70),
        userHandle: ArrayBuffer(10),
    },
    type: 'public-key'
}
Enter fullscreen mode Exit fullscreen mode

有关此对象的更多信息,请参阅规范

验证身份验证数据

获取断言后,将其发送到服务器进行验证。验证无误后,使用存储的公钥验证签名。

const storedCredential = await getCredentialFromDatabase(
  userHandle,
  credentialId
)

const signedData =
  authenticatorDataBytes + hashedClientDataJSON

const signatureIsValid = storedCredential.publicKey.verify(
  signature,
  signedData
)

if (signatureIsValid) {
  return 'Hooray! User is authenticated! 🎉'
} else {
  return 'Verification failed. 😭'
}
Enter fullscreen mode Exit fullscreen mode

进一步阅读和资源

以下资源是进一步阅读的良好起点:

概括

我们见证了这项令人惊叹的功能的实现是多么简单直接。如果您正在开发一个全新的项目,我强烈建议您一定要考虑实现它,让用户能够无密码地使用🔥👊🏻。对于当前的产品来说,这可以成为一个转折点,通过简化流程中最重要的部分来吸引更多用户加入系统😊。

文章来源:https://dev.to/yashints/a-world-without-passwords-21a
PREV
你知道滚动到文本吗?📜
NEXT
1分钟内制作您自己的自定义 LinkedIn 框架