首页
/ freeCodeCamp 高级 Node 与 Express 实战:用 Passport 实现用户登出(/logout)与 404 兜底中间件

freeCodeCamp 高级 Node 与 Express 实战:用 Passport 实现用户登出(/logout)与 404 兜底中间件

2026-09-04 12:24:21作者:江焘钦

在 freeCodeCamp 的 "Advanced Node and Express" 认证课程中,"Logging a User Out"(挑战文件 58965611f9fc0f352b528e6c.md)是登录体系落地的关键一步:它教你在基于 Express + Passport + express-session 的服务端应用中,用一条 /logout 路由把用户"从认证状态拉回未认证状态",并顺手补齐 Node 项目中最常见的 404 兜底中间件。读完本文,你将掌握 Passport 会话注销的完整流程、中间件注册顺序的重要性,以及该挑战的测试用例是如何对你的 server.js 做源码级与行为级双重验证的。

课程定位:登出挑战在整个认证链中的位置

根据课程结构文件 advanced-node-and-express.json,这个 block 的 22 个挑战按如下顺序推进认证功能:

  1. 模板引擎(Pug):Set up a Template Engine → Use a Template Engine's Powers;
  2. Passport 初始化:Set up Passport → Serialization of a User Object → Implement the Serialization of a Passport User;
  3. 本地认证策略:Authentication Strategies → How to Use Passport Strategies → Create New Middleware;
  4. 个人资料页:How to Put a Profile Together;
  5. 登出:Logging a User Out(本文主题)
  6. 注册新用户的 Registration of New Users,以及随后的 Hashing Your Passwords(bcrypt)。

也就是说,当你做到本文对应的挑战时,你的 Express 应用已经具备了:express-session 会话(客户端只保存 session id cookie,个人账号数据留在服务端,这是 Set up Passport 一课建立的安全模型)、passport.authenticate('local') 登录路由,以及一个展示 req.user.username/profile 页面。登出逻辑要做的,就是对称地"撤销"这一切。

核心实操一:编写 /logout 路由注销会话

原文档给出的实现非常简洁——路由只需"把用户置为未认证,然后重定向到首页,而不是渲染任何视图"。在 Passport 中,注销用户就是在重定向前调用 req.logout()

app.route('/logout')
  .get((req, res) => {
    req.logout();
    res.redirect('/');
  });

这段代码的工作机制可以拆成两层来理解:

  • req.logout():由 passport.session() 中间件挂载在 req 上的方法(见 Set up Passport 中要求注册的 app.use(passport.session()))。调用它会销毁当前会话(session destroy),使服务端保存的登录态失效。
  • res.redirect('/'):随后把客户端重定向回首页。重定向之所以必要,是因为如果直接渲染某个视图,可能暴露仍属于登录用户的页面状态。

与登录侧形成对称的是:登录路由(How to Use Passport Strategies)通过 passport.authenticate('local', { failureRedirect: '/' }) 认证成功后,把用户对象写入 req.user;登出路由则用 req.logout() 清除会话。一个"写入"、一个"清除",构成了完整的认证生命周期。

核心实操二:为所有未匹配路由添加 404 兜底中间件

原文档指出:你可能注意到,目前你的应用还没有处理"页面不存在(404)"的情况。在 Node/Express 中的常见做法是添加如下中间件——关键点是把它放在所有其他路由之后

app.use((req, res, next) => {
  res.status(404)
    .type('text')
    .send('Not Found');
});

这里有几个值得注意的技术细节:

  • 注册顺序决定生效范围:Express 按注册顺序执行路由与中间件。只有当请求没有被前面任何路由处理时,才会落到这个兜底中间件上,因此它必须"最后"注册;
  • 链式响应res.status(404) 设置状态码,res.type('text') 设置 Content-Type 响应头,res.send('Not Found') 写入响应体——三步链式调用返回的都是 res 自身,这是 Express 响应对象的典型风格;
  • 它也是错误处理链路的一环:注意这里声明了 next 参数却不调用,因为响应已经发出,请求处理到此终止。这与课程前面"Create New Middleware"一课中"通过 next(err) 传递错误"的模式形成对照。

与前后挑战的衔接:登出如何融入完整用户体验

登出不是孤立存在的路由,它和前后挑战共同拼出完整的登录/登出闭环:

  1. 入口在个人资料页:在 How to Put a Profile Together 一课中,你已经在 profile.pug 里添加了指向 https://gitcode.com/GitHub_Trending/fr/freeCodeCamp/tree/main//logout 的链接 a(href='/logout') Logout,并渲染了 Welcome, #{username}!。因此用户点击该链接发起 GET 请求时,就会命中本文的 /logout 路由并被弹回首页;
  2. 登出后 /profile 应不可访问:由于 /profile 路由带有"必须已认证"的守卫(未登录时重定向到首页),req.logout() 销毁会话之后,再访问 /profile 就会被重定向回首页——这正是登出功能的正确行为,而非 bug;
  3. 下一步是注册:紧随其后的 Registration of New Users 会实现 POST /register(先 findOne 查重,再 insertOne 入库,然后复用 passport.authenticate('local', { failureRedirect: '/' }) 完成注册即登录,最后重定向到 /profile),并在此课中给出覆盖"注册 → 登录 → 登出 → 登出后访问 /profile"全链路的行为测试;再之后的 Hashing Your Passwords 则用 bcrypt.hashSync(req.body.password, 12)bcrypt.compareSync 替换明文密码存储。

测试用例如何验证你的实现

该挑战的 --hints-- 部分(即官方测试)采用 freeCodeCamp 编辑器环境的通用做法:通过 new URL("/_api/server.js", code) 拿到你在在线编辑器中的 server.js 源码文本做静态断言,同时直接对运行中的应用发请求做行为断言。核心断言有两条:

断言 1:源码中必须存在 req.logout() 调用

const url = new URL("/_api/server.js", code);
const res = await fetch(url);
const data = await res.text();
assert.match(
  data,
  /req.logout/gi,
  'You should be calling req.logout() in your /logout route'
);

断言 2:GET /logout 之后,用户应被重定向到首页

const url = new URL("/logout", code);
const res = await fetch(url);
const data = await res.text();
assert.match(
  data,
  /Home page/gi,
  'When a user logs out they should be redirected to the homepage'
);

第二条断言能"穿透"重定向断言到首页内容,说明测试请求携带了凭据/会话信息(同课程的 Registration of New Users 行为测试中,$.ajax 明确使用了 xhrFields: { withCredentials: true } 来保持会话,并验证了"Logout should redirect to home"与"Profile should no longer work after logout"两条端到端性质)。这也提醒我们:https://gitcode.com/GitHub_Trending/fr/freeCodeCamp/tree/main//logout 设计为 GET 路由是课程刻意选择——这样 Pug 模板里的 <a href='/logout'> 超链接可以直接触发登出,无需额外的表单或 JS 提交。

小结:从这一课带走的东西

  • 在 Passport 体系中,登出 = 在重定向前调用 req.logout(),路由本身不渲染任何视图,res.redirect('/') 把用户安全地送回公开页面;
  • 404 兜底中间件是 Express 请求处理链的"最后一道关卡",必须注册在所有路由之后,通过 res.status(404).type('text').send('Not Found') 链式调用完成响应;
  • 结合课程上下文(登录策略、会话初始化、profile 守卫、后续注册与 bcrypt 挑战),可以推断出整个认证的"进—守—出"模型:req.login()(由 passport.authenticate 隐式完成)写入会话,/profile 守卫读取会话,req.logout() 销毁会话;
  • 官方测试同时验证"源码里有 req.logout"和"GET /logout 落到首页",静态检查与行为检查并用——这是理解该课程所有挑战验收逻辑的一个缩影。

如需在本地仓库中继续深入,可对照 Logging a User Out 挑战文件、block 结构定义 advanced-node-and-express.json,以及相邻挑战文件 Set up PassportHow to Put a Profile TogetherRegistration of New Users 查看完整认证流程的上下文。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
902
1.82 K
docsdocs
暂无描述
Markdown
888
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.51 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341