PHP 敏感操作二次验证

wen PHP项目 2

本文目录导读:

PHP 敏感操作二次验证

  1. 基于 TOTP(时间一次性密码)的二次验证
  2. 短信验证码方案
  3. 邮件验证码方案
  4. 综合二次验证类
  5. 最佳实践建议
  6. 注意事项

在 PHP 中实现敏感操作的二次验证(Two-Factor Authentication, 2FA)是提升系统安全性的重要措施,以下是几种常见的实现方案及代码示例:

基于 TOTP(时间一次性密码)的二次验证

使用 otp-php 库

<?php
// 安装: composer require spomky-labs/otphp
use OTPHP\TOTP;
class TwoFactorAuth {
    private $secret;
    public function __construct($userSecret = null) {
        $this->secret = $userSecret ?? $this->generateSecret();
    }
    // 生成密钥
    public function generateSecret() {
        return TOTP::generate()->getSecret();
    }
    // 获取二维码信息(用于 Google Authenticator)
    public function getQRCodeData($username) {
        $totp = TOTP::create($this->secret);
        $totp->setLabel($username);
        return $totp->getProvisioningUri();
    }
    // 验证 TOTP
    public function verify($code) {
        $totp = TOTP::create($this->secret);
        return $totp->verify($code, null, 1); // 允许1次偏差
    }
    // 获取当前 TOTP 码(测试用)
    public function getCurrentCode() {
        $totp = TOTP::create($this->secret);
        return $totp->now();
    }
}
// 使用示例
$tfa = new TwoFactorAuth();
$userSecret = $tfa->generateSecret(); // 保存到用户表
// 验证流程
if ($tfa->verify($_POST['code'])) {
    // 验证通过,执行敏感操作
} else {
    // 验证失败
}
?>

短信验证码方案

<?php
class SMSVerification {
    private $redis;
    private $expire = 300; // 5分钟
    public function __construct() {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    // 发送验证码
    public function sendCode($phone) {
        $code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
        // 存储验证码
        $this->redis->setex(
            "verify:{$phone}",
            $this->expire,
            json_encode([
                'code' => $code,
                'time' => time(),
                'attempts' => 0
            ])
        );
        // 发送短信(接入短信服务商)
        $this->sendSMS($phone, "您的验证码是: {$code}");
        return true;
    }
    // 验证码验证
    public function verifyCode($phone, $code) {
        $data = json_decode($this->redis->get("verify:{$phone}"), true);
        if (!$data) {
            return ['success' => false, 'message' => '验证码已过期'];
        }
        // 检查尝试次数
        if ($data['attempts'] >= 5) {
            $this->redis->del("verify:{$phone}");
            return ['success' => false, 'message' => '尝试次数过多'];
        }
        if ($data['code'] !== $code) {
            $data['attempts']++;
            $this->redis->setex(
                "verify:{$phone}",
                $this->expire - (time() - $data['time']),
                json_encode($data)
            );
            return ['success' => false, 'message' => '验证码错误'];
        }
        // 验证成功,删除验证码
        $this->redis->del("verify:{$phone}");
        return ['success' => true, 'message' => '验证成功'];
    }
    private function sendSMS($phone, $message) {
        // 接入短信服务商,如阿里云、腾讯云等
        // 这里只是示例
    }
}
?>

邮件验证码方案

<?php
use PHPMailer\PHPMailer\PHPMailer;
class EmailVerification {
    private $db;
    public function __construct() {
        $this->db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
    }
    // 生成并发送验证码
    public function sendVerification($userId, $email) {
        $code = bin2hex(random_bytes(16));
        $expire = date('Y-m-d H:i:s', strtotime('+10 minutes'));
        // 保存验证码
        $stmt = $this->db->prepare(
            "INSERT INTO verification_codes (user_id, code, type, expires_at) 
             VALUES (?, ?, 'email', ?)"
        );
        $stmt->execute([$userId, $code, $expire]);
        // 发送邮件
        $mail = new PHPMailer();
        // 配置邮件服务器...
        $mail->addAddress($email);
        $mail->Subject = '敏感操作验证';
        $mail->Body = "您的验证码: {$code}\n有效期10分钟";
        return $mail->send();
    }
    // 验证
    public function verifyCode($userId, $code) {
        $stmt = $this->db->prepare(
            "SELECT * FROM verification_codes 
             WHERE user_id = ? AND code = ? AND type = 'email' 
             AND expires_at > NOW() AND used = 0"
        );
        $stmt->execute([$userId, $code]);
        $result = $stmt->fetch();
        if ($result) {
            // 标记已使用
            $update = $this->db->prepare("UPDATE verification_codes SET used = 1 WHERE id = ?");
            $update->execute([$result['id']]);
            return true;
        }
        return false;
    }
}
?>

综合二次验证类

<?php
class SecondFactorAuth {
    private $user;
    private $action;
    private $sessionKey;
    public function __construct($user, $action) {
        $this->user = $user;
        $this->action = $action;
        $this->sessionKey = "2fa_{$action}_{$user['id']}";
    }
    // 开始验证流程
    public function beginVerification() {
        $method = $this->determineMethod();
        $_SESSION[$this->sessionKey] = [
            'method' => $method,
            'started' => time(),
            'expire' => time() + 300, // 5分钟
            'verified' => false
        ];
        // 发送验证码
        return $this->sendCode($method);
    }
    // 决定验证方式
    private function determineMethod() {
        // 根据用户设置选择验证方式
        if ($this->user['tfa_enabled']) {
            return 'totp'; // 可选: sms, email, totp
        }
        return 'sms';
    }
    // 发送验证码
    private function sendCode($method) {
        switch($method) {
            case 'totp':
                return ['type' => 'totp', 'message' => '请输入认证器APP中的动态密码'];
            case 'sms':
                $sms = new SMSVerification();
                $sms->sendCode($this->user['phone']);
                return ['type' => 'sms', 'message' => '已向您的手机发送验证码'];
            case 'email':
                $email = new EmailVerification();
                $email->sendVerification($this->user['id'], $this->user['email']);
                return ['type' => 'email', 'message' => '已向您的邮箱发送验证码'];
        }
    }
    // 验证码验证
    public function verify($code) {
        if (!isset($_SESSION[$this->sessionKey])) {
            return ['success' => false, 'message' => '验证会话已过期'];
        }
        $session = $_SESSION[$this->sessionKey];
        if ($session['verified']) {
            return ['success' => true, 'message' => '已验证'];
        }
        if (time() > $session['expire']) {
            unset($_SESSION[$this->sessionKey]);
            return ['success' => false, 'message' => '验证码已过期'];
        }
        // 验证逻辑
        $result = $this->verifyCode($session['method'], $code);
        if ($result['success']) {
            $_SESSION[$this->sessionKey]['verified'] = true;
        }
        return $result;
    }
    private function verifyCode($method, $code) {
        switch($method) {
            case 'totp':
                $tfa = new TwoFactorAuth($this->user['secret']);
                return ['success' => $tfa->verify($code), 'message' => '验证成功'];
            case 'sms':
                $sms = new SMSVerification();
                return $sms->verifyCode($this->user['phone'], $code);
            case 'email':
                $email = new EmailVerification();
                return ['success' => $email->verifyCode($this->user['id'], $code), 'message' => '验证成功'];
        }
    }
    // 检查是否已验证
    public function isVerified() {
        return isset($_SESSION[$this->sessionKey]) && 
               $_SESSION[$this->sessionKey]['verified'];
    }
    // 清除验证
    public function clearVerification() {
        unset($_SESSION[$this->sessionKey]);
    }
}
// 使用示例
class BankApi {
    public function transferMoney($amount, $toAccount) {
        $user = getCurrentUser();
        // 敏感操作 - 转账
        $tfa = new SecondFactorAuth($user, 'transfer');
        if (!$tfa->isVerified()) {
            // 如果未通过验证
            throw new Exception('需要二次验证');
        }
        // 执行转账
        $this->doTransfer($amount, $toAccount);
        // 清除验证状态
        $tfa->clearVerification();
    }
}
?>

最佳实践建议

安全配置示例

<?php
class SecurityConfig {
    // 验证码长度
    const CODE_LENGTH = 6;
    // 有效时间(秒)
    const EXPIRE_TIME = 300;
    // 最大尝试次数
    const MAX_ATTEMPTS = 5;
    // 锁定时间(秒)
    const LOCK_TIME = 600;
    // 是否强制验证高风险操作
    const HIGH_RISK_ACTIONS = [
        'transfer', 'delete', 'change_password', 'update_phone'
    ];
    // 验证中间件
    public static function verifyMiddleware($action) {
        if (in_array($action, self::HIGH_RISK_ACTIONS)) {
            // 强制二次验证
            return true;
        }
        // 其他操作可配置
        return config('settings.2fa_enabled');
    }
}
// 异常处理器
class VerificationException extends Exception {
    public function render($request) {
        return response()->json([
            'success' => false,
            'message' => $this->getMessage(),
            'need_verification' => true
        ], 403);
    }
}
?>

注意事项

  1. 设置验证频率限制:防止暴力破解
  2. 验证码一次性使用:防止重放攻击
  3. 记录验证日志:便于审计
  4. 定期轮换密钥:适用于 TOTP
  5. 考虑验证失效机制:保证安全性
  6. 前端配合:实现完整的验证流程

选择哪种验证方式取决于您的业务需求、用户群体和安全性要求,建议根据实际场景选择合适的方式,必要时可以组合使用。

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