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

Node.js+Apollo GraphQL服务器JWT中间件未触发问题排查

问题:Apollo Server自定义JWT中间件未触发,无日志输出

问题描述

我编写了一个从请求头获取Token的JWT验证中间件,但该中间件始终未触发,连开头添加的hello日志都无法看到。相关代码如下,目前已通过Apollo插件实现了Token验证,但仍想明确原中间件失效的原因。

原JWT中间件代码

const jwt = require('jsonwebtoken');
const { GraphQLError } = require('graphql');

module.exports = async ({ req }) => {
  console.log('hello');
  const authHeader = req.headers.authorization || '';
  if (!authHeader.startsWith('Bearer ')) {
    throw new GraphQLError('Authorization header must be provided and must be Bearer token');
  }

  const token = authHeader.split('Bearer ')[1];
  if (!token) {
    throw new GraphQLError('Authentication token must be provided');
  }

  try {
    const user = jwt.verify(token, process.env.JWT_SECRET_KEY);
    return { user };
  } catch (err) {
    console.log(err);
    throw new GraphQLError('Invalid/Expired token');
  }
};

原服务器配置

const express = require('express')
const app = express()
const { ApolloServer } = require('@apollo/server');
const { startStandaloneServer } = require('@apollo/server/standalone')
const { ApolloServerPluginDrainHttpServer } = require('@apollo/server/plugin/drainHttpServer');
const { schema } = require('./schema.js');
const { resolvers } = require('./resolvers.js');
const http = require('http');
const cors = require('cors');
const { json } = require('body-parser');
const { expressMiddleware } = require('@apollo/server/express4');
const mongoose = require('mongoose')
const jwtmiddleware = require('./middleware/auth.js')
require('dotenv').config(); 

app.use(express.json())
app.use(express.urlencoded({ extended: false }))

const start = async() => {
  await mongoose.connect('mongodb://localhost:27017/SamichayUsers',{
      useNewUrlParser: true,
      useUnifiedTopology: true
  });
}

const httpServer = http.createServer(app);
const server = new ApolloServer({
  schema,
  resolvers,
  plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});

const startServer = async () => {
  await startStandaloneServer(server, {
    listen: { port: 5552 },
    context: jwtmiddleware
  });

  app.use(
      '/graphql',
      cors(),
      json(), 
      expressMiddleware(server, {
        context: async ({ req }) => ({ token: req.headers.token }),
      })
    );

  app.listen(5551, () => {
    console.log('Connesso alla porta: 5551');
  });
}


start()
startServer();

module.exports = {app}

测试查询Resolver

UpdateUserName: async (parent, { _id, newName }, context) => {
  console.log(context);
  const user = await User.findByIdAndUpdate(_id, { userName: newName },);
  return user;
}

Sandbox请求截图

Sandbox请求截图

已生效的插件实现

const jwt = require('jsonwebtoken');
require('dotenv').config(); 

const jwtmiddleware = {
  async requestDidStart(requestContext) {
    try {
      const operationType = requestContext.request.operationName;
      // 跳过登录、注册及Sandbox的自省查询
      if (['IntrospectionQuery', 'LogUser', 'CreateUser', 'CheckUserIsRegistered'].includes(operationType)) {
        return;
      }
      const headers = requestContext.request.http.headers;
      const authHeader = headers.get('authorization');
      if (authHeader) {
        const token = authHeader.split(' ')[1];
        jwt.verify(token, process.env.JWT_SECRET_KEY, (err, decoded) => {
          if (err) {
            throw new AuthenticationError('Invalid token');
          }
          console.log('Token verified: ', decoded);
        });
      } else {
        throw new AuthenticationError('Authorization header not present');
      }
    } catch (error) {
      console.error('Error during token verification: ', error.message);
      throw error;
    }
  },
};

module.exports = jwtmiddleware;

插件在服务器中的配置:

const server = new ApolloServer({
  schema,
  resolvers,
  plugins: [ApolloServerPluginDrainHttpServer({ httpServer }), jwtmiddleware],
});

问题根源

你同时使用了startStandaloneServer和expressMiddleware两种Apollo Server启动方式,导致了冲突:

  • startStandaloneServer会启动一个独立的HTTP服务器(端口5552),并配置了你传入的context: jwtmiddleware,但这个服务器并未被你实际请求。
  • 之后你又通过app.use('/graphql', ...)给Express应用配置了另一个GraphQL端点(端口5551),这里的context被重写为仅返回token: req.headers.token,而你实际在Sandbox中请求的是这个端口5551的端点,所以原JWT中间件根本没有被调用。
  • 额外的app.listen(5551)和startStandaloneServer的listen会启动两个独立的服务器,属于冗余配置。

解决方案

只保留一种启动方式,推荐使用Express集成模式(适配你已有的Express中间件),移除startStandaloneServer:

修正后的服务器配置

const express = require('express')
const app = express()
const { ApolloServer } = require('@apollo/server');
const { ApolloServerPluginDrainHttpServer } = require('@apollo/server/plugin/drainHttpServer');
const { schema } = require('./schema.js');
const { resolvers } = require('./resolvers.js');
const http = require('http');
const cors = require('cors');
const { json } = require('body-parser');
const { expressMiddleware } = require('@apollo/server/express4');
const mongoose = require('mongoose')
const jwtmiddleware = require('./middleware/auth.js')
require('dotenv').config(); 

app.use(express.json())
app.use(express.urlencoded({ extended: false }))

const start = async() => {
  await mongoose.connect('mongodb://localhost:27017/SamichayUsers',{
      useNewUrlParser: true,
      useUnifiedTopology: true
  });
}

const httpServer = http.createServer(app);
const server = new ApolloServer({
  schema,
  resolvers,
  plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});

const startServer = async () => {
  // 先启动Apollo Server实例
  await server.start();

  // 配置Express的GraphQL端点,使用自定义JWT中间件作为context
  app.use(
      '/graphql',
      cors(),
      json(), 
      expressMiddleware(server, {
        context: jwtmiddleware,
      })
    );

  // 启动HTTP服务器
  await new Promise(resolve => httpServer.listen({ port: 5551 }, resolve));
  console.log('Connesso alla porta: 5551');
}

start()
startServer();

module.exports = {app}

验证说明

修改后,当你通过Sandbox访问http://localhost:5551/graphql时,原JWT中间件会被正确触发,你能看到hello日志,解析后的用户信息会被注入到Resolver的context.user中。

额外说明

  • startStandaloneServer适合快速启动独立的Apollo Server,无需额外Express中间件的场景;expressMiddleware适合与现有Express应用集成的场景,二者不要混用。
  • 插件方式能生效是因为插件挂载在Apollo Server实例上,不受启动方式影响,但context中间件是与启动方式绑定的,需要对应配置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 21:54:58