You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

发送无效Token时JsonWebTokenError未被中间件处理致应用崩溃

问题:无效JWT Token未被错误中间件处理导致应用崩溃

配置了错误处理中间件,但发送无效Token时,jwt.verify(request.token, process.env.SECRET)抛出JsonWebTokenError: invalid signature错误,该错误未被中间件捕获处理,导致应用崩溃。此问题出现在使用无效Token尝试添加新博客的场景中,涉及代码文件如下:

blog.js

const jwt = require("jsonwebtoken");
const blogsRouter = require("express").Router();

const Blog = require("../models/blog");
const User = require("../models/user");

blogsRouter.post("/", async (request, response) => {
    const body = request.body;

    const decodedToken = jwt.verify(request.token, process.env.SECRET);
    if (!decodedToken.id) {
        return response.status(401).json({ error: "token invalid" });
    }
    const user = await User.findById(decodedToken.id);

    const newBlog = new Blog({
        title: body.title,
        author: body.author,
        url: body.url,
        likes: body.likes || 0,
        user: user.id,
    });

    const savedBlog = await newBlog.save();
    user.blogs = user.blogs.concat(savedBlog._id);
    await user.save();

    response.status(201).json(savedBlog);
});

module.exports = blogsRouter

middleware.js

const unknownEndpoint = (request, response) => {
    response.status(404).send({ error: "unknown endpoint" });
};

const errorHandler = (error, request, response, next) => {
    logger.error(error.message);

    if (error.name === "CastError") {
        return response.status(400).send({ error: "malformatted id" });
    } else if (error.name === "ValidationError") {
        return response.status(400).json({ error: error.message });
    } else if (error.name === "JsonWebTokenError") {
        return response.status(401).json({ error: "invalid token" });
    } else if (error.name === "TokenExpiredError") {
        return response.status(401).json({
            error: "token expired",
        });
    }
    
    next(error);
};

const tokenExtractor = (request, response, next) => {
    const authorization = request.get("authorization");
    if (authorization && authorization.startsWith("Bearer ")) {
        request.token = authorization.replace("Bearer ", "");
    }
    next()
}

module.exports = { requestLogger, unknownEndpoint, errorHandler, tokenExtractor };

app.js

const express = require("express");
const app = express();
const cors = require("cors");
const mongoose = require("mongoose");
const config = require("./utils/config");
const logger = require("./utils/logger");
const middleware = require("./utils/middleware");

const blogsRouter = require("./controllers/blogs");
const usersRouter = require("./controllers/users");
const loginRouter = require("./controllers/login");

mongoose.set("strictQuery", false);

mongoose
    .connect(config.MONGODB_URI)
    .then(() => {
        logger.info("connected to MongoDB");
    })
    .catch((error) => {
        logger.info("error connectig to MongoDB", error.message);
    });

app.use(cors());
app.use(express.json());
app.use(middleware.tokenExtractor)

app.use("/api/blogs", blogsRouter);
app.use("/api/users", usersRouter);
app.use("/api/login", loginRouter);

app.use(middleware.errorHandler);

module.exports = app;

问题原因

Express的错误处理中间件仅能捕获两类错误:

  1. 同步代码中直接抛出的错误
  2. 通过next(error)主动传递的错误

jwt.verify是同步方法,在async路由处理函数中抛出的错误会被封装为拒绝状态的Promise。若未显式捕获该Promise拒绝,Express无法自动将错误传递给错误处理中间件,最终导致应用崩溃。

解决方案

有两种可行的修复方式:

方式一:路由函数内用try/catch捕获错误

修改blog.js的POST路由逻辑,用try/catch包裹jwt.verify及后续异步操作,捕获错误后通过next(error)传递给错误处理中间件:

blogsRouter.post("/", async (request, response, next) => {
    try {
        const body = request.body;

        const decodedToken = jwt.verify(request.token, process.env.SECRET);
        if (!decodedToken.id) {
            return response.status(401).json({ error: "token invalid" });
        }
        const user = await User.findById(decodedToken.id);

        const newBlog = new Blog({
            title: body.title,
            author: body.author,
            url: body.url,
            likes: body.likes || 0,
            user: user.id,
        });

        const savedBlog = await newBlog.save();
        user.blogs = user.blogs.concat(savedBlog._id);
        await user.save();

        response.status(201).json(savedBlog);
    } catch (error) {
        next(error); // 将错误传递给错误处理中间件
    }
});

方式二:新增Token验证中间件

将jwt.verify逻辑移至单独中间件,同步抛出的错误会被Express自动捕获并传递给错误处理中间件:

  1. 在middleware.js中添加tokenValidator中间件:
const tokenValidator = (request, response, next) => {
    if (request.token) {
        const decodedToken = jwt.verify(request.token, process.env.SECRET);
        request.user = decodedToken; // 将解码后的Token存入request对象
    }
    next();
};

更新导出内容:

module.exports = { requestLogger, unknownEndpoint, errorHandler, tokenExtractor, tokenValidator };
  1. 在app.js中,于tokenExtractor之后、路由挂载前添加该中间件:
app.use(middleware.tokenExtractor);
app.use(middleware.tokenValidator); // 新增此行

app.use("/api/blogs", blogsRouter);
// ... 其他路由
  1. 修改blog.js的路由函数,直接使用request中存储的解码信息:
blogsRouter.post("/", async (request, response) => {
    const body = request.body;

    if (!request.user?.id) {
        return response.status(401).json({ error: "token invalid" });
    }
    const user = await User.findById(request.user.id);

    // ... 后续逻辑保持不变
});

两种方式均可让JsonWebTokenError被错误处理中间件捕获,返回正确的401响应,避免应用崩溃。

内容的提问来源于stack exchange,提问作者Vedagya Jha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 05:34:54