首页
/ freeCodeCamp 进阶 Node.js 与 Express:编写 ensureAuthenticated 中间件保护 /profile 路由

freeCodeCamp 进阶 Node.js 与 Express:编写 ensureAuthenticated 中间件保护 /profile 路由

2026-09-04 16:11:33作者:袁立春Spencer

本篇基于 freeCodeCamp 课程仓库中 Advanced Node and Express 模块的 "Create New Middleware" 挑战讲解一个典型的 Express 安全实践:当任何访客直接输入 URL 访问 /profile 时,无论其是否已登录都能看到页面——这正是需要自定义认证中间件的场景。你将学会实现 ensureAuthenticated(req, res, next) 函数,借助 Passport 的 isAuthenticated 方法检查会话状态,并通过 app.route API 把它挂在路由处理函数之前,同时理解该挑战源码级的验证方式(静态正则断言加运行时重定向测试),从而掌握"路由守卫"在 Node.js 全栈认证流程中的完整落地。

挑战定位:认证链路中的"路由守卫"环节

该挑战位于 curriculum 目录下的 Advanced Node and Express 模块(块名 advanced-node-and-express),其元数据见 curriculum/challenges/english/blocks/advanced-node-and-express/5895f70df9fc0f352b528e6a.md

---
id: 5895f70df9fc0f352b528e6a
title: Create New Middleware
challengeType: 2
dashedName: create-new-middleware
---

该模块的任务顺序由 curriculum/structure/blocks/advanced-node-and-express.jsonchallengeOrder 数组定义,本挑战排在第 7 位:

顺序 挑战标题 与本挑战的关系
6 How to Use Passport Strategies 配置 /login 的 POST 认证,成功后 req.user 被填充
7 Create New Middleware(本挑战) 阻止未认证用户直接访问 /profile
8 How to Put a Profile Together 使用 req.user.username 渲染个人主页

模块归属方面,curriculum/structure/superblocks/quality-assurance.jsonadvanced-node-and-express 列为该认证超块的第二个 block,前后分别是 quality-assurance-and-testing-with-chaiquality-assurance-projects

原文档给出的问题背景很直接:

As is, any user can just go to /profile whether they have authenticated or not by typing in the URL. You want to prevent this by checking if the user is authenticated first before rendering the profile page. This is the perfect example of when to create a middleware.

也就是说,认证(authentication)只发生在 /login 提交时,而 /profile 的 GET 处理函数本身不做任何身份检查——浏览器里只要输入地址就能访问。解决思路就是把"是否已登录"这一横切关注点抽成中间件,在渲染之前统一拦截。

完整实现:ensureAuthenticated 中间件函数

原文档给出的标准实现如下(此处按可运行语法做了收尾修正,见下文"细节注意"):

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {
    return next();
  }
  res.redirect('/');
}

逐行解析这三个参数与两条分支:

  • req:Express 请求对象。Passport 在会话机制(passport.session())启用后,会在请求对象上扩展出 isAuthenticated() 方法。原文档明确指出其语义:"...by calling Passport's isAuthenticated method on the request which checks if req.user is defined",即该方法的本质是判断 req.user 是否已定义。而 req.user 的来源正是前一课 How to Use Passport Strategies 中的约定:"If the authentication was successful, the user object will be saved in req.user"——/login 使用 passport.authenticate('local') 认证成功后,用户对象才会落到 req.user 上。
  • res:响应对象。未登录时调用 res.redirect('/'),把请求重定向回首页(那里有登录表单),从而保证用户永远无法在未认证状态下拿到个人页面。
  • next:Express 中间件链的推进器。isAuthenticated() 为真时调用 next(),控制权才会交给下一个处理函数(即渲染 profile 视图的回调);若不调用,请求将挂起无响应。

这里的 return next() 值得强调:显式 return 保证 next() 之后不会再继续执行 res.redirect('/'),避免"既放行又重定向"的重复响应。

细节注意:原代码片段的收尾写法

原挑战文档中的代码块以 }; 结尾(function 关键字的函数声明后带分号)。在 Node.js 中,function 声明语句末尾的分号不是合法语法,直接粘贴运行会报 SyntaxError;若写成表达式赋值 const ensureAuthenticated = function (req, res, next) { ... };; 是必需的。课程沙盒的静态断言不校验语法(见下文),但本地实践时请去掉这个分号。

挂载方式:app.route 链式 API 与执行顺序

原文档要求把中间件传给 /profile 路由,且必须位于 GET 处理函数之前:

app
 .route('/profile')
 .get(ensureAuthenticated, (req, res) => {
    res.render('profile');
 });

要点说明:

  • app.route('/profile'):Express 的路由实例 API,返回一个 app.Route 对象,可以链式注册 .get() / .post() 等多个方法;相比 app.get('/profile', ...),它的优势是多个 HTTP 方法可以共享同一路径与中间件。
  • 参数顺序即执行顺序ensureAuthenticated 作为 .get() 的第一个参数先执行,只有它调用 next() 后,第二个参数 (req, res) => res.render('profile') 才会执行。把中间件放在处理函数"之前"(before the argument to the GET request)是本挑战的硬性要求。
  • 视图渲染:处理函数最终执行 res.render('profile'),渲染 views/profile.pug(该模块使用 Pug 模板引擎,见模块首课 Set up a Template Engine)。

验证机制:静态断言与运行时集成测试

挑战文档的 # --hints-- 部分内嵌了测试代码,它揭示了平台如何判定本挑战完成。测试通过 /_api/server.js/_api/package.json 等接口在沙盒中读取项目文件,分两类:

1. 静态正则断言(检查代码结构)

  const url = new URL("/_api/server.js", code);
  const res = await fetch(url);
  const data = await res.text();
  assert.match(
    data,
    /ensureAuthenticated[^]*req.isAuthenticated/,
    'Your ensureAuthenticated middleware should be defined and utilize the req.isAuthenticated function'
  );
  assert.match(
    data,
    /profile[^]*get[^]*ensureAuthenticated/,
    'Your ensureAuthenticated middleware should be attached to the /profile route'
  );

两条正则对应两条要求:其一,server.jsensureAuthenticated 之后([^]* 为跨行的"任意内容")必须出现 req.isAuthenticated,即中间件必须用 req.isAuthenticated() 判断登录态;其二,profile 之后、get 之后必须出现 ensureAuthenticated,即中间件必须挂在 /profile 路由的 GET 上。

2. 运行时断言(检查真实行为)

  const url = new URL("/profile", code);
  const res = await fetch(url);
  const data = await res.text();
  assert.match(
    data,
    /Home page/,
    'An attempt to go to the profile at this point should redirect to the homepage since we are not logged in'
  );

测试在"没有任何已注册用户"的沙盒状态下对 /profile 发起 GET。期望的完整链路是:请求命中 ensureAuthenticatedreq.isAuthenticated() 为假 → res.redirect('/') 将 302 重定向到首页 → 断言拿到的响应文本包含首页标记 /Home page/。如果中间件没挂上,或重定向目标写错,这条断言都会失败。

这套"静态结构 + 运行时行为"的双重验证,正好覆盖了本挑战的两个交付物:中间件的定义与中间件的挂载

与前后课程的衔接:req.user 从产生到使用

把本挑战放回整条认证链路,可以看到 req.user 的"生产—守卫—消费"闭环:

  1. 生产:前课 Authentication Strategies 注册 passport.use(new LocalStrategy(...)),在 findOne 查到用户且密码匹配后 done(null, user)How to Use Passport Strategies 配置 app.post('/login', passport.authenticate('local', { failureRedirect: '/' })),认证成功后 Passport 将用户对象写入 req.user
  2. 守卫(本挑战):ensureAuthenticated 通过 req.isAuthenticated() 检查 req.user 是否存在,不存在则 res.redirect('/')
  3. 消费:后课 How to Put a Profile Together 在守卫通过的前提下,把 username: req.user.username 传给 render,并在 profile.pug 中用 h2#welcome Welcome, #{username}! 展示用户名——没有本挑战的中间件,这一步就暴露了越权风险。

从源码结构看,这也是 Passport 中间件模式的典型分层:passport.initialize() / passport.session() 属于全局中间件(挂在所有路由之前,负责重建 req.user),而 ensureAuthenticated 属于路由级中间件(只守卫特定路径),两者分工明确。

小结

本挑战的全部核心交付物可以浓缩为三点:实现一个调用 req.isAuthenticated() 的二分支中间函数;通过 app.route('/profile').get(ensureAuthenticated, handler) 把它挂在处理函数之前;未认证访问被重定向回 /。对应的仓库证据链完整:挑战正文与断言见 5895f70df9fc0f352b528e6a.md,任务顺序与模块归属见 advanced-node-and-express.jsonquality-assurance.json,前后课衔接见 5895f70df9fc0f352b528e69.md5895f70ef9fc0f352b528e6b.md。掌握了这套"定义守卫 → 挂载路由 → 重定向兜底"的写法,就可以把同样的模式推广到任何需要按会话状态放行或拦截的 Express 路由上。

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

项目优选

收起
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++
903
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