PHP项目敏感操作二次验证

wen PHP项目 2

本文目录导读:

PHP项目敏感操作二次验证

  1. 验证码二次验证
  2. 密码二次验证
  3. 两步验证(2FA)
  4. Token验证机制
  5. 完整的安全建议
  6. 总结建议

在PHP项目中实现敏感操作二次验证,通常用于修改密码、删除数据、转账等关键操作的二次确认,下面我将为你介绍几种常见的实现方案。

验证码二次验证

实现方式

使用图片验证码或短信验证码进行二次确认。

<?php
// 生成验证码并存入session
session_start();
$code = generateRandomCode(6);
$_SESSION['verify_code'] = $code;
$_SESSION['verify_expire'] = time() + 300; // 5分钟过期
?>
<!-- 前端HTML -->
<form method="POST" action="process.php">
    <label>请输入验证码验证身份</label>
    <input type="text" name="verify_code" required>
    <input type="hidden" name="action" value="delete_account">
    <button type="submit">确认删除账号</button>
</form>
<?php
// 后端验证
if ($_POST['action'] === 'delete_account') {
    // 验证是否存在验证码和重要操作标识
    session_start();
    if (!isset($_SESSION['verify_code']) || 
        $_SESSION['verify_expire'] < time()) {
        die('验证已过期,请重新验证');
    }
    if ($_POST['verify_code'] !== $_SESSION['verify_code']) {
        die('验证码错误');
    }
    // 清除已使用的验证码
    unset($_SESSION['verify_code']);
    unset($_SESSION['verify_expire']);
    // 执行敏感操作
    deleteAccount($userId);
}
?>

密码二次验证

要求用户再次输入密码来确认身份。

<?php
class SecurityVerification {
    private $db;
    private $session;
    public function __construct($db, $session) {
        $this->db = $db;
        $this->session = $session;
    }
    /**
     * 密码二次验证
     */
    public function passwordVerify($password) {
        // 从session获取当前用户ID
        $userId = $this->session->get('user_id');
        // 从数据库获取用户密码哈希
        $stmt = $this->db->prepare("SELECT password_hash FROM users WHERE id = ?");
        $stmt->execute([$userId]);
        $user = $stmt->fetch();
        if (!$user) {
            return false;
        }
        // 验证密码
        return password_verify($password, $user['password_hash']);
    }
    /**
     * 执行敏感操作(带二次验证)
     */
    public function sensitiveOperation($operation, $params, $password) {
        // 验证密码
        if (!$this->passwordVerify($password)) {
            throw new Exception('密码验证失败');
        }
        // 记录操作日志
        $this->logOperation($operation, $params);
        // 执行具体操作
        switch ($operation) {
            case 'change_email':
                return $this->changeEmail($params['new_email']);
            case 'delete_account':
                return $this->deleteAccount();
            default:
                throw new Exception('未知操作');
        }
    }
}
?>

两步验证(2FA)

使用时间基的一次性密码(TOTP)

<?php
require_once 'vendor/autoload.php'; // 引入 OTP 库
use OTPHP\TOTP;
class TwoFactorAuth {
    private $db;
    private $totp;
    public function __construct($db) {
        $this->db = $db;
        $this->totp = TOTP::create();
    }
    /**
     * 生成并保存2FA密钥
     */
    public function generateSecret($userId) {
        $secret = $this->totp->getSecret();
        // 保存到数据库
        $stmt = $this->db->prepare("UPDATE users SET totp_secret = ? WHERE id = ?");
        $stmt->execute([$secret, $userId]);
        return $secret;
    }
    /**
     * 验证一次性密码
     */
    public function verifyOTP($userId, $otp) {
        // 获取用户密钥
        $stmt = $this->db->prepare("SELECT totp_secret FROM users WHERE id = ?");
        $stmt->execute([$userId]);
        $secret = $stmt->fetchColumn();
        if (!$secret) {
            return false;
        }
        // 验证OTP
        $totp = TOTP::create($secret);
        return $totp->verify($otp);
    }
    /**
     * 敏感操作二次验证
     */
    public function verifyBeforeOperation($userId, $otp, $callback) {
        if (!$this->verifyOTP($userId, $otp)) {
            throw new Exception('验证码无效');
        }
        // 执行回调函数(敏感操作)
        return $callback();
    }
}
// 使用示例
$twoFactor = new TwoFactorAuth($db);
$userId = $_SESSION['user_id'];
// 验证OTP后执行转账操作
$twoFactor->verifyBeforeOperation($userId, $_POST['otp_code'], function() use ($userId, $amount) {
    // 执行转账逻辑
    transferMoney($userId, $amount);
});
?>

Token验证机制

创建一个临时的操作令牌用于二次确认。

<?php
class TokenVerification {
    private $db;
    private $session;
    public function __construct($db, $session) {
        $this->db = $db;
        $this->session = $session;
    }
    /**
     * 生成操作令牌
     */
    public function generateToken($userId, $operation, $expireMinutes = 5) {
        $token = bin2hex(random_bytes(32));
        $expires = date('Y-m-d H:i:s', strtotime("+{$expireMinutes} minutes"));
        // 保存令牌信息
        $stmt = $this->db->prepare("
            INSERT INTO operation_tokens (user_id, token, operation, expires_at) 
            VALUES (?, ?, ?, ?)
        ");
        $stmt->execute([$userId, $token, $operation, $expires]);
        return $token;
    }
    /**
     * 验证令牌
     */
    public function verifyToken($token, $operation) {
        $stmt = $this->db->prepare("
            SELECT * FROM operation_tokens 
            WHERE token = ? AND operation = ? AND expires_at > NOW() 
            AND used = 0
        ");
        $stmt->execute([$token, $operation]);
        $result = $stmt->fetch();
        if (!$result) {
            return false;
        }
        // 标记令牌已使用
        $stmt = $this->db->prepare("UPDATE operation_tokens SET used = 1 WHERE id = ?");
        $stmt->execute([$result['id']]);
        return true;
    }
    /**
     * 敏感操作流程
     */
    public function sensitiveOperation($operation, $data) {
        // 第一步:生成令牌并发送到用户邮箱/短信
        $token = $this->generateToken(
            $this->session->get('user_id'), 
            $operation
        );
        // 发送令牌到用户手机或邮箱
        sendVerificationCode($token);
        return [
            'status' => 'pending',
            'message' => '请查收验证码并确认操作',
            'token_id' => $token
        ];
    }
    /**
     * 确认操作(第二步)
     */
    public function confirmOperation($token, $operation, $data) {
        if (!$this->verifyToken($token, $operation)) {
            throw new Exception('令牌无效或已过期');
        }
        // 执行敏感操作
        switch ($operation) {
            case 'delete_assets':
                return deleteAssets($data['asset_id']);
            case 'transfer_ownership':
                return transferOwnership($data);
        }
    }
}
?>

完整的安全建议

<?php
trait SecurityVerificationTrait {
    /**
     * 验证请求来源和CSRF保护
     */
    private function validateRequest() {
        // 验证CSRF令牌
        if (!isset($_POST['csrf_token']) || 
            $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
            throw new Exception('CSRF验证失败');
        }
        // 验证请求来源
        $referrer = parse_url($_SERVER['HTTP_REFERER'] ?? '');
        if ($referrer['host'] !== $_SERVER['HTTP_HOST']) {
            throw new Exception('非法的请求来源');
        }
        // 验证请求方法
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            throw new Exception('仅支持POST请求');
        }
    }
    /**
     * 操作日志记录
     */
    private function logOperation($userId, $operation, $status, $details = []) {
        $logData = [
            'user_id' => $userId,
            'operation' => $operation,
            'status' => $status,
            'ip' => $_SERVER['REMOTE_ADDR'],
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'details' => json_encode($details),
            'created_at' => date('Y-m-d H:i:s')
        ];
        // 保存到日志表
        $sql = "INSERT INTO operation_logs (user_id, operation, status, ip, user_agent, details, created_at) 
                VALUES (:user_id, :operation, :status, :ip, :user_agent, :details, :created_at)";
        // 执行SQL...
    }
    /**
     * 限制尝试次数,防止暴力破解
     */
    private function checkAttemptLimit($userId) {
        $attempts = $this->redis->get("verify_attempts:{$userId}") ?: 0;
        if ($attempts >= 5) {
            // 锁定账户一段时间
            $this->redis->setex("account_locked:{$userId}", 1800, 1);
            throw new Exception('尝试次数过多,账户已被锁定30分钟');
        }
        // 增加尝试次数
        $this->redis->incr("verify_attempts:{$userId}");
        $this->redis->expire("verify_attempts:{$userId}", 3600);
    }
}
// 综合使用示例
class SensitiveOperationController {
    use SecurityVerificationTrait;
    public function executeSensitiveOperation($operation, $params) {
        try {
            // 1. 基础验证
            $this->validateRequest();
            // 2. 检查尝试限制
            $userId = $_SESSION['user_id'];
            $this->checkAttemptLimit($userId);
            // 3. 二次验证
            if (!isset($_POST['verify_code'])) {
                throw new Exception('请提供验证码');
            }
            $verifyResult = $this->verifyCode($userId, $_POST['verify_code']);
            if (!$verifyResult) {
                $this->logOperation($userId, $operation, 'failed', [
                    'reason' => '验证码错误'
                ]);
                throw new Exception('验证失败');
            }
            // 4. 执行操作
            $result = $this->performOperation($operation, $params);
            // 5. 记录成功日志
            $this->logOperation($userId, $operation, 'success', $params);
            return $result;
        } catch (Exception $e) {
            // 记录失败日志
            $this->logOperation($userId ?? 0, $operation, 'error', [
                'error' => $e->getMessage()
            ]);
            throw $e;
        }
    }
}
?>

总结建议

  1. 选择合适方案:根据安全性要求选择:简单操作用验证码,高安全用2FA或Token

  2. 记录日志:所有二次验证尝试和敏感操作都要记录

  3. 限制尝试次数:防止暴力破解

  4. 令牌过期机制:设置合理的过期时间(通常5-10分钟)

  5. CSRF保护:结合CSRF Token防止跨站请求伪造

  6. 会话管理:验证过程应基于会话状态,确保用户已登录

  7. 通知用户:验证成功后通知用户(如邮件、短信)

  8. 异常处理:妥善处理各种异常情况,避免信息泄露

根据项目具体需求选择实现方式,轻量级项目可使用验证码方案,金融级项目建议使用2FA或多重验证。

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