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

Apollo Server中WebSocket模式下上下文与Token获取问题

问题分析与解决方案

核心问题在于HTTP请求和WebSocket连接的上下文处理逻辑不一致,且存在变量初始化顺序、Token解析后未传递到上下文的问题,导致Resolver无法获取用户Token信息。以下是具体修复步骤和代码:

关键问题点

  1. PubSub初始化顺序错误:useServer中引用pub时,pub还未定义,导致上下文无法获取PubSub实例
  2. HTTP中间件未传递用户信息:解析Token后未将currentUser挂载到req上,ApolloServer的上下文无法拿到用户数据
  3. WebSocket上下文未解析Token:仅打印请求头但未从握手请求中提取并解析Authorization Token
  4. 上下文结构不统一: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 };

修复说明

  1. PubSub初始化顺序:将pub的定义移到useServer之前,确保WebSocket上下文能获取到PubSub实例
  2. 统一上下文结构:HTTP和WebSocket的上下文都返回{ user, pub },让Resolver无需区分请求类型
  3. WebSocket Token解析:从握手请求的ctx.extra.request.headers中提取Authorization头,解析后得到用户信息
  4. HTTP中间件修复:将解析后的currentUser挂载到req上,ApolloServer的上下文直接引用该值
  5. Resolver修正:确保contextValue.user能正确获取到用户信息,同时补全GraphQLError的导入(原代码中未导入会报错)

内容的提问来源于stack exchange,提问作者Werbeth Pereira Veras

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 09:47:07