验证密码
本课属于 Coddy Node.js 学习旅程的“身份验证与安全”单元。第 6 课,共 24 课。
登录时,会使用保存的盐值和原始密钥长度,根据尝试输入的密码派生出一个密钥。即使密码正确,使用新的盐值也会得到不同的密钥。存储的密码永远不会被解码。
// 邀请码以 salt.keyHex 的格式存储。
const [salt, keyHex] = savedInvite.split('.');
const expected = Buffer.from(keyHex, 'hex');
const attempted = await scryptAsync(inviteCode, salt, 32);确认两个字节序列的长度相同后,使用 timingSafeEqual 比较它们。这样可以避免因内容而提前退出,否则攻击者可能通过计时获取信息。Buffer 的 === 检查的是对象身份,而普通的字符串比较无法保证恒定时间。你的密码函数使用密码记录中的冒号分隔符和 64 字节密钥。你添加的 POST /login 路由会根据结果返回 200 或 401。
挑战
简单此步骤会打开课程预先准备好的私密笔记 API 项目,而不是你在 Never Store Passwords 中的项目文件(包括 passwords.js)。你之前的工作仍保留在它自己的课中。编辑 passwords.js 前,先阅读提供的文件。这份副本包含密码哈希辅助函数和提供的调试路由。
现在,注册会保存加盐后的哈希。添加登录功能,检查密码是否与注册时保存的密码匹配。
- 在
passwords.js中,添加并导出一个async函数verifyPassword(password, stored)。stored是注册时保存的值,例如f2bb...f065:c3f7...dbd2。如果password是生成该值的密码,则返回true,否则返回false:- 取冒号前面的盐值。
- 用该盐值对
password进行哈希,生成一个 64 字节的密钥,方式与hashPassword相同。 - 将冒号后保存的密钥从十六进制解码。如果两个密钥的字节长度不同,不要调用
timingSafeEqual,直接返回false,如“比较密钥字节”一课所示。否则,返回比较结果。
- 在
server.js中,在hashPassword旁边导入verifyPassword。添加POST /login。它接收与注册相同的请求体,例如{"username": "ada", "password": "correct horse"}。- 查找用户名对应的用户。
verifyPassword运行较慢,并且会返回一个 promise,因此要等待它完成。 - 如果用户存在且密码正确,则使用状态码 200 响应,并返回
{"username": "ada"}。 - 否则,使用状态码 401 响应,并返回
{"error": "Wrong username or password"}。401 Unauthorized 表示请求未能证明请求者的身份。 - 密码错误和用户名未注册时,都返回完全相同的 401 响应。这样,响应体就不会泄露任何已注册的用户名。
- 查找用户名对应的用户。
试试看:按“运行”。发送 POST /signup,请求体为 {"username": "ada", "password": "correct horse"}:状态码为 201。发送 POST /login,请求体相同:状态码为 200,并返回 {"username": "ada"}。发送 POST /login,请求体为 {"username": "ada", "password": "wrong horse"}:状态码为 401,并返回 {"error": "Wrong username or password"}。发送 POST /login,请求体为 {"username": "nobody", "password": "correct horse"}:返回相同的 401。
亲手试试
import { randomBytes, scrypt } from 'node:crypto';
import { promisify } from 'node:util';
const scryptAsync = promisify(scrypt);
// 将密码转换为注册时保存的文本。
export async function hashPassword(password) {
const salt = randomBytes(16).toString('hex');
const key = await scryptAsync(password, salt, 64);
return `${salt}:${key.toString('hex')}`;
}
身份验证与安全中的所有课
自己练习: Express 在线编辑器