登录支持用户名,与手机号并存 (#2)

原来 Auth::attempt 只按 phone_hash 查,用户名永远登不进去。
家人账号常常不填手机号,演示场景下用名字登录更自然。

- 先按手机号 hash 精确查(唯一),未命中再按 name 查
- 重名命中多条一律拒绝,绝不取第一条(否则 A 的密码能登进 B 的账号)
- 表单 label 改「手机号 / 用户名」,错误文案改「账号或密码错误」

验证:逻辑单测 10/10(含重名拒绝);反向验证改坏判断即变红 (#2)
This commit is contained in:
2026-11-01 11:07:30 +08:00
parent fdc890fee0
commit d6a7cc99bc
3 changed files with 34 additions and 11 deletions
+31 -8
View File
@@ -60,21 +60,44 @@ class Auth
} }
/** /**
* 通过手机号 + 密码尝试登录 * 尝试登录。账号可以是**手机号**,也可以是**姓名**。
*
* 为什么两种都收:安装向导创建的管理员填的是手机号,而家人账号常常不填手机号
* (只有名字),演示/内网场景下用名字登录更自然。
*
* 顺序:先按手机号 hash 精确匹配(唯一,不会歧义),再按姓名匹配。
* 姓名可能重名 ⇒ **命中多条一律判失败**,绝不「取第一条」——
* 那等于让 A 用密码登进了 B 的账号,是最坏的一种 bug。
*
* @return array|null 成功返回用户数组,失败返回 null * @return array|null 成功返回用户数组,失败返回 null
*/ */
public static function attempt(string $phone, string $password): ?array public static function attempt(string $account, string $password): ?array
{ {
$phone = trim($phone); $account = trim($account);
if ($phone === '' || $password === '') return null; if ($account === '' || $password === '') return null;
$phoneHash = hash('sha256', $phone); $db = Database::get();
$stmt = Database::get()->prepare(
// ① 手机号:走 sha256 精确查(唯一,不会歧义)
$stmt = $db->prepare(
'SELECT * FROM users WHERE phone_hash = ? AND role IN ("admin","user") AND status = "active" LIMIT 1' 'SELECT * FROM users WHERE phone_hash = ? AND role IN ("admin","user") AND status = "active" LIMIT 1'
); );
$stmt->execute([$phoneHash]); $stmt->execute([hash('sha256', $account)]);
$user = $stmt->fetch(); $user = $stmt->fetch();
if (!$user) return null;
// ② 手机号没命中,再按姓名查
if (!$user) {
$stmt = $db->prepare(
'SELECT * FROM users WHERE name = ? AND role IN ("admin","user") AND status = "active"'
);
$stmt->execute([$account]);
$rows = $stmt->fetchAll();
// 重名 → 拒绝,不做猜测
if (count($rows) !== 1) return null;
$user = $rows[0];
}
if (!password_verify($password, (string)($user['password_hash'] ?? ''))) return null; if (!password_verify($password, (string)($user['password_hash'] ?? ''))) return null;
return $user; return $user;
+1 -1
View File
@@ -24,7 +24,7 @@ class AuthController
$user = Auth::attempt($phone, $password); $user = Auth::attempt($phone, $password);
if (!$user) { if (!$user) {
flash('error', '手机号或密码错误'); flash('error', '账号或密码错误');
redirect('auth/login'); redirect('auth/login');
} }
+2 -2
View File
@@ -5,8 +5,8 @@
<p class="muted">请登录后继续</p> <p class="muted">请登录后继续</p>
<div class="form-row"> <div class="form-row">
<label>手机号</label> <label>手机号 / 用户名</label>
<input type="text" name="phone" required autofocus placeholder="请输入手机号"> <input type="text" name="phone" required autofocus placeholder="手机号或用户名">
</div> </div>
<div class="form-row"> <div class="form-row">
<label>密码</label> <label>密码</label>