You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 03:47:22