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请求截图

已生效的插件实现
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
相关产品推荐
相关产品推荐

