PHP项目多因素认证MFA

wen PHP项目 4

本文目录导读:

PHP项目多因素认证MFA

  1. 📋 MFA核心概念
  2. 🔧 主流实现方案
  3. 🔐 完整认证流程实现
  4. 📊 数据库设计
  5. 🛡️ 安全最佳实践
  6. ⚠️ 注意事项
  7. 📦 推荐PHP库

我来为您详细介绍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);
    }
}

⚠️ 注意事项

  1. HTTPS必需:所有MFA通信必须加密
  2. 时间同步:TOTP需要服务器时间准确(使用NTP)
  3. 用户体验:提供"记住此设备"选项
  4. 错误处理:不明确指出验证失败的环节
  5. 日志记录:记录所有MFA尝试(成功和失败)

📦 推荐PHP库

  • spomky-labs/otphp: TOTP/HOTP实现
  • sonata-project/GoogleAuthenticator: Google Authenticator兼容
  • pragmarx/google2fa: 另一个流行的TOTP库
  • phpgangsta/googleAuthenticator: 简单易用的TOTP实现

这个方案可以根据您的具体需求进行调整和扩展,需要更详细的某个部分实现吗?

抱歉,评论功能暂时关闭!