WebAuthn(Web 身份验证 API)是一项 W3C 标准,它使用公钥密码学实现安全的无密码身份验证。用户无需密码,而是使用基于硬件的身份验证器(如指纹读取器、Face ID、YubiKeys 等)或平台身份验证器(内置的,如 Touch ID)进行注册和身份验证。
以下展示如何使用 JavaScript 客户端处理注册和身份验证。
WebAuthn 注册流程
用户发起注册:
用户提供用户名并点击注册。
浏览器向服务器请求选项:
前端发起 POST 请求以获取注册选项。
浏览器创建凭据:
通过 SimpleWebAuthnBrowser.startRegistration() 使用 navigator.credentials.create() API 创建凭据。
服务器验证注册:
浏览器将凭据发送回服务器。
服务器验证注册并为该用户存储公钥。
TsgcWSAPIServer_WebAuthn 组件包含一个用于测试 WebAuthn 协议的 HTML 文件。该 HTML 文件包含与 WebAuthn 交互所需的最小 UI 和 JavaScript。
sgcWebAuthn.html 演练
结构概览:
用户名输入:捕获用户的标识符。
按钮:
Register – 启动 WebAuthn 注册。
Authenticate – 启动登录(处理方式类似)。
调试控制台:显示实时调试信息(来自 WebAuthn 的 JSON)。
1. 输入的 HTML UI
<input type="text" id="username" name="username" autocomplete="username webauthn" />
<button id="btnRegBegin"><strong>Register</strong></button>
2. JavaScript:按钮点击处理程序
document.querySelector('#btnRegBegin').addEventListener('click', async () => {
const username = document.getElementById("username").value;
if (username == "") {
document.getElementById('Error').innerText = 'Please enter a username to register';
return;
}
const resp = await fetch('<#webauthn_registration_options>', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, algorithms: [] })
});
const options = await resp.json();
const attResp = await startRegistration(options); // WebAuthn API
3. 服务器响应(HTML 中的虚拟端点)
fetch('/sgcWebAuthn/Registration/Options', ...)
fetch('/sgcWebAuthn/Registration/Verify', ...)
4. 完成注册
const verificationResp = await fetch('/webauthn/register/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(attResp)
});
const verificationJSON = await verificationResp.json();
if (verificationJSON && verificationJSON.verified) {
document.getElementById('Success').innerHTML = `Authenticator registered!`;
}
WebAuthn 身份验证工作原理
用户发起身份验证:
用户提供用户名并点击注册。
浏览器向服务器请求选项:
前端发出 POST 请求以获取身份验证选项。
浏览器创建凭据:
通过 SimpleWebAuthnBrowser.startAuthentication() 使用 navigator.credentials.get() API。
服务器验证身份:
浏览器将凭据发回给服务器。
服务器验证已签名的结果。
1. HTML UI 设置
<div class="container">
<h1>WebAuthn Authentication Sample</h1>
<section id="userdata">
<label for="username">Username:</label>
<input type="text" id="username" name="username" autocomplete="username webauthn" autofocus />
</section>
<button id="btnAuthBegin"><strong>Authenticate</strong></button>
<p id="Success" class="success"></p>
<p id="Error" class="error"></p>
<details open>
<summary>Console</summary>
<textarea id="Debug"></textarea>
</details>
</div>
2. 获取身份验证选项
在调用 startAuthentication 之前,您需要向服务器发送用户名
const resp = await fetch('<#webauthn_authentication_options>', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: document.getElementById("username").value,
user_verification: 'preferred'
}),
});
服务器以包含以下内容的 JSON 对象响应:
{
"challenge": "base64url-encoded-random-string",
"allowCredentials": [
{
"id": "base64url-credential-id",
"type": "public-key"
}
],
"userVerification": "preferred",
"rpId": "yourdomain.com"
}
这称为 PublicKeyCredentialRequestOptions。
3. 接收验证器响应
asseResp 的简化示例如下:
{
"id": "credentialId",
"rawId": "base64url-encoded-id",
"response": {
"authenticatorData": "...",
"clientDataJSON": "...",
"signature": "...",
"userHandle": "..."
},
"type": "public-key",
"clientExtensionResults": {}
}
此响应证明用户:
4. 向服务器发送签名的身份验证响应
用户通过身份验证器交互(通过 startAuthentication())后,您将在 JavaScript 中获得一个响应对象。
const verificationResp = await fetch('/webauthn/authenticate/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(asseResp), // this is the signed response
});
5. 获取服务器响应
服务器将以如下结果回复:
{ "verified": true }
或者,如果出现错误:
{ "verified": false, "error": "Invalid signature" }
您可以在前端代码中处理它:
const result = await verificationResp.json();
if (result.verified) {
document.getElementById('Success').textContent = 'User authenticated!';
} else {
document.getElementById('Error').textContent = 'Authentication failed!';
}