在上一讲我们讨论了错误处理与服务稳定性,让服务在出错时仍可控、可观测、可恢复。在此基础上,还需要保证接口不被未授权访问、不被常见 Web 攻击利用,否则再稳的服务也会在安全问题上栽跟头。
这部分会围绕安全与认证基础展开:我们会先梳理常见 Web 安全问题及其与 Node 和 HTTP 的关系,接着说明 Token 与 JWT 的原理以及为何无状态凭证适合现代前后端分离架构,最后把鉴权作为中间件管道上的一环,说明如何设计鉴权中间件并与路由、Controller 配合。
HTTP 请求里的参数、body、header 最终都会以某种形式参与业务逻辑:查库、调系统命令、拼进 HTML 或脚本里。若服务端直接把用户输入当作「可执行」或「可拼接」的内容使用,就会给注入类攻击留下空间。例如在 Node 里用字符串拼接拼 SQL 时,攻击者可以在参数里塞入 ' OR '1'='1 之类的片段,从而改变查询语义,这就是典型的 SQL 注入。同理,若把用户输入传给 child_process.exec 或类似接口而不做转义,就可能变成命令注入,攻击者能在服务器上执行任意命令。这类问题的本质是「把用户输入当成了可信数据」,因此防护思路是在边界上做输入校验与规范化:对类型、长度、格式做约束,对要参与拼接或执行的内容做转义或参数化(例如用预编译语句、参数化查询),绝不把原始输入直接拼进 SQL、命令或 HTML。
与此相关的是敏感信息泄露。在开发或调试时,我们常把错误对象原样返回给客户端,或把完整堆栈打到日志再通过日志系统暴露出去。一旦生产环境也这样做了,攻击者可以通过故意触发错误来探测内部路径、依赖版本或业务逻辑,为后续攻击做准备。因此,对外返回的错误信息应统一收敛为「对用户友好、对攻击者无信息量」的文案,详细堆栈只留在服务端日志与监控里,不随响应体返回。

XSS(跨站脚本)是指攻击者把恶意脚本「塞进」你的页面,当其他用户访问该页面时,脚本在用户浏览器里执行,从而窃取 Cookie、篡改页面或发起以该用户身份为凭的请求。在 Node 后端场景里,XSS 往往与「服务端把用户输入原样写进 HTML 或 JSON 里再返回」有关:例如评论、昵称、搜索关键词若未经转义就出现在响应里,攻击者就可以注入 <script>...</script> 或事件属性。防护方式是在输出时做转义(根据输出上下文选择 HTML 转义、JavaScript 转义或 URL 编码),并配合前端的 Content-Security-Policy 等头,减少「即使有漏洞也难以执行」的纵深。
CSRF(跨站请求伪造)是另一类跨站问题:用户已在你的站点登录,Cookie 里带着会话或 Token;攻击者在自己的站点放一个表单或脚本,诱使用户点击或加载,浏览器会带着该 Cookie 向你的站点发请求。从服务端角度看,请求来自「已登录用户」,但意图并非用户本意。防护思路包括:对敏感操作校验 Referer / Origin、使用 CSRF Token(服务端下发、表单提交时带回)、以及对于 API 优先使用 Authorization 头携带 Token 而非仅依赖 Cookie(因为跨站脚本发起的请求默认不会带上自定义 Header,而 Cookie 会被浏览器自动带上)。Node 里通常会在中间件层做这些校验,与鉴权中间件配合。
传统基于 Session 的认证方式在服务端维护会话状态:用户登录后,服务端在内存或存储里记一份「sessionId → 用户信息」,把 sessionId 通过 Set-Cookie 写给浏览器,后续请求带着 Cookie 上来,服务端根据 sessionId 查会话再得到用户身份。这种方式在单机或少量节点时简单直观,但在水平扩展时就要考虑会话存储的共享与一致性,且前后端分离、多端(Web / 移动端 / 第三方)接入时,Cookie 的携带与同源策略会带来不少约束。
Token 的思路是:登录成功后,服务端不存会话,而是签发一个自包含的凭证,客户端在后续请求里携带这个凭证(例如放在 Authorization: Bearer <token> 里),服务端只需验证凭证的合法性与有效性即可识别用户。这样服务端无状态,扩缩容时不必同步会话存储,也便于多端统一使用同一套鉴权接口。JWT(JSON Web Token)是这类凭证的一种常见标准:一段 Base64 编码的字符串,由三部分组成,用点号分隔,分别为 header、payload、signature。header 里通常包含算法标识(如 HS256、RS256),payload 里是可以自定义的声明(如用户 id、角色、过期时间 exp),signature 是用服务端持有的密钥(或私钥)对「header.payload」做签名得到的,用于校验 Token 是否被篡改、是否由本服务签发。
验证时,服务端用同一密钥(或公钥)对收到的 header 和 payload 再算一次签名,与 Token 里的第三段比对;同时检查 payload 里的 exp 是否已过期。只要签名正确且未过期,就认为该 Token 有效,可以从 payload 里取出用户信息,无需查库或查会话存储。这就是「无状态」的含义:身份信息编码在 Token 里,服务端只做校验不解密(payload 是 Base64 编码而非加密,任何人都能解码看到内容,因此不要在 payload 里放敏感信息)。

下面这段代码演示在 Node 里如何用 jsonwebtoken 库签发与验证 JWT。登录成功后调用 sign 生成 Token,payload 里放入用户 id 与过期时间;鉴权时从请求头取出 Token,用 verify 校验签名与过期时间,通过后即可从解码结果里拿到用户信息。
|const jwt = require('jsonwebtoken'); const SECRET = process.env.JWT_SECRET || 'your-secret'; // 登录成功后签发 function issueToken(userId) { return jwt.sign( { sub: userId, exp: Math.floor(Date.now() / 1000) + 3600 }, SECRET, { algorithm: 'HS256'
不要在 payload 里存放密码、密钥等敏感信息,因为 payload 仅做 Base64 编码,任何人拿到 Token 都能解码查看。同时务必在校验时检查 exp 与签名,并确保密钥(JWT_SECRET)足够强且不泄露;若 Token 在前端存储,注意 XSS 会导致 Token 被窃取,因此要配合 CSP 与输出转义降低 XSS 风险。
在第十一讲中我们把中间件理解为「请求—响应」管道上的一环,在第十二讲中又把这条管道对应到组合模式。鉴权正是这样一种横切逻辑:很多路由需要「已登录用户」才能访问,我们不必在每个 Controller 里重复「取 Token、校验、取用户」的代码,而是把它抽成鉴权中间件,挂到管道上。请求进入时,先经过 body 解析等通用中间件,再经过鉴权中间件:从 req.headers.authorization 或 ctx.headers.authorization 里取出 Bearer <token>,调用上一节里的 verifyToken,若校验通过就把解码出的用户信息挂到 req.user 或 ctx.state.user,然后调用 next() 把控制交给下一环;若未带 Token 或 Token 非法或已过期,则直接返回 401,不调用 next(),请求在这一环结束。
这样,挂在鉴权中间件之后的路由 handler 被调用时,可以默认认为「当前请求已认证」,直接从 req.user 或 ctx.state.user 取用户信息即可。若某些路由需要区分「匿名可访问」与「必须登录」,可以只对需要保护的路由或路由组挂鉴权中间件,而不是全局挂;例如在 Express 里对 /api/admin/* 使用 router.use(authMiddleware),而 /api/public/* 则不挂,这样既保证需要保护的路由统一经过鉴权,又避免对公开接口做无谓的 Token 校验。

下面以 Express 风格写一个简单的鉴权中间件:从 Authorization 头中取出 Bearer Token,调用 verifyToken,成功则把用户信息挂到 req.user 并 next(),失败则返回 401。
|function authMiddleware(req, res, next) { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'missing or invalid authorization' }); } const token = authHeader.slice(
在 Koa 中思路一致:从 ctx.headers.authorization 取 Token,校验通过后写 ctx.state.user = decoded 再 await next(),否则设 ctx.status = 401 并写 body,不调用 next()。这样,鉴权与业务逻辑解耦,业务层只依赖「管道已经做过鉴权」的约定,符合前面几讲中模块化与中间件组合的思路。
这节课我们围绕安全与认证基础展开了三块内容。在常见 Web 安全问题中,我们说明了注入类攻击源于把用户输入当作可信数据参与拼接或执行,防护依赖输入校验与参数化;同时提到了敏感信息泄露、XSS 与 CSRF 与 Node/HTTP 的关系,以及输出转义、Referer/Origin 校验和用 Authorization 头携带 Token 等防护思路。 在Token / JWT 原理中,我们解释了为何无状态凭证适合水平扩展与多端接入,JWT 的 header.payload.signature 结构与校验逻辑,以及签发与验证的示例代码,并提醒不要在 payload 中放敏感信息、务必校验签名与过期时间。在鉴权中间件设计中,我们把鉴权作为管道上的一环,说明如何从请求头取 Token、校验后挂载用户信息或直接 401,以及如何按路由或路由组挂载鉴权中间件,并给出了 Express 风格的简短实现示例。
理解了常见 Web 安全风险与 Token/JWT 的用法之后,接口的「谁可以访问」就有了清晰的边界。下一部分我们将讨论性能、扩展性与多进程:Node 的性能瓶颈、Cluster 模式以及如何利用多核 CPU,从而在「稳」和「安全」的基础上再提升服务的吞吐与扩展能力。