PDO预编译语句函数修正及用户登录验证失败问题排查
解决PDO改写后登录验证失败的问题及代码修正
我帮你梳理下问题所在,以及对应的修复方案:
一、登录失败的核心问题
你在Auth类的getMemberByUsername方法里犯了两个关键错误:
- 方法没有返回查询结果:你只是把查询结果赋值给了类的
$this->data属性,但调用方法时$user = $auth->getMemberByUsername($username);,这里$user会是null,因为方法没有return语句。 - 结果取值方式不匹配:你的
DBController已经设置了默认的PDO::FETCH_OBJ(返回对象),但登录代码里却用数组下标$user[0]["member_password"]去取值,这会直接报错,导致password_verify永远不会成功,最终触发"Invalid Login"。
二、DBController里的冗余方法问题
你的insert和update方法完全是无效的:参数写的是$query,但内部用的是未定义的$sql,也没接收$args参数,逻辑完全混乱。其实你已经写了run方法,它可以处理所有类型的SQL语句(查询、插入、更新、删除),完全不需要单独的insert/update方法,直接删掉这两个方法即可,或者修正它们复用run方法。
三、修正后的代码
1. 修复Auth类的getMemberByUsername方法
class Auth { protected $db; protected $data; public function __construct() { $this->db = DBController::instance(); } // 修正后:返回查询结果,且如果用户名唯一,用fetch()更高效 function getMemberByUsername($username) { return $this->db->run("Select * from `members` where `member_name` = ?", [$username])->fetch(); } // 其他方法优化:查询单条用fetch,批量用fetchAll,更新/插入无需fetchAll function getTokenByUsername($username,$expired) { return $this->db->run("Select * from `tbl_token_auth` where `username` = ? and `is_expired` = ?", [$username, $expired])->fetchAll(); } function markAsExpired($tokenId) { $expired = 1; return $this->db->run("UPDATE `tbl_token_auth` SET `is_expired` = ? WHERE id = ?", [$expired, $tokenId]); } function insertToken($username, $random_password_hash, $random_selector_hash, $expiry_date) { return $this->db->run("INSERT INTO `tbl_token_auth` (username, password_hash, selector_hash, expiry_date) values (?, ?, ?,?)", [$username, $random_password_hash, $random_selector_hash, $expiry_date]); } }
2. 修复登录验证代码
if (! empty($_POST["login"])) { $isAuthenticated = false; $username = $_POST["member_name"]; $password = $_POST["member_password"]; // 现在$user是单个对象(如果查到),或者false(没查到) $user = $auth->getMemberByUsername($username); // 先判断用户是否存在,再验证密码 if ($user && password_verify($password, $user->member_password)) { $isAuthenticated = true; } // 后续处理登录成功/失败的逻辑... }
3. 修复DBController类
删掉冗余的insert和update方法,同时增加连接异常捕获方便调试:
<?php define('DB_HOST', 'xxx'); define('DB_NAME', 'xxx'); define('DB_USER', 'xxx'); define('DB_PASS', 'xxx'); define('DB_CHAR', 'xxx'); class DBController { protected static $instance; protected $pdo; public function __construct() { $opt = array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, PDO::ATTR_EMULATE_PREPARES => FALSE, ); $dsn = 'mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset='.DB_CHAR; // 增加连接异常捕获,方便调试 try { $this->pdo = new PDO($dsn, DB_USER, DB_PASS, $opt); } catch(PDOException $e) { die("数据库连接失败: " . $e->getMessage()); } } public static function instance() { if (self::$instance === null) { self::$instance = new self; } return self::$instance; } public function __call($method, $args) { return call_user_func_array(array($this->pdo, $method), $args); } public function run($sql, $args = []) { if (!$args) { return $this->query($sql); } $stmt = $this->pdo->prepare($sql); $stmt->execute($args); return $stmt; } }
四、额外建议
- 确保数据库中
members表的member_password字段是用password_hash()生成的哈希值,而不是明文或者其他加密方式,否则password_verify会失效。 - 对于只查询单条数据的场景(比如根据用户名查用户),用
fetch()代替fetchAll(),性能更好,代码也更简洁。
内容的提问来源于stack exchange,提问作者Maria
相关产品推荐
相关产品推荐

