导读:本期聚焦于小伙伴创作的《HTML表单结合WebAuthn实现硬件安全密钥认证完整指南》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《HTML表单结合WebAuthn实现硬件安全密钥认证完整指南》有用,将其分享出去将是对创作者最好的鼓励。

HTML表单结合WebAuthn实现硬件安全密钥认证

在Web应用中,传统的账号密码认证方式存在泄露、撞库等安全风险。WebAuthn(Web Authentication API)是FIDO2标准的核心组成部分,允许网站通过硬件安全密钥、手机生物识别等方式完成无密码认证,大幅提升账户安全性。本文将介绍如何结合HTML表单与WebAuthn实现硬件安全密钥认证流程。

WebAuthn核心概念

WebAuthn的认证流程分为两个核心阶段:注册(Registration)和认证(Authentication),涉及三个关键参与方:

  • 依赖方(Relying Party,RP):需要认证的网站,需提供合法的域名和标识信息

  • 用户(User):使用硬件安全密钥(如YubiKey、支持FIDO的手机)完成认证的终端用户

  • 认证器(Authenticator):用户持有的硬件安全设备,负责生成和存储凭证,完成签名验证

整个流程由浏览器作为中间层,调用navigator.credentials接口完成与认证器的交互,无需开发者直接操作硬件设备。

前期准备

使用WebAuthn需要满足以下环境要求:

  • 浏览器支持:Chrome 67+、Firefox 60+、Edge 18+、Safari 13+均已支持WebAuthn标准

  • 网站必须使用HTTPS协议部署,本地开发环境可使用localhost(非HTTPS的局域网地址不支持WebAuthn)

  • 准备硬件安全密钥:如YubiKey 5系列、Feitian ePass FIDO等支持FIDO2协议的设备

注册阶段实现

注册阶段的作用是让用户将硬件安全密钥与当前账号绑定,生成对应的凭证信息存储到服务端。结合HTML表单的场景中,通常先通过表单收集用户基本信息,再触发注册流程。

1. 前端注册逻辑

首先创建一个基础的HTML表单用于收集用户信息,再添加注册按钮触发WebAuthn注册:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>WebAuthn注册示例</title>
</head>
<body>
  <h3>账号注册</h3>
  <form id="registerForm">
    <div>
      <label for="username">用户名:</label>
      <input type="text" id="username" name="username" required>
    </div>
    <div>
      <label for="displayName">显示名称:</label>
      <input type="text" id="displayName" name="displayName" required>
    </div>
    <button type="button" id="startRegister">绑定硬件安全密钥</button>
  </form>
  <script src="register.js"></script>
</body>
</html>

对应的register.js实现注册逻辑:

// 检查浏览器是否支持WebAuthn
if (!window.PublicKeyCredential) {
  alert('当前浏览器不支持WebAuthn,请更换浏览器后重试');
  throw new Error('WebAuthn not supported');
}

document.getElementById('startRegister').addEventListener('click', async () => {
  const username = document.getElementById('username').value;
  const displayName = document.getElementById('displayName').value;
  if (!username || !displayName) {
    alert('请填写完整的用户信息');
    return;
  }

  try {
    // 1. 从服务端获取注册选项(挑战值、依赖方信息等)
    const optionsResp = await fetch('https://www.ipipp.com/api/webauthn/register-options', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username, displayName })
    });
    const options = await optionsResp.json();

    // 2. 转换服务端返回的需要ArrayBuffer的数据(如challenge、user.id)
    options.challenge = Uint8Array.from(options.challenge, c => c.charCodeAt(0));
    options.user.id = Uint8Array.from(options.user.id, c => c.charCodeAt(0));

    // 3. 调用浏览器API创建凭证,此时会提示用户插入并操作硬件安全密钥
    const credential = await navigator.credentials.create({
      publicKey: options
    });

    // 4. 将凭证信息发送到服务端存储
    const verificationResp = await fetch('https://www.ipipp.com/api/webauthn/register-verify', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        username,
        credential: {
          id: credential.id,
          rawId: Array.from(new Uint8Array(credential.rawId)),
          type: credential.type,
          response: {
            attestationObject: Array.from(new Uint8Array(credential.response.attestationObject)),
            clientDataJSON: Array.from(new Uint8Array(credential.response.clientDataJSON))
          }
        }
      })
    });

    const result = await verificationResp.json();
    if (result.success) {
      alert('硬件安全密钥绑定成功,请保存好您的密钥');
    } else {
      alert('绑定失败:' + result.message);
    }
  } catch (error) {
    alert('注册过程出错:' + error.message);
  }
});

2. 服务端注册逻辑参考

服务端需要实现两个接口:生成注册选项、验证注册凭证。以下是Node.js环境的简单示例:

const express = require('express');
const app = express();
app.use(express.json());

// 生成注册选项接口
app.post('/api/webauthn/register-options', (req, res) => {
  const { username, displayName } = req.body;
  // 生成随机挑战值(实际生产环境需使用加密安全的随机数生成器)
  const challenge = Buffer.from(require('crypto').randomBytes(32)).toString('base64');
  // 依赖方信息,实际生产环境需替换为你的网站域名
  const rp = {
    name: '示例网站',
    id: 'www.ipipp.com'
  };
  // 用户信息
  const user = {
    id: Buffer.from(username).toString('base64'), // 实际生产环境需使用唯一用户ID
    name: username,
    displayName: displayName
  };
  // 注册参数
  const options = {
    challenge: challenge,
    rp: rp,
    user: user,
    pubKeyCredParams: [
      { type: 'public-key', alg: -7 }, // ES256
      { type: 'public-key', alg: -257 } // RS256
    ],
    timeout: 60000,
    attestation: 'direct',
    // 排除已注册的凭证,避免重复绑定
    excludeCredentials: [],
    authenticatorSelection: {
      authenticatorAttachment: 'cross-platform', // 指定使用跨平台认证器(硬件密钥)
      requireResidentKey: false,
      userVerification: 'preferred'
    }
  };
  // 将challenge存到会话中,后续验证使用
  req.session.challenge = challenge;
  res.json(options);
});

// 验证注册凭证接口
app.post('/api/webauthn/register-verify', (req, res) => {
  const { username, credential } = req.body;
  // 实际生产环境需验证challenge、解析attestationObject、存储公钥信息等
  // 此处简化逻辑,仅返回成功
  res.json({ success: true, message: '注册成功' });
});

app.listen(3000, () => console.log('服务启动在3000端口'));

认证阶段实现

认证阶段是用户后续登录时,通过硬件安全密钥验证身份的流程,同样可以结合HTML表单实现。

1. 前端认证逻辑

创建登录表单,触发WebAuthn认证:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>WebAuthn登录示例</title>
</head>
<body>
  <h3>账号登录</h3>
  <form id="loginForm">
    <div>
      <label for="loginUsername">用户名:</label>
      <input type="text" id="loginUsername" name="loginUsername" required>
    </div>
    <button type="button" id="startLogin">使用硬件安全密钥登录</button>
  </form>
  <script src="login.js"></script>
</body>
</html>

对应的login.js实现认证逻辑:

document.getElementById('startLogin').addEventListener('click', async () => {
  const username = document.getElementById('loginUsername').value;
  if (!username) {
    alert('请输入用户名');
    return;
  }

  try {
    // 1. 从服务端获取认证选项
    const optionsResp = await fetch('https://www.ipipp.com/api/webauthn/login-options', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username })
    });
    const options = await optionsResp.json();

    // 2. 转换ArrayBuffer数据
    options.challenge = Uint8Array.from(options.challenge, c => c.charCodeAt(0));
    options.allowCredentials = options.allowCredentials.map(cred => ({
      ...cred,
      id: Uint8Array.from(cred.id, c => c.charCodeAt(0))
    }));

    // 3. 调用浏览器API获取凭证,提示用户操作硬件密钥
    const credential = await navigator.credentials.get({
      publicKey: options
    });

    // 4. 将认证结果发送到服务端验证
    const verificationResp = await fetch('https://www.ipipp.com/api/webauthn/login-verify', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        username,
        credential: {
          id: credential.id,
          rawId: Array.from(new Uint8Array(credential.rawId)),
          type: credential.type,
          response: {
            authenticatorData: Array.from(new Uint8Array(credential.response.authenticatorData)),
            clientDataJSON: Array.from(new Uint8Array(credential.response.clientDataJSON)),
            signature: Array.from(new Uint8Array(credential.response.signature)),
            userHandle: credential.response.userHandle ? Array.from(new Uint8Array(credential.response.userHandle)) : null
          }
        }
      })
    });

    const result = await verificationResp.json();
    if (result.success) {
      alert('登录成功,即将跳转');
      window.location.href = 'https://www.ipipp.com/user/dashboard';
    } else {
      alert('登录失败:' + result.message);
    }
  } catch (error) {
    alert('认证过程出错:' + error.message);
  }
});

注意事项

  • 硬件安全密钥丢失后无法找回,建议引导用户绑定多个认证器,或者保留备用认证方式(如短信验证码、邮箱验证码)

  • 生产环境中挑战值(challenge)必须使用加密安全的随机数生成器生成,避免被伪造

  • 服务端验证时需要校验挑战值的有效性、依赖方ID是否匹配、签名是否合法,防止重放攻击

  • 如果用户使用平台内置认证器(如手机指纹、Windows Hello),可以将authenticatorAttachment设置为'platform',或设置为null允许用户选择认证方式

常见问题排查

  • 如果点击按钮后无反应,检查浏览器是否支持WebAuthn,网站是否使用HTTPS(或localhost)

  • 提示“找不到认证器”,确认硬件密钥已插入设备,或者设备已开启蓝牙/NFC(无线密钥需要)

  • 服务端验证失败,检查challenge是否过期、依赖方ID是否与当前域名一致、凭证信息是否解析正确

WebAuthn硬件安全密钥HTML表单无密码认证FIDO2标准

免责声明:已尽一切努力确保本网站所含信息的准确性。网站部分内容来源于网络或由用户自行发表,内容观点不代表本站立场。本站是个人网站免费分享,内容仅供个人学习、研究或参考使用,如内容中引用了第三方作品,其版权归原作者所有。若内容触犯了您的权益,请联系我们进行处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。前端、网络、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握网站开发与运维所需的核心技术栈。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端逻辑,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。