WebAuthn: Passwordless 认证实现

发布时间: 更新时间: 总字数:2066 阅读时间:5m 作者:IP:上海 网址

WebAuthn (Web Authentication API) 是现代网络安全领域的一项革命性技术。它由 W3C 和 FIDO 联盟共同制定,旨在提供一种**无密码(Passwordless)强多因素认证(MFA)**的标准化方案。本文详细介绍 WebAuthn 的概念、实现原理以及在 Golang 和 Python 中的常见实现库。

WebAuthn 介绍

Passkeys(通行密钥) 底层的核心技术就是 WebAuthn。

核心概念

传统登录依赖于“你知道什么”(密码),一旦密码泄露(如撞库、钓鱼网站),账户就不安全了。 WebAuthn 依赖于非对称加密(公钥/私钥),核心是“你拥有什么”(认证设备)和“你是什么”(生物特征)。

  • Relying Party (RP / 依赖方):你的网站或后端服务器。
  • Client (客户端):用户的浏览器或操作系统(如 Chrome, iOS, Windows)。
  • Authenticator (认证器)
    • 漫游认证器 (Roaming):如 YubiKey 等 USB/NFC 硬件密钥。
    • 平台认证器 (Platform):设备自带的,如苹果的 Touch ID/Face ID,Windows Hello,安卓指纹。

WebAuthn 的优势

  • 防钓鱼 (Phishing-Resistant):认证过程与域名(Origin)严格绑定。如果在假冒网站上,认证器不会提供真实的签名。
  • 无密码泄露风险:服务器只存储公钥,私钥永远保存在用户的认证器安全芯片中,且不可导出。即使服务器数据库被脱裤,黑客拿到的也只是一堆毫无用处的公钥。
  • 用户体验极佳:用户只需按一下指纹或刷一下脸即可完成登录。

WebAuthn 实现原理(工作流程)

WebAuthn 的底层逻辑是挑战-应答(Challenge-Response)机制结合非对称加密。主要分为两个阶段:注册(Registration)认证/登录(Authentication)

阶段一:注册流程 (Registration)

目标:在用户设备上生成一对密钥,并将公钥存到服务器。

  1. 用户发起请求:用户在前端点击“绑定 Passkey / 注册”。
  2. 服务器生成 Challenge:后端生成一段随机字符串(Challenge),以及依赖方信息(RP ID,通常是域名)和用户信息,发送给前端。
  3. 前端调用 API:前端调用浏览器的 navigator.credentials.create() 方法,传入服务器给的参数。
  4. 设备验证用户:浏览器唤起认证器(弹出指纹/面容/PIN码提示)。用户验证通过后,认证器专门为该域名生成一对新的 RSA 或 ECDSA 密钥对
  5. 返回公钥:认证器将私钥安全存储在本地,用私钥对 Challenge 签名,并将公钥和签名通过浏览器返回给前端。
  6. 服务器验证并存储:前端将数据发给后端。后端验证签名和 Challenge,确认无误后,将该用户的 ID 与公钥绑定,存入数据库。

阶段二:登录/认证流程 (Authentication)

目标:验证用户是否拥有之前注册的私钥。

  1. 用户发起登录:用户输入用户名(或直接点击“使用 Passkey 登录”)。
  2. 服务器生成 Challenge:后端生成新的 Challenge,并查出该用户之前绑定的所有公钥对应的 Credential ID,发给前端。
  3. 前端调用 API:前端调用浏览器的 navigator.credentials.get(),传入 Challenge 和允许使用的 Credential ID。
  4. 设备验证与签名:浏览器唤起认证器(指纹/面容)。认证器找到对应的私钥,使用私钥对 Challenge 进行签名
  5. 返回签名:认证器将签名后的数据返回给前端,前端发给后端。
  6. 服务器验证登录:后端从数据库取出该用户的公钥,用来解密和验证刚收到的签名。如果签名有效且 Challenge 匹配,则登录成功。

常见的 Golang / Python 实现库

实现 WebAuthn 后端非常复杂,因为需要处理各种加密算法、CBOR 数据解码、ASN.1 格式解析以及 FIDO 联盟的规范。强烈建议使用成熟的开源库,不要自己造轮子。

Golang 常用库

首选库:github.com/go-webauthn/webauthn

  • 简介:这是 Go 语言生态中最权威、使用最广泛的 WebAuthn 库。它最初由 Duo Labs 开发,后来移交给了开源社区维护。

  • 特点:完全实现了 W3C Web Authentication 规范,支持所有主流的 Attestation 格式,API 设计非常贴合 Go 的习惯。

  • 基本使用逻辑

    go
    import `github.com/go-webauthn/webauthn/webauthn`
    
    // 1. 初始化 WebAuthn 实例
    wconfig := &webauthn.Config{
        RPDisplayName: `My Website`, // 网站名称
        RPID:          `example.com`, // 域名
        RPOrigins:     []string{`https://example.com`},
    }
    webAuthn, err := webauthn.New(wconfig)
    
    // 2. 注册:开始 (生成 Challenge 发给前端)
    options, sessionData, err := webAuthn.BeginRegistration(user)
    
    // 3. 注册:完成 (接收前端数据,验证并存下公钥)
    credential, err := webAuthn.FinishRegistration(user, sessionData, request)
    
    // 4. 登录:开始
    options, sessionData, err := webAuthn.BeginLogin(user)
    
    // 5. 登录:完成
    credential, err := webAuthn.FinishLogin(user, sessionData, request)

Python 常用库

首选库:webauthn (PyPI package)

  • GitHubgithub.com/duo-labs/py_webauthn

  • 简介:由安全公司 Duo Labs 维护的官方推荐 Python 库。

  • 特点:支持 Python 3.8+,提供类型提示(Type Hints),代码非常简洁,可以无缝集成到 Django, Flask, FastAPI 等框架中。

  • 基本使用逻辑

    python
    from webauthn import generate_registration_options, verify_registration_response
    from webauthn import generate_authentication_options, verify_authentication_response
    
    # 1. 注册:生成配置和 Challenge (发给前端)
    options = generate_registration_options(
        rp_id=`example.com`,
        rp_name=`My Website`,
        user_id=b`user_123`,
        user_name=`test@example.com`,
    )
    
    # 2. 注册:验证前端传回的数据
    verification = verify_registration_response(
        credential=request.json(),
        expected_challenge=stored_challenge, # 之前存在 Redis/Session 的
        expected_origin=`https://example.com`,
        expected_rp_id=`example.com`,
    )
    # 验证成功后,将 verification.credential_data 存入数据库
    
    # 3. 登录:生成配置 (发给前端)
    options = generate_authentication_options(
        rp_id=`example.com`,
        allow_credentials=[...] # 数据库里查出来的该用户绑定的凭证ID
    )
    
    # 4. 登录:验证前端传回的数据
    verification = verify_authentication_response(
        credential=request.json(),
        expected_challenge=stored_challenge,
        expected_origin=`https://example.com`,
        expected_rp_id=`example.com`,
        credential_public_key=user_public_key_from_db, # 数据库里的公钥
        credential_current_sign_count=sign_count_from_db,
    )

建议

  1. 前端配合:除了后端库,前端还需要调用 @github/webauthn-json@simplewebauthn/browser 等封装好的 JS 库,把浏览器原生的 ArrayBuffer 数据转成友好的 Base64URL JSON 格式与后端通信。
  2. HTTPS 是必须的:WebAuthn 规范要求必须在安全的上下文(HTTPS 或 localhost)中才能运行。
  3. 回退机制:由于并不是所有老旧设备都支持 WebAuthn,在实现时通常将其作为一种“升级”登录方式,建议保留邮箱验证码或魔术链接(Magic Link)作为账户恢复的备用手段。