freeCodeCamp Advanced Node and Express:用 Passport LocalStrategy 实现本地认证策略(Authentication Strategies)
本篇围绕 freeCodeCamp 课程中 Advanced Node and Express 项目的一个核心关卡 "Authentication Strategies" 展开:讲解如何用 Passport 的 passport-local 策略实现"用户名 + 密码"的本地认证,包括 LocalStrategy 的注册方式、done 回调的三种返回语义、为什么策略代码必须放在数据库连接回调内,以及自动化测试是如何校验你的实现的。读完后你能把这套认证策略完整跑通,并理解它在整个登录链路(/login 路由 → 中间件 → 会话)中的位置。
在课程项目中的位置
该关卡属于 freeCodeCamp 课程仓库中"质量保证与测试"(Quality Assurance)超级块下的 Advanced Node and Express 项目块,关卡定义见 关卡文档,块内关卡顺序见 advanced-node-and-express.json。
在关卡顺序中,它排在完成模板引擎(Pug)、配置 Passport 与 express-session、实现用户序列化(serializeUser / deserializeUser)和数据库连接之后。也就是说,到达本关卡时你的项目已经具备:
pug视图引擎(app.set('view engine', 'pug')、views指向./views/pug);express-session会话(secret取自process.env.SESSION_SECRET,resave: true、saveUninitialized: true、cookie: { secure: false }),以及app.use(passport.initialize())和app.use(passport.session());passport.serializeUser/passport.deserializeUser,并且序列化/反序列化代码以及路由都包裹在myDB(async client => { ... })的数据库连接回调中(见 Set up Passport 与 Implement the Serialization of a Passport User)。
本关卡的任务是补上"认证策略"这块拼图:告诉 Passport 如何 验证一个用户,而不是 在哪个路由上 调用验证(后者是下一关 "How to Use Passport Strategies" 的内容)。
什么是策略(Strategy)
按原文档的定义:策略是一种认证用户的方式。你可以基于本地保存的信息(前提是先让用户注册过)来认证,也可以依赖 Google、GitHub 等第三方提供商。本项目使用 Passport 中间件——Passport 提供了一整套策略,支持用户名/密码、GitHub、Google 等多种认证方式。
本关卡用到的策略是 passport-local@~1.0.0(课程模板已预先作为依赖写入 package.json),你只需要在自己的服务器代码中引入它:
const LocalStrategy = require('passport-local');
注册 LocalStrategy:完整代码与逐行解析
原文档要求用 passport.use 注册一个实例化后的 LocalStrategy 对象,并且特别强调:从这一步起的代码都要封装在数据库连接回调内,因为策略依赖数据库。完整写法如下:
passport.use(new LocalStrategy((username, password, done) => {
myDataBase.findOne({ username: username }, (err, user) => {
console.log(`User ${username} attempted to log in.`);
if (err) return done(err);
if (!user) return done(null, false);
if (password !== user.password) return done(null, false);
return done(null, user);
});
}));
这段代码定义了"本地认证"的完整流程:先用用户输入的 username 去数据库查用户;再比对密码;只要没有命中错误分支,就把 user 对象交还给 Passport,用户即被认证通过。
逐行看 done 回调的三种调用语义,这是理解 Passport 本地策略的关键:
| 调用 | 含义 | 本例对应场景 |
|---|---|---|
done(err) |
认证过程本身出错(例如数据库查询失败) | findOne 返回 err |
done(null, false) |
凭证无效:用户不存在或密码不匹配 | !user 或 password !== user.password |
done(null, user) |
认证成功,把完整用户对象交给 Passport | 用户名、密码都匹配 |
注意密码比对采用的是明文全等比较(password !== user.password)。这并不是疏漏——课程在后面的 "Hashing Your Passwords" 关卡(关卡文档)会明确要求引入 bcrypt@~5.0.0,把这一行替换为:
if (!bcrypt.compareSync(password, user.password)) {
return done(null, false);
}
并在注册路由用 bcrypt.hashSync(req.body.password, 12) 生成哈希后再入库。也就是说,本关卡先建立"策略骨架",后续关卡再加固密码存储这一安全环节。
为什么必须放在数据库连接回调里
原文档用加粗文字强调"make sure this (as well as everything from this point on) is encapsulated in the database connection"。原因在于:策略的 LocalStrategy 回调中调用了 myDataBase.findOne,而 myDataBase 这个集合句柄是在 myDB(async client => { ... }) 回调内通过 client.db('database').collection('users') 创建的。从源码结构看,把 passport.use(...) 放在回调之外,myDataBase 要么未定义、要么指向已关闭的旧连接。因此正确结构是:
myDB(async client => {
const myDataBase = await client.db('database').collection('users');
// 序列化、反序列化也在这里
passport.serializeUser((user, done) => { done(null, user._id); });
passport.deserializeUser((id, done) => {
myDataBase.findOne({ _id: new ObjectID(id) }, (err, doc) => {
done(null, doc);
});
});
// 本关卡:注册 LocalStrategy
passport.use(new LocalStrategy((username, password, done) => {
myDataBase.findOne({ username: username }, (err, user) => {
if (err) return done(err);
if (!user) return done(null, false);
if (password !== user.password) return done(null, false);
return done(null, user);
});
}));
// 后续路由(/login 等)也在这里
}).catch(e => {
// 连接失败时渲染错误页
});
// app.listen 放在 myDB 回调之外
其他策略:以 GitHub 策略为例
原文档还指出:很多策略都通过各自不同的配置项搭建,通常参照该策略仓库的 README 即可轻松配置。它举了 GitHub 策略的例子——你不需要关心用户名或密码,因为用户会被跳转到 GitHub 的认证页;只要用户在 GitHub 处于登录状态并同意授权,GitHub 就会把用户资料返回给你使用。块内后续的三个 "Implementation of Social Authentication" 关卡(见 关卡顺序)正是把这一思路落地。
自动化测试如何校验你的实现
本关卡是 challengeType: 2(项目型关卡),测试代码内置在关卡文档的 --hints-- 区块中(原文档 的 L41-L77)。它通过 /_api/package.json 和 /_api/server.js 两个内部端点拉取你的代码做静态断言:
- 依赖检查:
package.json的dependencies中必须列出passport-local; - 引入检查:
server.js中必须匹配/require.*("|')passport-local("|')/,即确实require('passport-local'); - 策略注册检查:必须出现
new LocalStrategy(assert.match(data, /new LocalStrategy/)),证明你告诉 Passport 使用了一个新策略; - 数据库查询检查:必须出现
findOne(assert.match(data, /findOne/)),证明本地策略是基于输入的用户名执行findOne查询的,而不是硬编码用户。
对照原文档的验收标准:passport-local 是依赖、策略已实例化并注册、策略内使用 findOne 查询用户名——三条全部满足后,本关卡才算完成。
与下一关的衔接:策略注册与策略调用是分开的两步
本关卡末尾原文档预告:"在下一步,你将设置如何真正调用认证策略来基于表单数据验证用户。" 在下一关 How to Use Passport Strategies 中:
index.pug里的登录表单默认被if showLogin隐藏,需要在res.render时传入showLogin: true才会显示;- 表单 POST 到
/login,该路由需要挂载中间件passport.authenticate('local', { failureRedirect: '/' }),认证通过后才执行重定向到/profile的响应; - 认证成功时,完整用户对象保存在
req.user上(这正是前面序列化/反序列化配合会话 cookie 的工作成果)。
在尚未实现注册流程之前,对 /login 的 POST 请求会失败并重定向回首页 /,服务端控制台会打印 User {USERNAME} attempted to log in.——这是验证本关策略代码已生效的最直接信号(策略回调执行了,只是查不到已注册用户)。
小结与参考
本关卡在整条认证链路中的职责可以概括为:
- 策略(本关):定义"怎样判定一个用户通过"——
passport.use(new LocalStrategy(...))+findOne+ 密码比对 + 三种done语义; - 调用(下一关):在
/loginPOST 路由上用passport.authenticate('local')触发策略,failureRedirect处理失败分支; - 会话与持久化(前置关卡):express-session + 序列化保证"登录一次,后续请求免验证";
- 安全加固(后续关卡):
bcrypt哈希替换明文比对。
核心参考文件:
- Authentication Strategies 关卡文档(本文主体)
- Set up Passport、Serialization of a User Object、Implement the Serialization of a Passport User(前置关卡)
- How to Use Passport Strategies、Hashing Your Passwords(后续关卡)
- 关卡顺序定义、所在超级块
说明:以上内容基于当前课程仓库中文档的原文与关卡顺序文件整理;文中涉及的依赖版本(passport-local@~1.0.0、passport@~0.4.1、express-session@~1.17.1、mongodb@~3.6.0、bcrypt@~5.0.0)均以课程模板 package.json 中的声明为准。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00