You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

验证码过期异常问题:验证码立即失效而非1小时后过期

验证码过期机制异常问题排查与优化建议

问题描述

搭建了一套用户验证系统,生成验证码发送给用户用于账号确认,但验证码过期机制存在异常:本应1小时后过期的验证码,在提交验证时却立即显示失效。以下是相关代码文件内容,需解决该问题并获取代码优化建议。

相关代码文件

controllerUserData.php

if (isset($_POST['verification'])) {
    // Initialize an array to store the digits
    $digits = [];

    // Length of verification code
    $totalDigits = 6;

    // Loop through the digits and retrieve them
    for ($i = 1; $i <= $totalDigits; $i++) {
        $inputName = 'digit' . $i;
        $digit = mysqli_real_escape_string($con, $_POST[$inputName]);
        $digits[] = $digit;
    }

    // Concatenate the digits to form the verification code
    $verificationCode = implode('', $digits);

    // Check the verification code in the database using a prepared statement
    $checkCodeQuery = "SELECT * FROM usertable WHERE code = ?";
    $stmtCheckCode = mysqli_prepare($con, $checkCodeQuery);
    mysqli_stmt_bind_param($stmtCheckCode, "s", $verificationCode);
    mysqli_stmt_execute($stmtCheckCode);
    $codeResult = mysqli_stmt_get_result($stmtCheckCode);

    // Check if the verification code exists in the database
    if (mysqli_num_rows($codeResult) > 0) {
        $userData = mysqli_fetch_assoc($codeResult);
        $email = $userData['email'];
        $code = 0;
        $status = 'verified';

        // Check if the verification code is still valid (within 1 hour)
        $storedTimestamp = isset($_SESSION['verification_timestamp']) ? $_SESSION['verification_timestamp'] : 0;
        $currentTimestamp = time();
        $timeoutSeconds = 3600;

        // Calculate the expiration time
        $expirationTime = $storedTimestamp + $timeoutSeconds;

        if ($currentTimestamp <= $expirationTime) {
            // Update the code and status in the database using a prepared statement
            $updateOtpQuery = "UPDATE usertable SET code = ?, status = ? WHERE code = ?";
            $stmtUpdateOtp = mysqli_prepare($con, $updateOtpQuery);
            mysqli_stmt_bind_param($stmtUpdateOtp, "iss", $code, $status, $verificationCode);
            $updateResult = mysqli_stmt_execute($stmtUpdateOtp);

            // Check if the update was successful
            if ($updateResult) {
                // Set the user's email in the session and redirect to the home page
                session_start();
                session_regenerate_id(); // Regenerate session ID to prevent session fixation
                $_SESSION['email'] = $email;
                unset($_SESSION['verification_timestamp']); // Clear the timestamp after successful verification
                header('Location: home.php');
                exit();
            } else {
                // Display an error message if the update fails
                $errors['otpError'] = "Failed while updating code!";
            }
        } else {
            // Display an error message if the verification code has expired
            $errors['otpError'] = "The verification code has expired!";
        }
    } else {
        // Display an error message if the verification code is incorrect
        $errors['otpError'] = "You've entered an inavalid code!";
    }
}

verification.php

<?php
    // Include necessary files
    require_once "controllerUserData.php";

    // Check if the user is logged in
    $email = $_SESSION['email'];
    if (!$email) {
        // Redirect the user to the login page
        header('Location: login.php');
        // Terminate the script to ensure a clean redirect
        exit();
    }
?>

<!DOCTYPE html>
<html lang="en">

<body>
    <form action="" autocomplete="" method="post">
         <h1><Verification></h1>
        <div class="codeContainer">
            <?php
            $totalDigits = 6;
            for ($i = 1; $i <= $totalDigits; $i++) {
                echo '<input class="code active" type="text" maxlength="1" name="digit' . $i . '" placeholder="" required>';
            }
        ?>
        </div>
        <button class="primary-button" name="verification" type="submit">Verify</button>
        <p class="text-error text-center" id="errorMessage"><?php echo isset($errors['otpError']) ? $errors['otpError'] : ''; ?></p>
    </form>
    <script src="src/assets/js/form/codeContainer.js"></script>
</body>

</html>

问题根源分析

  1. Session启动时机错误:controllerUserData.php中仅在验证成功分支才调用session_start(),但判断验证码过期时已在读取$_SESSION['verification_timestamp'],此时Session未启动,导致$storedTimestamp始终为0,直接触发过期错误。
  2. Session未全局初始化:verification.php直接使用$_SESSION['email']但未提前启动Session,不仅会导致未登录判断失效,还影响后续Session读取。
  3. 验证码与用户绑定不严谨:仅通过code查询用户,存在多用户验证码重复(概率极低但有风险)时的错误匹配问题。
  4. 过期时间依赖Session不可靠:Session可能因浏览器关闭、超时等原因丢失,导致无法正确判断验证码有效期。

修复后的代码

controllerUserData.php

// 全局启动Session,确保脚本全程可访问
session_start();

if (isset($_POST['verification'])) {
    $digits = [];
    $totalDigits = 6;
    $errors = [];

    // 验证输入是否为数字
    for ($i = 1; $i <= $totalDigits; $i++) {
        $inputName = 'digit' . $i;
        $digit = $_POST[$inputName] ?? '';
        if (!ctype_digit($digit)) {
            $errors['otpError'] = "验证码必须为数字!";
            break;
        }
        $digits[] = $digit;
    }

    if (empty($errors)) {
        $verificationCode = implode('', $digits);
        $currentEmail = $_SESSION['email'] ?? '';

        if (empty($currentEmail)) {
            $errors['otpError'] = "请重新登录后验证!";
        } else {
            // 同时匹配邮箱和验证码,避免跨用户错误
            $checkCodeQuery = "SELECT code_created_at FROM usertable WHERE email = ? AND code = ?";
            $stmtCheckCode = mysqli_prepare($con, $checkCodeQuery);
            mysqli_stmt_bind_param($stmtCheckCode, "ss", $currentEmail, $verificationCode);
            mysqli_stmt_execute($stmtCheckCode);
            $codeResult = mysqli_stmt_get_result($stmtCheckCode);

            if (mysqli_num_rows($codeResult) > 0) {
                $userData = mysqli_fetch_assoc($codeResult);
                $code = 0;
                $status = 'verified';

                // 从数据库读取验证码生成时间(需提前在usertable新增code_created_at字段,类型为TIMESTAMP)
                $storedTimestamp = strtotime($userData['code_created_at']);
                $currentTimestamp = time();
                $timeoutSeconds = 3600;

                $expirationTime = $storedTimestamp + $timeoutSeconds;

                if ($currentTimestamp <= $expirationTime) {
                    // 按邮箱更新,避免验证码重复时的错误更新
                    $updateOtpQuery = "UPDATE usertable SET code = ?, status = ? WHERE email = ?";
                    $stmtUpdateOtp = mysqli_prepare($con, $updateOtpQuery);
                    mysqli_stmt_bind_param($stmtUpdateOtp, "iss", $code, $status, $currentEmail);
                    $updateResult = mysqli_stmt_execute($stmtUpdateOtp);

                    if ($updateResult) {
                        // 安全更新Session ID
                        session_regenerate_id(true);
                        $_SESSION['email'] = $currentEmail;
                        header('Location: home.php');
                        exit();
                    } else {
                        $errors['otpError'] = "更新用户状态失败!";
                    }
                } else {
                    $errors['otpError'] = "验证码已过期!";
                }
            } else {
                $errors['otpError'] = "验证码错误!";
            }
        }
    }
}

verification.php

<?php
// 提前启动Session
session_start();
require_once "controllerUserData.php";

$email = $_SESSION['email'] ?? '';
if (!$email) {
    header('Location: login.php');
    exit();
}
?>

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>验证码验证</title>
</head>
<body>
    <form action="" autocomplete="off" method="post">
         <h1>验证码验证</h1>
        <div class="codeContainer">
            <?php
            $totalDigits = 6;
            for ($i = 1; $i <= $totalDigits; $i++) {
                echo '<input class="code active" type="text" maxlength="1" name="digit' . $i . '" placeholder="" required>';
            }
        ?>
        </div>
        <button class="primary-button" name="verification" type="submit">验证</button>
        <p class="text-error text-center" id="errorMessage"><?php echo isset($errors['otpError']) ? $errors['otpError'] : ''; ?></p>
    </form>
    <script src="src/assets/js/form/codeContainer.js"></script>
</body>
</html>

代码优化建议

  • 数据库存储有效期:新增code_created_at字段存储验证码生成时间,完全替代Session存储,避免Session丢失导致的有效期判断错误。
  • 验证码唯一性保障:生成验证码时结合用户ID或随机字符串,确保全局唯一;验证成功后立即清空验证码,防止重复使用。
  • 输入验证增强:前端限制输入为数字,后端二次校验,避免非法输入提交。
  • 安全配置强化:在php.ini中启用session.cookie_secure=On和session.cookie_httponly=On,防止Session劫持;session_regenerate_id(true)销毁旧Session,避免会话固定攻击。
  • SQL查询优化:避免使用SELECT *,仅查询需要的字段;绑定参数时匹配字段类型(如验证码为数字则用i而非s)。
  • 用户体验优化:将错误提示改为中文,前端添加输入自动跳转(单个输入框填满后自动聚焦下一个)。

内容的提问来源于stack exchange,提问作者acy2k5

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 00:12:05