本文目录导读:

我来为您详细介绍PHP项目实现多因素认证(MFA)的方案。
📋 MFA核心概念
多因素认证需要至少两种不同类型的验证:
- 知识因素:密码、PIN码
- 持有因素:手机、硬件令牌
- 固有因素:指纹、面部识别
🔧 主流实现方案
TOTP(基于时间的一次性密码)
最流行的方式,如Google Authenticator、Authy等。
使用PHP库实现
// 安装:composer require spomky-labs/otphp
use OTPHP\TOTP;
class MFAManager {
private $secret;
public function generateSecret(): string {
$totp = TOTP::create();
$this->secret = $totp->getSecret();
return $this->secret;
}
public function getProvisioningUri(string $username): string {
$totp = TOTP::create($this->secret);
return $totp->getProvisioningUri(
$username,
'YourAppName'
);
}
public function verifyCode(string $code, string $secret): bool {
$totp = TOTP::create($secret);
return $totp->verify($code);
}
}
// 使用示例
$mfa = new MFAManager();
$secret = $mfa->generateSecret();
// 保存secret到数据库
// 生成二维码给用户扫描
$uri = $mfa->getProvisioningUri('user@example.com');
短信验证码
class SMSAuthenticator {
private $twilio;
public function __construct() {
$this->twilio = new Twilio\Rest\Client(
getenv('TWILIO_SID'),
getenv('TWILIO_TOKEN')
);
}
public function sendCode(string $phone): string {
$code = str_pad(rand(0, 999999), 6, '0', STR_PAD_LEFT);
// 保存到session或数据库
$_SESSION['mfa_code'] = [
'code' => password_hash($code, PASSWORD_DEFAULT),
'expires' => time() + 300, // 5分钟有效
'phone' => $phone
];
// 发送短信
$this->twilio->messages->create($phone, [
'from' => getenv('TWILIO_PHONE'),
'body' => "您的验证码是: $code,5分钟内有效"
]);
return $code;
}
public function verifyCode(string $code): bool {
if (!isset($_SESSION['mfa_code'])) {
return false;
}
$stored = $_SESSION['mfa_code'];
if (time() > $stored['expires']) {
unset($_SESSION['mfa_code']);
return false;
}
if (password_verify($code, $stored['code'])) {
unset($_SESSION['mfa_code']);
return true;
}
return false;
}
}
邮箱验证码
class EmailAuthenticator {
public function sendCode(string $email): string {
$code = bin2hex(random_bytes(3)); // 6位十六进制
$expires = time() + 600; // 10分钟
// 存储验证码
$this->storeVerificationCode($email, $code, $expires);
// 发送邮件
$subject = "您的MFA验证码";
$body = "您的验证码是: $code\n有效期10分钟";
mail($email, $subject, $body);
return $code;
}
private function storeVerificationCode(string $email, string $code, int $expires) {
// 存储到数据库
$stmt = $this->db->prepare(
"INSERT INTO mfa_codes (email, code, expires_at) VALUES (?, ?, ?)"
);
$stmt->execute([$email, $code, date('Y-m-d H:i:s', $expires)]);
}
public function verifyCode(string $email, string $code): bool {
$stmt = $this->db->prepare(
"SELECT * FROM mfa_codes WHERE email = ? AND code = ? AND expires_at > NOW()"
);
$stmt->execute([$email, $code]);
return $stmt->fetch() !== false;
}
}
🔐 完整认证流程实现
class MFAAuthentication {
private $db;
private $session;
public function login(string $username, string $password): array {
// 1. 验证用户名密码
if (!$this->verifyPassword($username, $password)) {
return ['success' => false, 'error' => '密码错误'];
}
// 2. 检查用户是否启用MFA
$user = $this->getUser($username);
if ($user['mfa_enabled']) {
// 3. 生成临时token,进入MFA验证阶段
$tempToken = bin2hex(random_bytes(32));
$this->storeTempToken($tempToken, $user['id']);
return [
'success' => true,
'mfa_required' => true,
'temp_token' => $tempToken,
'mfa_type' => $user['mfa_type'] // 'totp', 'sms', 'email'
];
}
// 4. 无MFA,直接登录
$this->createSession($user);
return ['success' => true, 'mfa_required' => false];
}
public function verifyMFA(string $tempToken, string $code): bool {
// 1. 验证临时token
$userId = $this->validateTempToken($tempToken);
if (!$userId) {
return false;
}
// 2. 获取用户MFA配置
$user = $this->getUserById($userId);
// 3. 验证MFA代码
$valid = match ($user['mfa_type']) {
'totp' => $this->verifyTOTP($code, $user['mfa_secret']),
'sms' => $this->verifySMSCode($userId, $code),
'email' => $this->verifyEmailCode($user['email'], $code),
default => false
};
if ($valid) {
// 4. 清除临时token,创建登录session
$this->deleteTempToken($tempToken);
$this->createSession($user);
return true;
}
return false;
}
}
📊 数据库设计
-- MFA配置表
CREATE TABLE mfa_configs (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
mfa_enabled BOOLEAN DEFAULT FALSE,
mfa_type ENUM('totp', 'sms', 'email') DEFAULT 'totp',
mfa_secret VARCHAR(64),
backup_codes JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 验证码表
CREATE TABLE mfa_codes (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
code VARCHAR(64) NOT NULL,
type ENUM('sms', 'email') NOT NULL,
expires_at DATETIME NOT NULL,
used BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
INDEX idx_user_code (user_id, code),
INDEX idx_expires (expires_at)
);
-- 临时令牌表
CREATE TABLE temp_tokens (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
token VARCHAR(64) NOT NULL UNIQUE,
expires_at DATETIME NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
INDEX idx_token (token)
);
🛡️ 安全最佳实践
备份码机制
class BackupCodes {
public function generateBackupCodes(int $count = 10): array {
$codes = [];
for ($i = 0; $i < $count; $i++) {
$codes[] = strtoupper(
implode('-', [
bin2hex(random_bytes(2)),
bin2hex(random_bytes(2)),
bin2hex(random_bytes(2))
])
);
}
return $codes;
}
public function verifyBackupCode(string $code): bool {
$storedCodes = json_decode($user['backup_codes'], true);
$hashedCodes = array_map(function($c) {
return password_hash($c, PASSWORD_DEFAULT);
}, $storedCodes);
foreach ($hashedCodes as $index => $hashedCode) {
if (password_verify($code, $hashedCode)) {
// 移除已使用的备份码
unset($storedCodes[$index]);
$this->updateBackupCodes($storedCodes);
return true;
}
}
return false;
}
}
速率限制
class RateLimiter {
public function checkRateLimit(string $identifier, int $maxAttempts = 5, int $period = 300): bool {
$key = "rate_limit:{$identifier}";
$attempts = $this->redis->get($key) ?: 0;
if ($attempts >= $maxAttempts) {
return false; // 超出限制
}
$this->redis->incr($key);
$this->redis->expire($key, $period);
return true;
}
}
用户配置界面
class MFAEnrollment {
public function enableMFA(int $userId, string $type, string $phone = null): array {
$secret = '';
$backupCodes = [];
if ($type === 'totp') {
// 生成TOTP密钥
$secret = $this->generateTOTPSecret();
$backupCodes = $this->generateBackupCodes();
}
// 保存配置
$this->db->saveMFAConfig($userId, $type, $secret, $backupCodes);
return [
'secret' => $secret,
'qr_code' => $this->generateQRCode($secret, $userId),
'backup_codes' => $backupCodes
];
}
private function generateQRCode(string $secret, int $userId): string {
$issuer = 'YourApp';
$user = $this->db->getUser($userId);
$url = sprintf(
'otpauth://totp/%s:%s?secret=%s&issuer=%s',
$issuer,
$user['email'],
$secret,
$issuer
);
// 生成二维码图片
return (new QRCode())->render($url);
}
}
⚠️ 注意事项
- HTTPS必需:所有MFA通信必须加密
- 时间同步:TOTP需要服务器时间准确(使用NTP)
- 用户体验:提供"记住此设备"选项
- 错误处理:不明确指出验证失败的环节
- 日志记录:记录所有MFA尝试(成功和失败)
📦 推荐PHP库
- spomky-labs/otphp: TOTP/HOTP实现
- sonata-project/GoogleAuthenticator: Google Authenticator兼容
- pragmarx/google2fa: 另一个流行的TOTP库
- phpgangsta/googleAuthenticator: 简单易用的TOTP实现
这个方案可以根据您的具体需求进行调整和扩展,需要更详细的某个部分实现吗?