用哈希密码登录
机器翻译
本页由英文原文机器翻译而来,尚未经过母语审校,欢迎在 GitHub 上提出修改。内容如有出入,以英文原文为准。
任务: 让用户用电子邮件和密码登录,数据库只保存密码的哈希,并且某个页面只对已登录用户显示。
数据表保存的是 bcrypt 哈希(用 hashPassword 生成,见注册示例), 从不保存密码本身:
yaml
# Recipe: sign in against a password hash, and a page only a signed-in user sees.
paths:
components: ./components
migrations: ./migrations
datasources:
db:
driver: sqlite
database: ./data/users.db1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
sql
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'member',
password_hash TEXT NOT NULL
);
-- The password is "correct horse battery"; only its bcrypt hash is stored
-- (made with hashPassword, see the sign-up recipe).
INSERT INTO users (email, name, role, password_hash) VALUES
('ana@example.com', 'Ana', 'admin',
'$2b$12$I4TEFfyLIaWrYBSQnyB1cuE1nkqh3CS4qpXmtZcUPUokfv/cOZNnC');1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
登录动作查找用户,并用 verifyPassword 检查密码。对于错误的密码、不存在的用户或空字段, 它返回 false,从不报错。成功时,它设置 require_auth 和 require_role 读取的会话变量。 session.sessionExpiry 是必需的:没有它的会话算作已过期。
xml
<q:component name="Login">
<q:action name="signin" method="POST">
<q:param name="email" type="email" required="true" />
<q:param name="password" required="true" />
<q:query name="user" datasource="db">
SELECT id, name, role, password_hash FROM users WHERE email = :email
<q:param name="email" value="{email}" type="string" />
</q:query>
<q:if condition="user_result.recordCount == 1 and verifyPassword(password, user[0].password_hash)">
<q:set name="session.authenticated" value="true" type="boolean" />
<q:set name="session.userId" value="{user[0].id}" />
<q:set name="session.userName" value="{user[0].name}" />
<q:set name="session.userRole" value="{user[0].role}" />
<q:set name="session.sessionExpiry" value="{dateAdd('h', 8)}" />
<q:redirect url="/" flash="Welcome, {user[0].name}!" />
</q:if>
<!-- The same message whether the address or the password is wrong. -->
<q:redirect url="/login" flash="Wrong e-mail or password." flashType="error" />
</q:action>
<h1>Sign in</h1>
<q:if condition="flash"><p class="flash-{flashType}">{flash}</p></q:if>
<form method="POST">
<input name="email" type="email" placeholder="E-mail" />
<input name="password" type="password" placeholder="Password" />
<button>Sign in</button>
</form>
</q:component>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
首页用 require_auth="true" 要求一个已登录的会话:
xml
<q:component name="Home" require_auth="true">
<q:if condition="flash"><p>{flash}</p></q:if>
<h1>Hello, {session.userName}</h1>
<p>You are signed in as {session.userRole}.</p>
</q:component>1
2
3
4
5
2
3
4
5
错误的地址和错误的密码得到同样的消息,所以表单不会告诉陌生人哪些地址有账号:
xml
<q:test name="a visitor who is not signed in is sent to sign in" page="/">
<test:visit />
<test:expect text="Sign in" />
<test:expect no-text="Hello," />
</q:test>
<q:test name="the right password signs in" page="/login">
<test:submit action="signin" email="ana@example.com" password="correct horse battery" />
<test:expect redirect="/" flash="Welcome, Ana!" />
<test:expect text="Hello, Ana" />
<test:expect text="You are signed in as admin." />
</q:test>
<q:test name="a wrong password is refused, with the same message as an unknown address" page="/login">
<test:submit action="signin" email="ana@example.com" password="guess" />
<test:expect redirect="/login" flash="Wrong e-mail or password." />
<test:submit action="signin" email="nobody@example.com" password="guess" />
<test:expect redirect="/login" flash="Wrong e-mail or password." />
</q:test>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
text
tests/login.test.q
PASS a visitor who is not signed in is sent to sign in
PASS the right password signs in
PASS a wrong password is refused, with the same message as an unknown address
3 passed, 0 failed1
2
3
4
5
2
3
4
5
更多内容见 Authentication 指南(英文)。