如何在WordPress网站中集成Passkeys(PHP插件开发方案)
WordPress Passkeys 集成插件实现方案
1. 基础准备与依赖
- 依赖第三方WebAuthn库(推荐
web-auth/webauthn-lib),通过Composer引入后打包到插件的vendor目录 - 确保WordPress环境运行在HTTPS下(本地开发可使用
localhost或自签证书) - 启用PHP会话支持(WordPress默认已启用)
2. 插件基础结构
wp-passkeys/ ├── wp-passkeys.php # 主插件入口 ├── includes/ │ ├── class-webauthn.php # WebAuthn核心逻辑处理类 │ └── admin-settings.php # 后台Passkeys管理页面 ├── assets/ │ ├── js/ │ │ └── passkeys.js # 前端交互脚本 │ └── css/ │ └── passkeys.css # 基础样式 └── vendor/ # Composer依赖目录
3. 主插件文件(wp-passkeys.php)
<?php /* Plugin Name: WP Passkeys Description: 为WordPress添加Passkeys登录与管理功能 Version: 1.0 Author: Your Name License: GPL2 */ if (!defined('ABSPATH')) exit; // 加载Composer自动加载 if (file_exists(__DIR__ . '/vendor/autoload.php')) { require_once __DIR__ . '/vendor/autoload.php'; } // 引入核心文件 require_once __DIR__ . '/includes/class-webauthn.php'; require_once __DIR__ . '/includes/admin-settings.php'; // 激活插件时创建数据库表 register_activation_hook(__FILE__, 'wp_passkeys_activate'); function wp_passkeys_activate() { global $wpdb; $table_name = $wpdb->prefix . 'passkeys'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, user_id mediumint(9) NOT NULL, credential_id varchar(255) NOT NULL, public_key text NOT NULL, device_name varchar(100) DEFAULT '未命名设备', created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY credential_id (credential_id), KEY user_id (user_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } // 加载前端资源 add_action('wp_enqueue_scripts', 'wp_passkeys_enqueue_assets'); add_action('login_enqueue_scripts', 'wp_passkeys_enqueue_assets'); function wp_passkeys_enqueue_assets() { wp_enqueue_script('wp-passkeys-js', plugin_dir_url(__FILE__) . 'assets/js/passkeys.js', [], '1.0', true); wp_enqueue_style('wp-passkeys-css', plugin_dir_url(__FILE__) . 'assets/css/passkeys.css', [], '1.0'); wp_localize_script('wp-passkeys-js', 'wpPasskeys', [ 'ajaxUrl' => admin_url('admin-ajax.php'), 'currentUserId' => get_current_user_id() ]); }
4. WebAuthn核心逻辑(includes/class-webauthn.php)
class WP_Passkeys_WebAuthn { private $rpId; private $rpName; public function __construct() { $this->rpId = $_SERVER['HTTP_HOST']; $this->rpName = get_bloginfo('name'); } // 生成Passkey注册选项(返回给前端) public function generateRegistrationOptions() { if (!is_user_logged_in()) { wp_send_json_error('请先登录'); } $user = wp_get_current_user(); $webauthnOptions = new \WebAuthn\PublicKeyCredentialCreationOptions( new \WebAuthn\RpEntity($this->rpName, $this->rpId), new \WebAuthn\UserEntity( $user->user_login, base64_encode(random_bytes(16)), $user->display_name ?: $user->user_login ), random_bytes(32), [ new \WebAuthn\PublicKeyCredentialParameters(\Cose\Algorithms::COSE_ALGORITHM_ES256), new \WebAuthn\PublicKeyCredentialParameters(\Cose\Algorithms::COSE_ALGORITHM_RS256), ] ); $_SESSION['wp_passkeys_reg_challenge'] = base64_encode($webauthnOptions->getChallenge()); $serializedOptions = \WebAuthn\Serializer\PublicKeyCredentialCreationOptionsSerializer::serialize($webauthnOptions); wp_send_json_success($serializedOptions); } // 验证Passkey注册响应并存储凭证 public function verifyRegistrationResponse() { if (!is_user_logged_in() || !isset($_POST['credential'])) { wp_send_json_error('参数错误'); } $userId = get_current_user_id(); $credential = json_decode(stripslashes($_POST['credential']), true); $storedChallenge = base64_decode($_SESSION['wp_passkeys_reg_challenge']); unset($_SESSION['wp_passkeys_reg_challenge']); try { $webauthnManager = new \WebAuthn\WebAuthnManager(); $attestationResponse = \WebAuthn\Serializer\AuthenticatorAttestationResponseSerializer::deserialize($credential['response']); $publicKeyCredential = \WebAuthn\PublicKeyCredential::createFromPublicKeyCredentialCreationOptions( \WebAuthn\Serializer\PublicKeyCredentialCreationOptionsSerializer::deserialize($credential['clientExtensionResults']), $attestationResponse ); $verificationResult = $webauthnManager->verifyPublicKeyCredentialCreationOptions( $publicKeyCredential, $storedChallenge, $this->rpId, $_SERVER['HTTP_ORIGIN'] ); // 存储凭证到数据库 global $wpdb; $wpdb->insert( $wpdb->prefix . 'passkeys', [ 'user_id' => $userId, 'credential_id' => base64_encode($verificationResult->getPublicKeyCredentialId()), 'public_key' => $verificationResult->getPublicKey()->asPem(), 'device_name' => sanitize_text_field($_POST['deviceName'] ?? '未命名设备') ] ); wp_send_json_success('Passkey添加成功'); } catch (\Exception $e) { wp_send_json_error('注册失败: ' . $e->getMessage()); } } // 生成Passkey登录选项(返回给前端) public function generateAuthenticationOptions() { $webauthnOptions = new \WebAuthn\PublicKeyCredentialRequestOptions( random_bytes(32), 60000, $this->rpId ); $_SESSION['wp_passkeys_auth_challenge'] = base64_encode($webauthnOptions->getChallenge()); $serializedOptions = \WebAuthn\Serializer\PublicKeyCredentialRequestOptionsSerializer::serialize($webauthnOptions); wp_send_json_success($serializedOptions); } // 验证Passkey登录响应并登录用户 public function verifyAuthenticationResponse() { if (!isset($_POST['credential'])) { wp_send_json_error('参数错误'); } $credential = json_decode(stripslashes($_POST['credential']), true); $storedChallenge = base64_decode($_SESSION['wp_passkeys_auth_challenge']); unset($_SESSION['wp_passkeys_auth_challenge']); // 根据凭证ID查找用户 global $wpdb; $credentialId = base64_encode(base64_decode($credential['id'], true)); $passkey = $wpdb->get_row($wpdb->prepare( "SELECT p.*, u.user_login FROM {$wpdb->prefix}passkeys p JOIN {$wpdb->users} u ON p.user_id = u.ID WHERE p.credential_id = %s", $credentialId )); if (!$passkey) { wp_send_json_error('Passkey不存在'); } try { $webauthnManager = new \WebAuthn\WebAuthnManager(); $assertionResponse = \WebAuthn\Serializer\AuthenticatorAssertionResponseSerializer::deserialize($credential['response']); $publicKeyCredential = \WebAuthn\PublicKeyCredential::createFromPublicKeyCredentialRequestOptions( \WebAuthn\Serializer\PublicKeyCredentialRequestOptionsSerializer::deserialize($credential['clientExtensionResults']), $assertionResponse ); $publicKey = \WebAuthn\PublicKeyPemLoader::loadPem($passkey->public_key); $webauthnManager->verifyPublicKeyCredentialRequestOptions( $publicKeyCredential, $storedChallenge, $this->rpId, $_SERVER['HTTP_ORIGIN'], $publicKey ); // 登录用户 wp_set_auth_cookie($passkey->user_id); do_action('wp_login', $passkey->user_login, get_user_by('id', $passkey->user_id)); wp_send_json_success('登录成功'); } catch (\Exception $e) { wp_send_json_error('登录失败: ' . $e->getMessage()); } } // 注册AJAX钩子 public function registerAjaxHooks() { // 注册流程(仅登录用户可用) add_action('wp_ajax_wp_passkeys_generate_reg_options', [$this, 'generateRegistrationOptions']); add_action('wp_ajax_wp_passkeys_verify_reg_response', [$this, 'verifyRegistrationResponse']); // 登录流程(所有用户可用) add_action('wp_ajax_nopriv_wp_passkeys_generate_auth_options', [$this, 'generateAuthenticationOptions']); add_action('wp_ajax_wp_passkeys_generate_auth_options', [$this, 'generateAuthenticationOptions']); add_action('wp_ajax_nopriv_wp_passkeys_verify_auth_response', [$this, 'verifyAuthenticationResponse']); add_action('wp_ajax_wp_passkeys_verify_auth_response', [$this, 'verifyAuthenticationResponse']); } } // 初始化核心类 add_action('init', function() { $wpPasskeys = new WP_Passkeys_WebAuthn(); $wpPasskeys->registerAjaxHooks(); });
5. 前端交互脚本(assets/js/passkeys.js)
document.addEventListener('DOMContentLoaded', () => { // 登录页面添加Passkey登录按钮 if (document.querySelector('#loginform')) { const loginBtn = document.createElement('button'); loginBtn.id = 'wp-passkeys-login'; loginBtn.textContent = '使用Passkey登录'; loginBtn.classList.add('button', 'button-primary', 'mt-3'); document.querySelector('#loginform p.submit').after(loginBtn); loginBtn.addEventListener('click', (e) => { e.preventDefault(); fetch(`${wpPasskeys.ajaxUrl}?action=wp_passkeys_generate_auth_options`) .then(res => res.json()) .then(data => { if (!data.success) throw new Error(data.data); return navigator.credentials.get({ publicKey: data.data }); }) .then(credential => { return fetch(wpPasskeys.ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ action: 'wp_passkeys_verify_auth_response', credential: JSON.stringify(credential) }) }); }) .then(res => res.json()) .then(data => { data.success ? window.location.href = `${window.location.origin}/wp-admin/` : alert(data.data); }) .catch(err => alert(`登录失败: ${err.message}`)); }); } // 用户个人中心添加Passkey注册按钮 if (wpPasskeys.currentUserId && document.querySelector('#user-info')) { const regBtn = document.createElement('button'); regBtn.id = 'wp-passkeys-register'; regBtn.textContent = '添加Passkey'; regBtn.classList.add('button', 'mt-3'); document.querySelector('#user-info').after(regBtn); regBtn.addEventListener('click', (e) => { e.preventDefault(); const deviceName = prompt('请输入设备名称:'); if (!deviceName) return; fetch(`${wpPasskeys.ajaxUrl}?action=wp_passkeys_generate_reg_options`) .then(res => res.json()) .then(data => { if (!data.success) throw new Error(data.data); return navigator.credentials.create({ publicKey: data.data }); }) .then(credential => { return fetch(wpPasskeys.ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ action: 'wp_passkeys_verify_reg_response', credential: JSON.stringify(credential), deviceName: deviceName }) }); }) .then(res => res.json()) .then(data => { data.success ? (alert('添加成功'), window.location.reload()) : alert(data.data); }) .catch(err => alert(`注册失败: ${err.message}`)); }); } });
6. 后台管理页面(includes/admin-settings.php)
add_action('admin_menu', 'wp_passkeys_add_admin_menu'); function wp_passkeys_add_admin_menu() { add_users_page( 'Passkeys管理', 'Passkeys', 'manage_options', 'wp-passkeys', 'wp_passkeys_admin_page' ); } function wp_passkeys_admin_page() { global $wpdb; $tableName = $wpdb->prefix . 'passkeys'; // 处理删除操作 if (isset($_GET['delete_id']) && current_user_can('manage_options')) { $wpdb->delete($tableName, ['id' => intval($_GET['delete_id'])]); echo '<div class="updated"><p>Passkey已删除</p></div>'; } $passkeys = $wpdb->get_results("SELECT p.*, u.user_login FROM $tableName p JOIN {$wpdb->users} u ON p.user_id = u.ID ORDER BY p.created_at DESC"); ?> <div class="wrap"> <h1>Passkeys管理</h1> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>ID</th> <th>关联用户</th> <th>设备名称</th> <th>创建时间</th> <th>操作</th> </tr> </thead> <tbody> <?php foreach ($passkeys as $pk): ?> <tr> <td><?php echo $pk->id; ?></td> <td><?php echo $pk->user_login; ?></td> <td><?php echo esc_html($pk->device_name); ?></td> <td><?php echo $pk->created_at; ?></td> <td><a href="?page=wp-passkeys&delete_id=<?php echo $pk->id; ?>" class="delete" onclick="return confirm('确定删除此Passkey?')">删除</a></td> </tr> <?php endforeach; ?> </tbody> </table> </div> <?php }
关键注意事项
- HTTPS强制:Passkeys仅支持HTTPS环境,生产环境必须配置有效SSL证书
- 会话安全:挑战值使用会话存储,验证后立即销毁,避免重放攻击
- 兼容性:仅支持Chrome 67+、Firefox 60+、Safari 16+等现代浏览器
- 错误处理:捕获WebAuthn库抛出的异常,返回用户友好的错误提示
内容的提问来源于stack exchange,提问作者AYNTK
相关产品推荐
相关产品推荐

