在當(dāng)今網(wǎng)絡(luò)安全威脅日益嚴(yán)峻的環(huán)境下,為WordPress網(wǎng)站添加雙因素驗(yàn)證(2FA)已成為保護(hù)管理員賬戶(hù)的必要措施。雖然市面上有許多優(yōu)秀的2FA插件,但通過(guò)純代碼實(shí)現(xiàn)可以避免插件依賴(lài),減少潛在的安全漏洞,并提高網(wǎng)站性能。本文將詳細(xì)介紹如何在WordPress中不使用插件,僅通過(guò)代碼實(shí)現(xiàn)雙因素驗(yàn)證功能。
準(zhǔn)備工作
在開(kāi)始之前,您需要確保:
- 擁有WordPress網(wǎng)站的管理員權(quán)限
- 可以編輯主題的functions.php文件或創(chuàng)建自定義插件
- 了解基本的PHP編程知識(shí)
- 準(zhǔn)備一個(gè)支持TOTP(基于時(shí)間的一次性密碼)的認(rèn)證應(yīng)用,如Google Authenticator或Authy
實(shí)現(xiàn)步驟
1. 創(chuàng)建必要的數(shù)據(jù)庫(kù)表
我們需要?jiǎng)?chuàng)建一個(gè)表來(lái)存儲(chǔ)用戶(hù)的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. 生成并存儲(chǔ)2FA密鑰
我們需要為每個(gè)用戶(hù)生成一個(gè)唯一的密鑰:
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. 創(chuàng)建Google Authenticator類(lèi)
我們需要一個(gè)類(lèi)來(lái)處理TOTP驗(yàn)證:
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設(shè)置界面
在用戶(hù)個(gè)人資料頁(yè)面添加2FA設(shè)置選項(xiàng):
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>雙因素驗(yàn)證設(shè)置</h3>
<table class="form-table">
<tr>
<th><label for="2fa_status">啟用雙因素驗(yàn)證</label></th>
<td>
<input type="checkbox" name="2fa_status" id="2fa_status" value="1" <?php checked($user_2fa->is_active, 1); ?>>
<span class="description">啟用后,登錄時(shí)需要輸入驗(yàn)證碼</span>
</td>
</tr>
<?php if (!$user_2fa->is_active) : ?>
<tr>
<th><label>設(shè)置雙因素驗(yàn)證</label></th>
<td>
<p>請(qǐng)使用認(rèn)證應(yīng)用掃描下方二維碼或手動(dòng)輸入密鑰:</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">請(qǐng)妥善保存這些備用代碼,以防無(wú)法訪(fǎng)問(wèn)認(rèn)證應(yīng)用</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設(shè)置
處理用戶(hù)提交的2FA設(shè)置:
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. 修改登錄流程
在標(biāo)準(zhǔn)WordPress登錄流程中添加2FA驗(yàn)證步驟:
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) {
// 存儲(chǔ)用戶(hù)ID在會(huì)話(huà)中,以便在驗(yàn)證頁(yè)面使用
$_SESSION['2fa_user_id'] = $user->ID;
$_SESSION['2fa_remember'] = !empty($_POST['rememberme']);
// 重定向到2FA驗(yàn)證頁(yè)面
wp_redirect(home_url('/wp-2fa-verify'));
exit;
}
return $user;
}
add_filter('authenticate', 'custom_authenticate', 30, 3);
7. 創(chuàng)建2FA驗(yàn)證頁(yè)面
添加一個(gè)自定義頁(yè)面來(lái)處理2FA驗(yàn)證:
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']);
// 檢查驗(yàn)證碼或備用代碼
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')
);
}
// 清除會(huì)話(huà)并登錄用戶(hù)
unset($_SESSION['2fa_user_id']);
unset($_SESSION['2fa_remember']);
wp_set_auth_cookie($user_id, $remember);
wp_redirect(admin_url());
exit;
} else {
$error = '驗(yàn)證碼無(wú)效,請(qǐng)重試';
}
}
// 顯示驗(yàn)證頁(yè)面
?>
<!DOCTYPE html>
<html>
<head>
<title>雙因素驗(yàn)證</title>
<?php wp_head(); ?>
</head>
<body>
<div class="login">
<h1>雙因素驗(yàn)證</h1>
<?php if (isset($error)) : ?>
<div class="error"><p><?php echo $error; ?></p></div>
<?php endif; ?>
<form method="post">
<p>請(qǐng)輸入您的驗(yàn)證碼或備用代碼:</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="驗(yàn)證" />
</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. 添加重寫(xiě)規(guī)則
確保我們的自定義驗(yàn)證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');
安全注意事項(xiàng)
- 密鑰存儲(chǔ)安全:確保數(shù)據(jù)庫(kù)中的密鑰被妥善保護(hù),考慮加密存儲(chǔ)
- 會(huì)話(huà)安全:使用安全的會(huì)話(huà)管理,防止會(huì)話(huà)劫持
- 暴力破解防護(hù):限制驗(yàn)證碼嘗試次數(shù),防止暴力破解
- 備用代碼安全:確保備用代碼一次性使用后失效
- SSL加密:整個(gè)流程必須通過(guò)HTTPS進(jìn)行,防止中間人攻擊
擴(kuò)展功能
您可以根據(jù)需要擴(kuò)展此基礎(chǔ)實(shí)現(xiàn):
- 郵件/SMS驗(yàn)證:添加通過(guò)郵件或短信發(fā)送驗(yàn)證碼的選項(xiàng)
- 多設(shè)備支持:允許用戶(hù)注冊(cè)多個(gè)認(rèn)證設(shè)備
- 緊急訪(fǎng)問(wèn):設(shè)置緊急聯(lián)系人可以在需要時(shí)恢復(fù)賬戶(hù)
- 活動(dòng)日志:記錄所有2FA驗(yàn)證嘗試,便于安全審計(jì)
結(jié)論
通過(guò)上述代碼實(shí)現(xiàn),您可以在不使用任何插件的情況下為WordPress網(wǎng)站添加雙因素驗(yàn)證功能。這種方法減少了對(duì)外部插件的依賴(lài),提高了網(wǎng)站的安全性和性能。雖然初始設(shè)置比使用插件更復(fù)雜,但它提供了更大的靈活性和控制權(quán),適合對(duì)安全性有更高要求的WordPress網(wǎng)站。
記得在實(shí)施前進(jìn)行全面測(cè)試,并確保備份您的網(wǎng)站,以防需要回滾更改。