在当今网络安全威胁日益严峻的环境下,为WordPress网站添加双因素验证(2FA)已成为保护管理员账户的必要措施。虽然市面上有许多优秀的2FA插件,但通过纯代码实现可以避免插件依赖,减少潜在的安全漏洞,并提高网站性能。本文将详细介绍如何在WordPress中不使用插件,仅通过代码实现双因素验证功能。
准备工作
在开始之前,您需要确保:
- 拥有WordPress网站的管理员权限
- 可以编辑主题的functions.php文件或创建自定义插件
- 了解基本的PHP编程知识
- 准备一个支持TOTP(基于时间的一次性密码)的认证应用,如Google Authenticator或Authy
实现步骤
1. 创建必要的数据库表
我们需要创建一个表来存储用户的2FA密钥:
function create_2fa_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'user_2fa';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
user_id bigint(20) NOT NULL,
secret_key varchar(255) NOT NULL,
recovery_codes text,
is_active tinyint(1) DEFAULT 0,
PRIMARY KEY (id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
register_activation_hook(__FILE__, 'create_2fa_table');
2. 生成并存储2FA密钥
我们需要为每个用户生成一个唯一的密钥:
function generate_2fa_secret($user_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'user_2fa';
require_once(ABSPATH . 'wp-includes/class-phpass.php');
$tfa = new GoogleAuthenticator();
$secret = $tfa->createSecret();
$recovery_codes = generate_recovery_codes();
$wpdb->insert(
$table_name,
array(
'user_id' => $user_id,
'secret_key' => $secret,
'recovery_codes' => json_encode($recovery_codes),
'is_active' => 0
),
array('%d', '%s', '%s', '%d')
);
return array('secret' => $secret, 'recovery_codes' => $recovery_codes);
}
3. 创建Google Authenticator类
我们需要一个类来处理TOTP验证:
class GoogleAuthenticator {
const keyRegeneration = 30;
const otpLength = 6;
private static $validChars = array(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'2', '3', '4', '5', '6', '7', '=',
);
public function createSecret($length = 16) {
$secret = '';
$rnd = false;
if (function_exists('random_bytes')) {
$rnd = random_bytes($length);
} elseif (function_exists('openssl_random_pseudo_bytes')) {
$rnd = openssl_random_pseudo_bytes($length, $crypto_strong);
if ($crypto_strong !== true) {
$rnd = false;
}
}
if ($rnd !== false) {
for ($i = 0; $i < $length; $i++) {
$secret .= self::$validChars[ord($rnd[$i]) & 31];
}
} else {
throw new Exception('No proper random source available');
}
return $secret;
}
public function getCode($secret, $timeSlice = null) {
if ($timeSlice === null) {
$timeSlice = floor(time() / self::keyRegeneration);
}
$secretkey = $this->base32Decode($secret);
$time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice);
$hm = hash_hmac('SHA1', $time, $secretkey, true);
$offset = ord(substr($hm, -1)) & 0x0F;
$hashpart = substr($hm, $offset, 4);
$value = unpack('N', $hashpart);
$value = $value[1];
$value = $value & 0x7FFFFFFF;
$modulo = pow(10, self::otpLength);
return str_pad($value % $modulo, self::otpLength, '0', STR_PAD_LEFT);
}
// 其他必要的方法...
}
4. 添加2FA设置界面
在用户个人资料页面添加2FA设置选项:
function add_2fa_profile_fields($user) {
if (!current_user_can('edit_user', $user->ID)) {
return;
}
global $wpdb;
$table_name = $wpdb->prefix . 'user_2fa';
$user_2fa = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE user_id = %d", $user->ID));
if (!$user_2fa) {
$secret_data = generate_2fa_secret($user->ID);
$user_2fa = (object)array(
'user_id' => $user->ID,
'secret_key' => $secret_data['secret'],
'recovery_codes' => json_encode($secret_data['recovery_codes']),
'is_active' => 0
);
} else {
$secret_data = array(
'secret' => $user_2fa->secret_key,
'recovery_codes' => json_decode($user_2fa->recovery_codes, true)
);
}
?>
<h3>双因素验证设置</h3>
<table class="form-table">
<tr>
<th><label for="2fa_status">启用双因素验证</label></th>
<td>
<input type="checkbox" name="2fa_status" id="2fa_status" value="1" <?php checked($user_2fa->is_active, 1); ?>>
<span class="description">启用后,登录时需要输入验证码</span>
</td>
</tr>
<?php if (!$user_2fa->is_active) : ?>
<tr>
<th><label>设置双因素验证</label></th>
<td>
<p>请使用认证应用扫描下方二维码或手动输入密钥:</p>
<img src="<?php echo get_2fa_qrcode_url($user, $secret_data['secret']); ?>" alt="QR Code">
<p>密钥: <code><?php echo $secret_data['secret']; ?></code></p>
<p>一次性备用代码:</p>
<ul>
<?php foreach ($secret_data['recovery_codes'] as $code) : ?>
<li><code><?php echo $code; ?></code></li>
<?php endforeach; ?>
</ul>
<p class="description">请妥善保存这些备用代码,以防无法访问认证应用</p>
</td>
</tr>
<?php endif; ?>
</table>
<?php
}
add_action('show_user_profile', 'add_2fa_profile_fields');
add_action('edit_user_profile', 'add_2fa_profile_fields');
5. 保存2FA设置
处理用户提交的2FA设置:
function save_2fa_profile_fields($user_id) {
if (!current_user_can('edit_user', $user_id)) {
return;
}
global $wpdb;
$table_name = $wpdb->prefix . 'user_2fa';
$is_active = isset($_POST['2fa_status']) ? 1 : 0;
$wpdb->update(
$table_name,
array('is_active' => $is_active),
array('user_id' => $user_id),
array('%d'),
array('%d')
);
}
add_action('personal_options_update', 'save_2fa_profile_fields');
add_action('edit_user_profile_update', 'save_2fa_profile_fields');
6. 修改登录流程
在标准WordPress登录流程中添加2FA验证步骤:
function custom_authenticate($user, $username, $password) {
if (is_wp_error($user)) {
return $user;
}
global $wpdb;
$table_name = $wpdb->prefix . 'user_2fa';
$user_2fa = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE user_id = %d", $user->ID));
if ($user_2fa && $user_2fa->is_active) {
// 存储用户ID在会话中,以便在验证页面使用
$_SESSION['2fa_user_id'] = $user->ID;
$_SESSION['2fa_remember'] = !empty($_POST['rememberme']);
// 重定向到2FA验证页面
wp_redirect(home_url('/wp-2fa-verify'));
exit;
}
return $user;
}
add_filter('authenticate', 'custom_authenticate', 30, 3);
7. 创建2FA验证页面
添加一个自定义页面来处理2FA验证:
function add_2fa_verify_page() {
if (!isset($_SESSION['2fa_user_id'])) {
wp_redirect(wp_login_url());
exit;
}
$user_id = $_SESSION['2fa_user_id'];
$remember = $_SESSION['2fa_remember'];
if (isset($_POST['2fa_code'])) {
global $wpdb;
$table_name = $wpdb->prefix . 'user_2fa';
$user_2fa = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE user_id = %d", $user_id));
$tfa = new GoogleAuthenticator();
$code = sanitize_text_field($_POST['2fa_code']);
// 检查验证码或备用代码
if ($tfa->verifyCode($user_2fa->secret_key, $code, 2) ||
in_array($code, json_decode($user_2fa->recovery_codes, true))) {
// 如果是备用代码,则从列表中移除
if (in_array($code, json_decode($user_2fa->recovery_codes, true))) {
$recovery_codes = array_diff(json_decode($user_2fa->recovery_codes, true), array($code));
$wpdb->update(
$table_name,
array('recovery_codes' => json_encode($recovery_codes)),
array('user_id' => $user_id),
array('%s'),
array('%d')
);
}
// 清除会话并登录用户
unset($_SESSION['2fa_user_id']);
unset($_SESSION['2fa_remember']);
wp_set_auth_cookie($user_id, $remember);
wp_redirect(admin_url());
exit;
} else {
$error = '验证码无效,请重试';
}
}
// 显示验证页面
?>
<!DOCTYPE html>
<html>
<head>
<title>双因素验证</title>
<?php wp_head(); ?>
</head>
<body>
<div class="login">
<h1>双因素验证</h1>
<?php if (isset($error)) : ?>
<div class="error"><p><?php echo $error; ?></p></div>
<?php endif; ?>
<form method="post">
<p>请输入您的验证码或备用代码:</p>
<p>
<input type="text" name="2fa_code" id="2fa_code" class="input" value="" size="20" autocapitalize="off" autocomplete="off" />
</p>
<p class="submit">
<input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large" value="验证" />
</p>
</form>
</div>
</body>
</html>
<?php
exit;
}
add_action('init', function() {
if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/wp-2fa-verify') !== false) {
add_2fa_verify_page();
}
});
8. 添加重写规则
确保我们的自定义验证URL可以正常工作:
function add_2fa_rewrite_rule() {
add_rewrite_rule('^wp-2fa-verify/?$', 'index.php?2fa_verify=1', 'top');
}
add_action('init', 'add_2fa_rewrite_rule');
function add_2fa_query_var($vars) {
$vars[] = '2fa_verify';
return $vars;
}
add_filter('query_vars', 'add_2fa_query_var');
安全注意事项
- 密钥存储安全:确保数据库中的密钥被妥善保护,考虑加密存储
- 会话安全:使用安全的会话管理,防止会话劫持
- 暴力破解防护:限制验证码尝试次数,防止暴力破解
- 备用代码安全:确保备用代码一次性使用后失效
- SSL加密:整个流程必须通过HTTPS进行,防止中间人攻击
扩展功能
您可以根据需要扩展此基础实现:
- 邮件/SMS验证:添加通过邮件或短信发送验证码的选项
- 多设备支持:允许用户注册多个认证设备
- 紧急访问:设置紧急联系人可以在需要时恢复账户
- 活动日志:记录所有2FA验证尝试,便于安全审计
结论
通过上述代码实现,您可以在不使用任何插件的情况下为WordPress网站添加双因素验证功能。这种方法减少了对外部插件的依赖,提高了网站的安全性和性能。虽然初始设置比使用插件更复杂,但它提供了更大的灵活性和控制权,适合对安全性有更高要求的WordPress网站。
记得在实施前进行全面测试,并确保备份您的网站,以防需要回滚更改。