Express + TypeScript 下写 JWT 中间件,我踩了三个坑

我是小ao。在"面试克星"项目里,我需要用 JWT 保护用户更新接口,确保用户只能改自己的信息。之前在 Vue 论坛项目中,我用 JavaScript 写过类似的中间件,跑得很顺畅。但这次换到 TypeScript + Express,我本以为只是多写几个类型,结果连踩三个坑。

这篇文章,就是我从头实现一个 JWT 鉴权中间件的实战记录。

中间件要做什么?

我的中间件逻辑很简单:

  1. 从请求头取出 Authorization 字段,格式必须是 Bearer <token>
  2. jsonwebtoken 库验证 token 的合法性。
  3. 验证通过后,从数据库查出用户信息,挂载到 req 对象上。
  4. 后面的路由处理函数就能直接从 req.user 拿到当前登录用户。

在 JS 版本里,我直接在 req 上挂了一个 user 属性,什么事都没有。但到了 TS 里,编译器直接报错:类型"Request"上不存在属性"user"

第一个坑:扩展 Express 的 Request 类型

Express 的类型定义里,Request 对象没有 user 属性。我需要告诉 TypeScript:"我的 req 上会多一个 user 对象"。

我首先尝试了最"优雅"的做法------创建一个 src/types/express.d.ts 文件,用 declare global 来扩展 Express.Request。谁知不管怎么配,编译时就是找不到这个属性。后来发现,只要 .d.ts 文件里包含 import 语句,TypeScript 就会把它当成普通模块,全局扩展失效。

折腾了一下午,我选择了最务实的方法:直接在中间件文件 auth.ts 里定义一个扩展接口 AuthRequest extends Request,并导出它。在路由文件里导入并使用:

typescript 复制代码
export interface AuthRequest extends Request {
  user?: any;
}

在路由里,我显式声明 req: AuthRequest,TypeScript 终于安静了。优雅败给了现实,但代码跑通了。

第二个坑:req.user 可能为 undefined

扩展后,TS 不再报错"属性不存在",但它在使用 req.user._id 时依旧会警告:"对象可能为'未定义'"。因为 user 被我定义成了可选属性(user?),而 TypeScript 不知道中间件已经在运行时给它赋了值。

解决方案是加非空断言 !req.user!._id。这行代码的意思是:"我确定这个值在这里一定不是 nullundefined,你不用检查了"。它不是偷懒,而是一种与编译器的"约定":中间件保证了这个值存在,请放心。

第三个坑:Token 应该存在哪儿?

Token 的存储位置一直是个争议点。放在 localStorage 里,有被 XSS 攻击窃取的风险;放在 httpOnly cookie 里更安全,但需要后端配合设置,实现成本高。

考虑到这还是个人开发阶段,而且我之前论坛项目的 JWT 也是一直存在 localStorage,我决定暂时沿用这个方案。安全性与开发成本的权衡,没有银弹,只有适合当前阶段的方案。

完整的中间件实现

下面是最终能正常工作的 authMiddleware。它从 authHeader 中提取 token,用 jwt.verify 解析,查数据库,挂载用户,最后调用 next()。如果 token 无效或过期,会返回不同的错误信息。

typescript 复制代码
export const authMiddleware = async (req: AuthRequest, res: Response, next: NextFunction) => {
  try {
    const authHeader = req.headers.authorization;
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return res.status(401).json({ message: '未授权' });
    }
    const token = authHeader.split(' ')[1];
    const decoded = jwt.verify(token, process.env.JWT_SECRET!) as { userId: string };
    const user = await User.findById(decoded.userId).select('-password');
    if (!user) return res.status(401).json({ message: '用户不存在' });
    req.user = user;
    next();
  } catch (error) {
    // 处理 Token 过期、无效等情况
  }
};

写在最后

TypeScript 给后端开发带来的"严格",刚开始时确实会拖慢节奏。但当你习惯了先定义类型、再实现逻辑的流程后,会发现很多运行时的错误在编译阶段就被消灭了。这次踩坑经历让我更清楚地认识到:在 JS 里靠经验避免的错误,在 TS 里可以靠编译器帮你记住。

相关推荐
右耳朵猫AI2 小时前
JS/TS周刊2026W22 | Deno 2.8、Node.js v26.2.0、Firefox 151、Storybook 10.4、npm 12.0
javascript·node.js·firefox
右耳朵猫AI2 小时前
前端周刊2026W22 | React 13周年、TanStack Router、Deno 2.8、Node.js 26、npm 分阶段发布
前端·react.js·node.js
Java.熵减码农4 小时前
Windows 下 Node.js 安装与配置完全指南
windows·node.js
夜雪闻竹12 小时前
测试策略:单元测试 + 集成测试怎么写
typescript·单元测试·集成测试·chatcrystal
凌云拓界12 小时前
文件管理:让AI安全操作你的电脑 ——CogitoAgent开发实战(三)
javascript·人工智能·架构·开源·node.js
凌云拓界13 小时前
联网能力:让AI看见更广阔的世界 ——CogitoAgent开发实战(四)
javascript·人工智能·架构·node.js·创业创新
JieE21219 小时前
Bun + TypeScript:下一代 JavaScript 全栈开发的正确打开方式
typescript·全栈·bun
GuWenyue19 小时前
告别JS类型坑!Ts为什么在ai时代逐渐成为"第一"语言
前端·算法·typescript
kisshyshy19 小时前
告别 Node 噩梦?用 Bun + TypeScript 像写诗一样调用大模型
前端·typescript