PHP项目找回密码功能如何安全开发

wen PHP项目 20

本文目录导读:

PHP项目找回密码功能如何安全开发

  1. 核心安全原则
  2. 安全实现步骤
  3. 安全增强措施
  4. 完整流程示例
  5. 安全注意事项

在PHP项目中开发安全的找回密码功能,需重点防范账号枚举令牌泄露时间差攻击重放攻击,以下是经过安全实践验证的开发方案:

核心安全原则

  1. 无差异响应:成功/失败返回相同信息,避免枚举有效账号
  2. 临时令牌:使用高熵随机数,设置短有效期(通常15-30分钟)
  3. 一次性使用:令牌使用后立即失效
  4. 防暴力破解:限制请求频率和尝试次数

安全实现步骤

生成重置令牌

// 生成安全的随机令牌
function generateResetToken() {
    return bin2hex(random_bytes(32)); // 64字符十六进制
}

存储令牌(带过期时间)

// 存储到数据库
function storeResetToken($userId, $token) {
    $expiresAt = date('Y-m-d H:i:s', strtotime('+30 minutes'));
    $hashedToken = hash('sha256', $token); // 存储哈希值
    $stmt = $pdo->prepare("INSERT INTO password_resets (user_id, token_hash, expires_at) 
                          VALUES (?, ?, ?)");
    $stmt->execute([$userId, $hashedToken, $expiresAt]);
}

发送邮件(关键安全点)

function sendResetEmail($email, $token) {
    // 不要提示账号是否存在
    // 统一返回"如果账号存在,已发送重置邮件"
    $resetLink = "https://yoursite.com/reset-password?token=" . urlencode($token);
    // 使用邮件模板,包含用户友好的说明
    $subject = "重置您的密码";
    $body = "请点击以下链接重置密码(30分钟内有效):\n\n";
    $body .= $resetLink;
    mail($email, $subject, $body, $headers);
}

验证令牌

function verifyResetToken($token) {
    $hashedToken = hash('sha256', $token);
    // 查找有效令牌(未使用且未过期)
    $stmt = $pdo->prepare("SELECT user_id, expires_at 
                          FROM password_resets 
                          WHERE token_hash = ? 
                          AND used_at IS NULL 
                          AND expires_at > NOW()");
    $stmt->execute([$hashedToken]);
    $result = $stmt->fetch();
    if (!$result) {
        return false; // 令牌无效或已过期
    }
    // 标记为已使用(防止重放)
    $stmt = $pdo->prepare("UPDATE password_resets 
                          SET used_at = NOW() 
                          WHERE token_hash = ?");
    $stmt->execute([$hashedToken]);
    return $result['user_id'];
}

重置密码

function resetPassword($userId, $newPassword) {
    // 密码强度验证
    if (strlen($newPassword) < 8) {
        throw new Exception("密码长度至少8位");
    }
    // 使用bcrypt哈希
    $hashedPassword = password_hash($newPassword, PASSWORD_BCRYPT);
    // 更新密码
    $stmt = $pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?");
    $stmt->execute([$hashedPassword, $userId]);
    // 可选:使其他活跃会话失效
    $stmt = $pdo->prepare("DELETE FROM sessions WHERE user_id = ?");
    $stmt->execute([$userId]);
}

安全增强措施

速率限制

// 基于IP限制重置请求
function checkRateLimit($ip) {
    $window = 3600; // 1小时
    $maxAttempts = 5;
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM reset_attempts 
                          WHERE ip = ? AND created_at > NOW() - INTERVAL 1 HOUR");
    $stmt->execute([$ip]);
    $attempts = $stmt->fetchColumn();
    if ($attempts >= $maxAttempts) {
        throw new Exception("请求过于频繁,请1小时后再试");
    }
}

令牌安全存储

-- 数据库表设计
CREATE TABLE password_resets (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    token_hash VARCHAR(64) NOT NULL,  -- SHA256哈希
    expires_at DATETIME NOT NULL,
    used_at DATETIME DEFAULT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_token_hash (token_hash),
    INDEX idx_expires (expires_at)
);

清理过期令牌

// 定时任务或每次请求前清理
function cleanExpiredTokens() {
    $pdo->exec("DELETE FROM password_resets WHERE expires_at < NOW()");
}

完整流程示例

// 1. 请求重置密码
if ($_POST['action'] == 'request_reset') {
    $email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
    // 速率限制检查
    checkRateLimit($_SERVER['REMOTE_ADDR']);
    // 查找用户(不要泄露是否存在)
    $user = findUserByEmail($email);
    if ($user) {
        $token = generateResetToken();
        storeResetToken($user['id'], $token);
        sendResetEmail($email, $token);
    }
    // 统一响应
    echo json_encode(['message' => '如果该邮箱已注册,您将收到重置邮件']);
}
// 2. 验证并重设密码
if ($_POST['action'] == 'reset_password') {
    $token = $_POST['token'];
    $password = $_POST['password'];
    $userId = verifyResetToken($token);
    if (!$userId) {
        echo json_encode(['error' => '令牌无效或已过期']);
        exit;
    }
    resetPassword($userId, $password);
    echo json_encode(['message' => '密码已成功重置']);
}

安全注意事项

  1. HTTPS强制使用:所有重置流程必须在HTTPS下进行
  2. CSRF防护:重置表单需包含CSRF令牌
  3. 日志记录:记录所有重置尝试(但不记录密码)
  4. 通知用户:密码更改成功后发送通知邮件
  5. 避免自动登录:重置后不要自动登录,让用户手动登录

采用上述方案,你的PHP找回密码功能将具备企业级的安全防护能力。

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