本文目录导读:

在PHP项目中实现用户密码找回功能,通常包含以下几个核心步骤,为了安全起见,不要直接重置密码,而是采用发送带有时效性令牌的验证链接的方式。
以下是标准的实现流程和关键代码示例:
核心流程
- 用户请求:用户输入注册邮箱/手机号,点击“忘记密码”。
- 生成令牌:服务器生成一个唯一的、随机的令牌(Token),并记录其过期时间。
- 存储令牌:将令牌与用户ID、过期时间存入数据库。
- 发送链接:向用户邮箱发送包含该令牌的链接(
https://yourapp.com/reset_password.php?token=xxx)。 - 用户点击:用户点击邮件中的链接,访问重置页面。
- 验证令牌:服务器验证令牌是否存在、是否过期、是否被使用过。
- 重置密码:验证通过后,显示新密码输入框,用户提交后更新密码,并删除或使令牌失效。
关键代码实现 (PHP + MySQL)
数据库表结构 (用于存储重置令牌)
CREATE TABLE password_resets (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL, -- 对应用户表ID
token VARCHAR(64) NOT NULL, -- 重置令牌
expires_at DATETIME NOT NULL, -- 过期时间
used TINYINT(1) DEFAULT 0, -- 是否已使用
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_token (token) -- 查询加速
);
请求重置 (forgot_password.php)
<?php
// 1. 接收用户输入的邮箱
$email = $_POST['email'];
// 2. 验证邮箱格式及是否存在(连接数据库查找)
$user = getUserByEmail($email);
if (!$user) {
// 为了安全,不要提示“该邮箱不存在”,统一提示“如果邮箱已注册,将收到邮件”
die("如果该邮箱已注册,您将收到重置密码的邮件。");
}
// 3. 生成安全的随机令牌(使用 random_bytes,比 rand() 安全)
$token = bin2hex(random_bytes(32)); // 64位十六进制字符串
// 4. 设置过期时间(1小时后)
$expiresAt = date('Y-m-d H:i:s', strtotime('+1 hour'));
// 5. 存储令牌到数据库(先删除该用户旧令牌,防止重复)
deleteOldTokens($user['id']);
saveToken($user['id'], $token, $expiresAt);
// 6. 发送邮件(使用PHPMailer或mail()函数)
$resetLink = "https://yourapp.com/reset_password.php?token=" . $token;
$subject = "密码重置请求";
$message = "您好,请点击以下链接重置密码(1小时内有效):\n" . $resetLink;
// 发送邮件...
sendEmail($email, $subject, $message);
echo "重置链接已发送到您的邮箱,请查收。";
?>
重置密码页面 (reset_password.php) - 显示表单与处理提交
<?php
$token = $_GET['token'] ?? '';
// 验证令牌是否有效
if ($token && validateToken($token)) {
// 显示重置密码表单
?>
<form method="POST">
<input type="hidden" name="token" value="<?= htmlspecialchars($token) ?>">
<input type="password" name="password" placeholder="新密码" required>
<input type="password" name="confirm_password" placeholder="确认密码" required>
<button type="submit">重置密码</button>
</form>
<?php
} else {
die("令牌无效或已过期。");
}
// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['token'])) {
$token = $_POST['token'];
$password = $_POST['password'];
$confirmPassword = $_POST['confirm_password'];
// 验证密码强度与一致性
if ($password !== $confirmPassword) {
die("两次密码输入不一致");
}
if (strlen($password) < 8) {
die("密码长度至少8位");
}
// 再次验证令牌有效
$tokenData = getTokenData($token);
if (!$tokenData) {
die("令牌无效");
}
// 更新密码(必须使用 password_hash)
$newPasswordHash = password_hash($password, PASSWORD_BCRYPT);
updateUserPassword($tokenData['user_id'], $newPasswordHash);
// 使令牌失效(标记为已使用)
markTokenUsed($token);
echo "密码已成功重置!<a href='login.php'>去登录</a>";
}
?>
⚠️ 关键安全注意事项
令牌生成
- 禁止使用
md5(uniqid(rand(), true))或时间戳等可预测值。 - 必须使用
random_bytes()或openssl_random_pseudo_bytes()(PHP 7+)。
令牌存储
- 数据库:存储令牌的哈希值(
hash('sha256', $token)),而不是明文,这样即使数据库泄露,攻击者无法直接使用令牌。 - 或者,至少确保数据库连接使用SSL,并且服务器权限严格控制。
令牌有效期
- 设置较短的有效期(通常15分钟到2小时)。
- 使用后立即销毁(标记为
used)。
用户枚举攻击
- 无论是成功还是失败,都输出相同的提示信息(如“如果该邮箱已注册,将收到邮件”)。
- 不要告诉用户“该邮箱不存在”。
密码存储
- 永远不要存储明文密码或MD5密码。
- 使用
password_hash()和password_verify()。
防止重放攻击
- 除了标记
used,还可以在令牌记录中添加IP或User-Agent信息进行额外校验。
传输安全
- 整个流程(请求页面、点击链接、提交新密码)必须使用 HTTPS,防止令牌在传输中被截获。
替代方案:短信验证码
如果项目使用手机号(而非邮箱):
- 用户输入手机号。
- 生成6位随机数字验证码。
- 调用短信API发送验证码。
- 用户输入验证码后,直接跳转到重置密码页面(此时可跳过令牌验证,因为验证码已证明了身份)。
- 验证码有效期极短(5分钟),且尝试次数应受限。
安全密码找回的核心是:使用高熵随机令牌 + 时效性 + 一次使用 + 密码高强度哈希。