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

Node.js Express中如何在中间件内调用其他中间件

问题背景

使用Node.js + Express搭配JWT认证开发时,遇到中间件使用问题:部分公开路由不需要强制用户登录,但如果用户处于已登录状态,需要解析用户身份并挂载到req.auth属性,供后续业务逻辑使用。
作为Express初学者,自行编写了isLoggedIn中间件,逻辑为检测客户端是否携带token:无token则直接放行,检测到token时尝试调用authorize授权中间件完成身份解析。但实际运行时授权逻辑被直接跳过,通过console.log打印执行链路调试始终未定位问题。

原有问题实现代码

isLoggedIn中间件实现:

function isLoggedIn() {
    return (req, res, next) => {
        var clientToken
            // 检测客户端是否携带token,无token则返回null
        if (req.headers.authorization && req.headers.authorization.split(" ")[0] === "Bearer") {
            clientToken = req.headers.authorization.split(" ")[1];
        } else if (req.query && req.query.token) {
            clientToken = req.query.token;
        } else if (req.cookies && req.cookies['session']) {
            clientToken = req.cookies['session'];
        } else {
            clientToken = null;
        }

        if (!clientToken) next();
        else authorize();
    }
}

authorize授权中间件实现:

function authorize(roles = []) {
    console.log("1");
    // roles参数支持传入单个角色字符串(如 Role.User 或 'User')
    // 也支持传入角色数组(如 [Role.Admin, Role.User] 或 ['Admin', 'User'])
    if (typeof roles === 'string') {
        roles = [roles];
    }

    console.log("2");
    return [
        // 校验JWT token,将解析出的用户信息挂载到请求对象的req.auth属性
        jwt({
            secret,
            algorithms: ['HS256'],
            getToken: function fromHeaderOrQuerystring(req) {
                console.log("4");
                if (req.headers.authorization && req.headers.authorization.split(" ")[0] === "Bearer") {
                    console.log("why is it here?");
                    return req.headers.authorization.split(" ")[1];
                } else if (req.query && req.query.token) {
                    console.log("query string?")
                    return req.query.token;
                } else if (req.cookies && req.cookies['session']) {
                    console.log("5");
                    return req.cookies['session'];
                }
                console.log("null?");
                return null;
            }
        }),
        // 基于用户角色做权限校验
        async(req, res, next) => {
            // 这部分逻辑始终没有执行
            console.log("7");
            const account = await User.findOne({ id: req.auth.sub });
            const refreshTokens = await refreshToken.find({ account: account.id });

            if (!account || (roles.length && !roles.includes(account.role))) {
                // 账号不存在或角色无权限
                return res.status(401).json({ message: 'Unauthorized' });
            }

            // 认证授权通过
            req.auth = account;
            req.auth.ownsToken = token => !!refreshTokens.find(x => x.token === token);
            next();
        }
    ];
}

原有路由使用逻辑

  • 公开路由:app.get("/", isLoggedIn(), (req, res) => res.render('index'));,预期效果为未登录用户可正常访问,登录用户可通过req.auth获取身份信息
  • 受保护路由:app.get("/user", authorize(), (req, res) => res.render('user'));,预期效果为未登录用户无法访问

最终解决方案

调整中间件设计思路,移除单独编写的isLoggedIn中间件,通过Express标准中间件组合实现需求:

  • 受保护路由直接挂载authorize()中间件,强制要求登录鉴权
  • 公开路由按顺序挂载authorize()、新增的NoLoginRequired错误处理中间件,实现可选登录解析
  • 后续可优化authorize()实现,使其符合Express官方中间件规范,不再返回函数数组

最终路由示例

  • 公开路由:app.get("/unprotected", authorize.authorize(), authorize.NoLoginRequired, (req, res) => res.render('unprotectedview'));
  • 受保护路由:app.get("/user", authorize.authorize(), (req, res) => res.render('user'));

NoLoginRequired中间件实现

注意:Express错误处理中间件必须保持(err, req, res, next)的四参数签名,挂载时不要加括号执行

function NoLoginRequired(err, req, res, next) {
    if (err && err.name === "UnauthorizedError") {
        // 捕获JWT校验抛出的未授权错误,直接放行,不要求用户必须登录
        next();
    } else {
        // 其余错误正常传递给后续错误处理逻辑
        next(err);
    }
}

该方案符合Express中间件设计规范,可稳定实现公开路由可选登录态解析、受保护路由强制鉴权的需求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 15:48:15