本文目录导读:

在PHP项目中实现FIDO2(WebAuthn)认证,通常需要结合前端JavaScript API和后端PHP库,以下是完整的实现方案:
核心概念
- WebAuthn: 浏览器端API,用于生成和验证凭证
- FIDO2: 包含WebAuthn + CTAP2(客户端到认证器协议)
- 认证器: 安全密钥(YubiKey)、平台认证器(Touch ID、Windows Hello)
推荐PHP库
主流选择:
- web-auth/webauthn-lib(最推荐,功能完善)
- lbuchs/webauthn(轻量级)
- spomky-labs/webauthn-framework(企业级)
安装示例(使用composer):
composer require web-auth/webauthn-lib
完整实现流程
1 注册流程
前端(JavaScript)
// 检测浏览器支持
if (!window.PublicKeyCredential) {
alert('您的浏览器不支持WebAuthn');
return;
}
// 1. 请求注册选项
async function startRegistration() {
const response = await fetch('/api/webauthn/register/begin', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const options = await response.json();
// 2. 转换base64格式
options.challenge = Uint8Array.from(atob(options.challenge), c => c.charCodeAt(0));
options.user.id = Uint8Array.from(atob(options.user.id), c => c.charCodeAt(0));
// 3. 调用浏览器API创建凭证
const credential = await navigator.credentials.create({ publicKey: options });
// 4. 格式化响应数据
const attestationObject = base64urlEncode(new Uint8Array(credential.response.attestationObject));
const clientDataJSON = base64urlEncode(new Uint8Array(credential.response.clientDataJSON));
// 5. 发送到服务器完成注册
const verificationResponse = await fetch('/api/webauthn/register/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: credential.id,
rawId: base64urlEncode(new Uint8Array(credential.rawId)),
type: credential.type,
response: {
attestationObject,
clientDataJSON
}
})
});
const result = await verificationResponse.json();
console.log(result.success ? '注册成功' : '注册失败');
}
function base64urlEncode(buffer) {
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
后端(PHP)
<?php
// 注册起点
require 'vendor/autoload.php';
use Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\PublicKeyCredentialRpEntity;
use Webauthn\PublicKeyCredentialUserEntity;
use Webauthn\PublicKeyCredentialParameters;
use Webauthn\AuthenticatorSelectionCriteria;
// 生成注册选项
function getRegistrationOptions($userId, $username, $displayName) {
$rpEntity = new PublicKeyCredentialRpEntity(
'Your App Name', // 应用名称
'example.com' // 域名
);
$userEntity = new PublicKeyCredentialUserEntity(
$username,
$userId,
$displayName
);
$credentialParameters = [
new PublicKeyCredentialParameters('public-key', -7), // ES256
new PublicKeyCredentialParameters('public-key', -257) // RS256
];
$authenticatorSelection = new AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_PLATFORM,
AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED,
false
);
$options = new PublicKeyCredentialCreationOptions(
$rpEntity,
$userEntity,
random_bytes(32), // challenge
$credentialParameters,
60000, // 超时时间
$authenticatorSelection,
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE
);
// 保存挑战到session
$_SESSION['webauthn_challenge'] = base64_encode($options->getChallenge());
return [
'challenge' => base64_encode($options->getChallenge()),
'rp' => ['name' => $options->getRp()->getName(), 'id' => $options->getRp()->getId()],
'user' => [
'id' => base64_encode($options->getUser()->getId()),
'name' => $options->getUser()->getName(),
'displayName' => $options->getUser()->getDisplayName()
],
'pubKeyCredParams' => [['type' => 'public-key', 'alg' => -7]],
'authenticatorSelection' => ['authenticatorAttachment' => 'platform', 'userVerification' => 'required'],
'timeout' => 60000,
'attestation' => 'none'
];
}
// 完成注册
function verifyRegistration($credentialData) {
// 验证挑战
$expectedChallenge = base64_decode($_SESSION['webauthn_challenge']);
// 解析客户端数据
$clientDataJSON = json_decode(base64_decode($credentialData['response']['clientDataJSON']));
// 验证challenge匹配
$receivedChallenge = base64_decode(strtr($clientDataJSON->challenge, '-_', '+/'));
if ($receivedChallenge !== $expectedChallenge) {
throw new Exception('Challenge不匹配');
}
// 验证来源origin
$expectedOrigin = 'https://example.com';
if ($clientDataJSON->origin !== $expectedOrigin) {
throw new Exception('Origin不匹配');
}
// 存储凭证(简化示例)
$credentialId = base64_decode($credentialData['id']);
$publicKey = $credentialData['response']['attestationObject']; // 实际需要解析
// 保存到数据库
saveCredential($userId, $credentialId, $publicKey);
return true;
}
2 认证流程
前端
// 认证请求
async function startAuthentication() {
const response = await fetch('/api/webauthn/login/begin', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'user@example.com' })
});
const options = await response.json();
// 转换格式
options.challenge = Uint8Array.from(atob(options.challenge), c => c.charCodeAt(0));
options.allowCredentials = options.allowCredentials.map(cred => ({
...cred,
id: Uint8Array.from(atob(cred.id), c => c.charCodeAt(0))
}));
// 调用浏览器API
const assertion = await navigator.credentials.get({ publicKey: options });
// 发送到服务器验证
const verificationResponse = await fetch('/api/webauthn/login/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: assertion.id,
rawId: base64urlEncode(new Uint8Array(assertion.rawId)),
type: assertion.type,
response: {
authenticatorData: base64urlEncode(new Uint8Array(assertion.response.authenticatorData)),
signature: base64urlEncode(new Uint8Array(assertion.response.signature)),
clientDataJSON: base64urlEncode(new Uint8Array(assertion.response.clientDataJSON)),
userHandle: base64urlEncode(new Uint8Array(assertion.response.userHandle))
}
})
});
const result = await verificationResponse.json();
return result.success;
}
后端
// 生成认证选项
function getAuthenticationOptions($username) {
// 从数据库获取用户的凭证
$credentials = getUserCredentials($username);
$allowedCredentials = [];
foreach ($credentials as $cred) {
$allowedCredentials[] = new PublicKeyCredentialDescriptor(
PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY,
base64_decode($cred['credential_id'])
);
}
$options = new PublicKeyCredentialRequestOptions(
random_bytes(32), // challenge
60000,
'example.com',
$allowedCredentials,
UserVerificationRequirement::REQUIRED
);
$_SESSION['auth_challenge'] = base64_encode($options->getChallenge());
return $options;
}
// 验证认证
function verifyAuthentication($response) {
$expectedChallenge = base64_decode($_SESSION['auth_challenge']);
// 从数据库获取公钥
$credentialId = base64_decode($response['id']);
$publicKey = getPublicKeyFromDatabase($credentialId);
// 验证签名(简化,实际使用库完成)
// $verification = \Webauthn\PublicKeyCredentialLoader::loadAndCheckAssertion(...);
return true;
}
数据库表结构
CREATE TABLE webauthn_credentials (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
credential_id VARCHAR(255) NOT NULL UNIQUE,
public_key TEXT NOT NULL,
counter INT DEFAULT 0,
credential_type VARCHAR(50) DEFAULT 'public-key',
transports JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
安全注意事项
- 使用HTTPS(WebAuthn要求安全上下文)
- 验证origin(防止CSRF)
- 使用安全的随机数生成器生成challenge
- 限制重放攻击(使用nonce和时间戳)
- 存储公钥安全(使用加密存储)
- 实现速率限制(防止暴力攻击)
完整示例项目
推荐参考开源项目:
这是一个完整的FIDO2实现方案,实际部署时需要根据具体需求调整配置和安全措施。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播