验证码过期异常问题:验证码立即失效而非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>
问题根源分析
- Session启动时机错误:
controllerUserData.php中仅在验证成功分支才调用session_start(),但判断验证码过期时已在读取$_SESSION['verification_timestamp'],此时Session未启动,导致$storedTimestamp始终为0,直接触发过期错误。 - Session未全局初始化:
verification.php直接使用$_SESSION['email']但未提前启动Session,不仅会导致未登录判断失效,还影响后续Session读取。 - 验证码与用户绑定不严谨:仅通过
code查询用户,存在多用户验证码重复(概率极低但有风险)时的错误匹配问题。 - 过期时间依赖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
相关产品推荐
相关产品推荐

