Apollo Server中WebSocket模式下上下文与Token获取问题
问题分析与解决方案
核心问题在于HTTP请求和WebSocket连接的上下文处理逻辑不一致,且存在变量初始化顺序、Token解析后未传递到上下文的问题,导致Resolver无法获取用户Token信息。以下是具体修复步骤和代码:
关键问题点
- PubSub初始化顺序错误:
useServer中引用pub时,pub还未定义,导致上下文无法获取PubSub实例 - HTTP中间件未传递用户信息:解析Token后未将
currentUser挂载到req上,ApolloServer的上下文无法拿到用户数据 - WebSocket上下文未解析Token:仅打印请求头但未从握手请求中提取并解析Authorization Token
- 上下文结构不统一:HTTP和WebSocket返回的上下文字段不一致,Resolver无法统一获取
user
修正后的代码
server.js
import { ApolloServer } from '@apollo/server'; import { createServer } from 'http'; import { expressMiddleware } from '@apollo/server/express4'; import express from 'express'; import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'; import { makeExecutableSchema } from '@graphql-tools/schema'; import { WebSocketServer } from 'ws'; import { useServer } from 'graphql-ws/lib/use/ws'; import { resolvers, typeDefs } from './src/graphql/modules/index.js'; import jwt from 'jsonwebtoken'; import cors from 'cors'; import bodyParser from 'body-parser'; import { PubSub } from 'graphql-subscriptions'; const app = express(); const httpServer = createServer(app); const schema = makeExecutableSchema({ typeDefs, resolvers }); // 提前初始化PubSub,确保useServer能引用到 const pub = new PubSub(); const wsServer = new WebSocketServer({ server: httpServer, path: '/graphql', }); const serverCleanup = useServer( { schema, context: async (ctx) => { // 从WebSocket握手请求头中获取Authorization const authHeader = ctx.extra.request.headers.authorization; let user = null; if (authHeader) { const token = authHeader.split(" ")[1]; if (token && token !== "null") { try { // 解析Token(如果需要验证签名用jwt.verify,decode仅解析不验证) user = jwt.decode(token, process.env.SECRET); } catch (e) { console.error('WebSocket Token解析失败:', e); } } } // 统一上下文结构:返回user和pub return { user, pub }; } }, wsServer ); const server = new ApolloServer({ schema, context: ({ req }) => { // 从req中拿到中间件解析的currentUser,统一上下文结构 return { user: req.currentUser, pub }; }, plugins: [ ApolloServerPluginDrainHttpServer({ httpServer }), { async serverWillStart() { return { async drainServer() { await serverCleanup.dispose(); }, }; }, }, ], }); await server.start(); // 修复HTTP请求的Token解析中间件 app.all('/graphql', function (req, res, next){ const authHeader = req.headers.authorization; req.currentUser = null; if (authHeader) { const token = authHeader.split(" ")[1] || ''; if (token && token !== "null") { try { // 解析Token,赋值给req.currentUser req.currentUser = jwt.decode(token, process.env.SECRET); } catch(e) { console.error('HTTP Token解析失败:', e); } } } next(); }); app.use('/graphql', cors(), bodyParser.json(), expressMiddleware(server)); httpServer.listen(4000, () => { console.log('服务器运行在 http://localhost:4000/graphql'); });
resolver.js
import { GraphQLError } from 'graphql'; const resolvers = { Query : { notificacoesUsuario(_, args, contextValue){ console.log(contextValue); if (!contextValue.user){ throw new GraphQLError('用户未认证', { extensions: { code: 'UNAUTHENTICATED', http: { status: 401 }, }, }); } return notificacaoDb.getByReceptor(contextValue.user.id); }, visualizarNotificacao(_, { id }){ return notificacaoDb.visualizarNotificacao(id); } }, Notificacao: { data(args){ return args.data.toISOString(); }, }, Subscription: { notificacaoCriada: { subscribe: (_, {}, context) => context.pub.asyncIterator(['NOTIFICACAO_CRIADA']) } } }; export default { resolvers };
修复说明
- PubSub初始化顺序:将
pub的定义移到useServer之前,确保WebSocket上下文能获取到PubSub实例 - 统一上下文结构:HTTP和WebSocket的上下文都返回
{ user, pub },让Resolver无需区分请求类型 - WebSocket Token解析:从握手请求的
ctx.extra.request.headers中提取Authorization头,解析后得到用户信息 - HTTP中间件修复:将解析后的
currentUser挂载到req上,ApolloServer的上下文直接引用该值 - Resolver修正:确保
contextValue.user能正确获取到用户信息,同时补全GraphQLError的导入(原代码中未导入会报错)
内容的提问来源于stack exchange,提问作者Werbeth Pereira Veras
相关产品推荐
相关产品推荐

