透過註冊信箱發送重置連結,使用一次性 Token 驗證身分,確保流程安全可控。以下為核心流程與關鍵代碼。
CREATE TABLE `users` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(50) NOT NULL UNIQUE,
`password` CHAR(60) NOT NULL, -- bcrypt hash
`email` VARCHAR(100) NOT NULL UNIQUE,
`reset_token` CHAR(64) DEFAULT NULL, -- SHA256 hex
`reset_expires` INT UNSIGNED DEFAULT NULL, -- Unix timestamp
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
<?php
require 'db.php'; // PDO 連線
$email = trim($_POST['email'] ?? '');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
exit(json_encode(['status'=>'error','msg'=>'信箱格式錯誤']));
}
$stmt = $pdo->prepare('SELECT id FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch();
if (!$user) {
// 為防止信箱枚舉,前端統一顯示「若信箱存在,重置郵件已發送」
exit(json_encode(['status'=>'ok','msg'=>'若信箱存在,重置郵件已發送']));
}
// 產生安全隨機 Token
$token = bin2hex(random_bytes(32)); // 64 字元 hex
$expires = time() + 3600; // 1 小時後過期
$stmt = $pdo->prepare('UPDATE users SET reset_token = ?, reset_expires = ? WHERE id = ?');
$stmt->execute([$token, $expires, $user['id']]);
// 發送郵件 (使用 PHPMailer 或 Symfony Mailer)
$resetLink = "https://example.com/reset.php?token=$token";
$mailBody = "請點擊以下連結重置密碼(1小時內有效):
$resetLink";
// send_mail($email, '密碼重置', $mailBody); // 實作省略
exit(json_encode(['status'=>'ok','msg'=>'若信箱存在,重置郵件已發送']));
?>
<?php
require 'db.php';
$token = $_GET['token'] ?? '';
if (strlen($token) !== 64 || !ctype_xdigit($token)) {
exit('無效的重置連結');
}
$stmt = $pdo->prepare('SELECT id, reset_expires FROM users WHERE reset_token = ?');
$stmt->execute([$token]);
$user = $stmt->fetch();
if (!$user) {
exit('無效的重置連結');
}
if ($user['reset_expires'] < time()) {
exit('重置連結已過期,請重新申請');
}
// Token 有效,顯示重置表單(隱藏 token 欄位)
?>
設定新密碼
<?php
require 'db.php';
$token = $_POST['token'] ?? '';
$pass = $_POST['password'] ?? '';
$pass2 = $_POST['password_confirm'] ?? '';
if ($pass !== $pass2) { exit('兩次密碼不一致'); }
if (strlen($pass) < 8) { exit('密碼至少 8 碼'); }
$stmt = $pdo->prepare('SELECT id FROM users WHERE reset_token = ? AND reset_expires > ?');
$stmt->execute([$token, time()]);
$user = $stmt->fetch();
if (!$user) { exit('Token 無效或已過期'); }
$hash = password_hash($pass, PASSWORD_BCRYPT);
$stmt = $pdo->prepare('UPDATE users SET password = ?, reset_token = NULL, reset_expires = NULL WHERE id = ?');
$stmt->execute([$hash, $user['id']]);
header('Location: login.php?reset=success');
exit;
?>
random_bytes(),不可用 md5()、uniqid() 等可預測值。password_hash() (BCRYPT/ARGON2ID),嚴禁明文或 MD5/SHA1。此流程符合 OWASP 認證重置最佳實踐,可直接套用於實際專案。