本文目录导读:

在 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);
}
}
?>
注意事项
- 设置验证频率限制:防止暴力破解
- 验证码一次性使用:防止重放攻击
- 记录验证日志:便于审计
- 定期轮换密钥:适用于 TOTP
- 考虑验证失效机制:保证安全性
- 前端配合:实现完整的验证流程
选择哪种验证方式取决于您的业务需求、用户群体和安全性要求,建议根据实际场景选择合适的方式,必要时可以组合使用。