本文目录导读:

我来详细解释PHP中如何实现Passkey(通行密钥)功能。
Passkey 概述
Passkey 是基于 WebAuthn 标准的无密码认证方式,使用公钥加密技术,用户通过生物识别或设备PIN完成认证。
PHP 实现方案
使用现成库(推荐)
Webauthn/webauthn-lib(最流行)
composer require web-auth/webauthn-lib
基础实现示例
注册流程(Registration)
<?php
use Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\PublicKeyCredentialRpEntity;
use Webauthn\PublicKeyCredentialUserEntity;
use Webauthn\PublicKeyCredentialParameters;
use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\PublicKeyCredentialLoader;
use Webauthn\AuthenticatorAssertionResponse;
use Webauthn\AuthenticatorAttestationResponse;
class PasskeyService {
private $server;
public function __construct() {
// 初始化服务器
$rpEntity = new PublicKeyCredentialRpEntity(
'your-domain.com', // 域名
'Your App' // 应用名称
);
// 配置存储(需要实现)
$this->server = new WebauthnServer($rpEntity);
}
// 1. 生成注册选项
public function getRegistrationOptions($userId, $userName) {
$userEntity = new PublicKeyCredentialUserEntity(
$userId,
$userName,
$userName
);
$options = new PublicKeyCredentialCreationOptions(
$userEntity,
new PublicKeyCredentialParameters('public-key', -7), // ES256算法
$this->server->getRpEntity(),
$this->generateChallenge()
);
return $options;
}
// 2. 处理注册响应
public function handleRegistration($requestData, $userId) {
$publicKeyCredentialLoader = new PublicKeyCredentialLoader();
try {
$credential = $publicKeyCredentialLoader->loadJSON($requestData);
if ($credential->getResponse() instanceof AuthenticatorAttestationResponse) {
// 验证并存储凭证
$publicKeyCredentialSource = $this->server->processCredentialCreation(
$requestData,
$credential
);
// 保存到数据库
$this->saveCredential($userId, $publicKeyCredentialSource);
return ['success' => true];
}
} catch (\Exception $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
// 3. 生成认证选项
public function getAuthenticationOptions($userCredentials) {
$options = new PublicKeyCredentialRequestOptions(
$this->generateChallenge(),
$userCredentials
);
return $options;
}
// 4. 处理认证响应
public function handleAuthentication($requestData, $userCredentials) {
try {
$publicKeyCredentialLoader = new PublicKeyCredentialLoader();
$credential = $publicKeyCredentialLoader->loadJSON($requestData);
if ($credential->getResponse() instanceof AuthenticatorAssertionResponse) {
$authenticatorData = $this->server->processCredentialAuthentication(
$requestData,
$credential,
$userCredentials
);
// 认证成功
return ['success' => true, 'credentialId' => $credential->getId()];
}
} catch (\Exception $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
private function generateChallenge() {
return base64_encode(random_bytes(32));
}
private function saveCredential($userId, $credential) {
// 实现数据库存储逻辑
}
}
前端集成
HTML + JavaScript
<!DOCTYPE html>
<html>
<head>Passkey Demo</title>
</head>
<body>
<button id="registerBtn">注册Passkey</button>
<button id="loginBtn">Passkey登录</button>
<script>
class PasskeyClient {
constructor() {
this.baseUrl = 'https://your-domain.com';
}
// 注册
async register() {
try {
// 1. 获取注册选项
const optionsResponse = await fetch(`${this.baseUrl}/api/passkey/register-options`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({userId: 'user-123'})
});
const options = await optionsResponse.json();
// 2. 调用浏览器API
const credential = await navigator.credentials.create({
publicKey: options
});
// 3. 发送给服务器验证
const verifyResponse = await fetch(`${this.baseUrl}/api/passkey/register`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
credential: this.transformCredential(credential)
})
});
if (verifyResponse.ok) {
alert('注册成功!');
}
} catch (error) {
console.error('注册失败:', error);
}
}
// 登录
async login() {
try {
// 1. 获取认证选项
const optionsResponse = await fetch(`${this.baseUrl}/api/passkey/verify-options`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username: 'user@example.com'})
});
const options = await optionsResponse.json();
// 2. 调用浏览器API
const credential = await navigator.credentials.get({
publicKey: options
});
// 3. 验证
const verifyResponse = await fetch(`${this.baseUrl}/api/passkey/verify`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
credential: this.transformCredential(credential)
})
});
if (verifyResponse.ok) {
alert('登录成功!');
}
} catch (error) {
console.error('登录失败:', error);
}
}
// 转换凭证格式
transformCredential(credential) {
return {
id: credential.id,
rawId: this.arrayBufferToBase64(credential.rawId),
type: credential.type,
response: {
clientDataJSON: this.arrayBufferToBase64(credential.response.clientDataJSON),
authenticatorData: credential.response.authenticatorData ?
this.arrayBufferToBase64(credential.response.authenticatorData) : null,
signature: credential.response.signature ?
this.arrayBufferToBase64(credential.response.signature) : null,
attestationObject: credential.response.attestationObject ?
this.arrayBufferToBase64(credential.response.attestationObject) : null
}
};
}
// ArrayBuffer转Base64
arrayBufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
}
// 初始化事件
document.getElementById('registerBtn').addEventListener('click', () => {
const client = new PasskeyClient();
client.register();
});
document.getElementById('loginBtn').addEventListener('click', () => {
const client = new PasskeyClient();
client.login();
});
</script>
</body>
</html>
数据库设计
-- 用户表
CREATE TABLE users (
id VARCHAR(64) PRIMARY KEY,
username VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Passkey凭证表
CREATE TABLE passkey_credentials (
id VARCHAR(255) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
public_key BLOB NOT NULL,
sign_count INT DEFAULT 0,
credential_id VARCHAR(255) NOT NULL,
aaguid VARCHAR(32),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 挑战表(用于存储临时认证挑战)
CREATE TABLE authentication_challenges (
id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64),
challenge BLOB NOT NULL,
expires_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
完整示例代码(PHP 8+)
<?php
require 'vendor/autoload.php';
use Webauthn\Server;
use Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\PublicKeyCredentialUserEntity;
use Webauthn\AttestationStatement\AttestationObjectLoader;
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
class PasskeyController {
private $server;
private $db;
public function __construct() {
$this->db = new PDO('mysql:host=localhost;dbname=passkey_db', 'user', 'password');
// 配置WebAuthn服务器
$rpEntity = new \Webauthn\PublicKeyCredentialRpEntity(
'localhost',
'Passkey Demo'
);
$this->server = Server::create($rpEntity);
$this->server->setCredentialRepository($this->getCredentialRepository());
}
// 处理注册请求
public function handleRegistration() {
$input = json_decode(file_get_contents('php://input'), true);
if (isset($input['username'])) {
// 创建新用户
$userId = bin2hex(random_bytes(16));
$username = $input['username'];
// 保存用户会话数据
$_SESSION['registration_user_id'] = $userId;
// 生成注册选项
$userEntity = new PublicKeyCredentialUserEntity(
$userId,
$username,
$username
);
$options = $this->server->generateRegistration(
$userEntity,
[
\Webauthn\PublicKeyCredentialParameters::create('public-key', -7)
]
);
// 返回给前端
header('Content-Type: application/json');
echo json_encode($options);
}
}
// 验证注册响应
public function verifyRegistration() {
$input = json_decode(file_get_contents('php://input'), true);
if (isset($input['credential'])) {
try {
$credential = $this->server->loadAndCheckAttestationResponse(
$input['credential'],
$_SESSION['registration_user_id']
);
// 获取公钥
$publicKey = $credential->getPublicKeyCredentialSource();
// 保存到数据库
$stmt = $this->db->prepare(
'INSERT INTO passkey_credentials (id, user_id, public_key, credential_id)
VALUES (?, ?, ?, ?)'
);
$stmt->execute([
bin2hex(random_bytes(16)),
$_SESSION['registration_user_id'],
$publicKey->getPublicKey(),
$credential->getId()
]);
echo json_encode(['success' => true]);
} catch (\Exception $e) {
http_response_code(400);
echo json_encode(['error' => $e->getMessage()]);
}
}
}
// 认证流程
public function handleAuthentication() {
$input = json_decode(file_get_contents('php://input'), true);
if (isset($input['username'])) {
// 获取用户凭证
$stmt = $this->db->prepare(
'SELECT * FROM passkey_credentials
WHERE user_id = (SELECT id FROM users WHERE username = ?)'
);
$stmt->execute([$input['username']]);
$credentials = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($credentials) {
// 生成认证选项
$options = $this->server->generateAuthentication(
$this->getCredentialSources($credentials)
);
header('Content-Type: application/json');
echo json_encode($options);
}
}
}
// 验证认证响应
public function verifyAuthentication() {
$input = json_decode(file_get_contents('php://input'), true);
try {
$result = $this->server->loadAndCheckAssertionResponse(
$input['credential'],
$this->getCredentialSources()
);
// 认证成功
$_SESSION['user_id'] = $result->getUser()->getId();
echo json_encode(['success' => true]);
} catch (\Exception $e) {
http_response_code(400);
echo json_encode(['error' => $e->getMessage()]);
}
}
// 获取凭证仓库
private function getCredentialRepository() {
return new class implements \Webauthn\PublicKeyCredentialSourceRepository {
private $db;
public function __construct() {
$this->db = new PDO('mysql:host=localhost;dbname=passkey_db', 'user', 'password');
}
public function findOneByCredentialId(string $publicKeyCredentialId):
?\Webauthn\PublicKeyCredentialSource {
// 从数据库查找凭证
$stmt = $this->db->prepare(
'SELECT * FROM passkey_credentials WHERE credential_id = ?'
);
$stmt->execute([$publicKeyCredentialId]);
$credential = $stmt->fetch(PDO::FETCH_ASSOC);
if ($credential) {
return \Webauthn\PublicKeyCredentialSource::createFromArray(
json_decode($credential['public_key'], true)
);
}
return null;
}
public function saveCredentialSource(
\Webauthn\PublicKeyCredentialSource $publicKeyCredentialSource
): void {
// 保存凭证
}
public function findAllByUserHandle(string $userHandle): array {
// 获取用户的所有凭证
$stmt = $this->db->prepare(
'SELECT * FROM passkey_credentials WHERE user_id = ?'
);
$stmt->execute([$userHandle]);
$credentials = $stmt->fetchAll(PDO::FETCH_ASSOC);
$sources = [];
foreach ($credentials as $credential) {
$sources[] = \Webauthn\PublicKeyCredentialSource::createFromArray(
json_decode($credential['public_key'], true)
);
}
return $sources;
}
};
}
}
路由配置
// routes.php
$router->post('/api/passkey/register-options', 'PasskeyController@handleRegistration');
$router->post('/api/passkey/register', 'PasskeyController@verifyRegistration');
$router->post('/api/passkey/verify-options', 'PasskeyController@handleAuthentication');
$router->post('/api/passkey/verify', 'PasskeyController@verifyAuthentication');
最佳实践建议
-
安全要求
- 使用HTTPS(必须)
- 有效且安全的域名
- 实施速率限制
- 使用CSRF保护
-
用户体验
- 提供降级方案(密码登录)
- 清晰的错误提示
- 支持多设备
-
配置要求
- 确保现代浏览器支持
- 正确配置CORS
- 处理移动端兼容性
-
测试建议
- 使用浏览器模拟器测试
- 测试不同认证器设备
- 确保错误处理完善
常见问题处理
// 错误处理示例
try {
// Passkey操作
} catch (\Webauthn\Exception\InvalidDataException $e) {
// 无效数据
http_response_code(400);
} catch (\Webauthn\Exception\ChallengeException $e) {
// 挑战验证失败
http_response_code(401);
} catch (\Webauthn\Exception\UserNotFoundException $e) {
// 用户不存在
http_response_code(404);
} catch (\Throwable $e) {
// 其他错误
error_log($e->getMessage());
http_response_code(500);
}
这是一个完整的Passkey实现方案,建议使用成熟库处理底层细节,并完善错误处理和日志记录。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播