WebAuthn: Passwordless 认证实现
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)
目标:在用户设备上生成一对密钥,并将公钥存到服务器。
- 用户发起请求:用户在前端点击“绑定 Passkey / 注册”。
- 服务器生成 Challenge:后端生成一段随机字符串(Challenge),以及依赖方信息(RP ID,通常是域名)和用户信息,发送给前端。
- 前端调用 API:前端调用浏览器的
navigator.credentials.create()方法,传入服务器给的参数。 - 设备验证用户:浏览器唤起认证器(弹出指纹/面容/PIN码提示)。用户验证通过后,认证器专门为该域名生成一对新的 RSA 或 ECDSA 密钥对。
- 返回公钥:认证器将私钥安全存储在本地,用私钥对 Challenge 签名,并将公钥和签名通过浏览器返回给前端。
- 服务器验证并存储:前端将数据发给后端。后端验证签名和 Challenge,确认无误后,将该用户的 ID 与公钥绑定,存入数据库。
阶段二:登录/认证流程 (Authentication)
目标:验证用户是否拥有之前注册的私钥。
- 用户发起登录:用户输入用户名(或直接点击“使用 Passkey 登录”)。
- 服务器生成 Challenge:后端生成新的 Challenge,并查出该用户之前绑定的所有公钥对应的 Credential ID,发给前端。
- 前端调用 API:前端调用浏览器的
navigator.credentials.get(),传入 Challenge 和允许使用的 Credential ID。 - 设备验证与签名:浏览器唤起认证器(指纹/面容)。认证器找到对应的私钥,使用私钥对 Challenge 进行签名。
- 返回签名:认证器将签名后的数据返回给前端,前端发给后端。
- 服务器验证登录:后端从数据库取出该用户的公钥,用来解密和验证刚收到的签名。如果签名有效且 Challenge 匹配,则登录成功。
常见的 Golang / Python 实现库
实现 WebAuthn 后端非常复杂,因为需要处理各种加密算法、CBOR 数据解码、ASN.1 格式解析以及 FIDO 联盟的规范。强烈建议使用成熟的开源库,不要自己造轮子。
Golang 常用库
首选库:github.com/go-webauthn/webauthn
-
简介:这是 Go 语言生态中最权威、使用最广泛的 WebAuthn 库。它最初由 Duo Labs 开发,后来移交给了开源社区维护。
-
特点:完全实现了 W3C Web Authentication 规范,支持所有主流的 Attestation 格式,API 设计非常贴合 Go 的习惯。
-
基本使用逻辑:
goimport `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)
-
GitHub:
github.com/duo-labs/py_webauthn -
简介:由安全公司 Duo Labs 维护的官方推荐 Python 库。
-
特点:支持 Python 3.8+,提供类型提示(Type Hints),代码非常简洁,可以无缝集成到 Django, Flask, FastAPI 等框架中。
-
基本使用逻辑:
pythonfrom 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, )
建议
- 前端配合:除了后端库,前端还需要调用
@github/webauthn-json或@simplewebauthn/browser等封装好的 JS 库,把浏览器原生的ArrayBuffer数据转成友好的 Base64URL JSON 格式与后端通信。 - HTTPS 是必须的:WebAuthn 规范要求必须在安全的上下文(HTTPS 或 localhost)中才能运行。
- 回退机制:由于并不是所有老旧设备都支持 WebAuthn,在实现时通常将其作为一种“升级”登录方式,建议保留邮箱验证码或魔术链接(Magic Link)作为账户恢复的备用手段。