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

如何将认证中间件传递至Apollo GraphQL服务器?

如何在Apollo GraphQL Server中实现认证(替代REST风格中间件)及依赖版本说明

一、依赖包版本梳理

你代码中同时引入了apollo-server(Apollo Server v3及更早版本)和@apollo/server/standalone(Apollo Server v4+版本),这会导致版本冲突。Apollo在v4版本将核心包名从apollo-server改为@apollo/server,且v3已进入维护状态,建议统一升级到v4:

  • 卸载旧包:npm uninstall apollo-server
  • 安装v4核心依赖:npm install @apollo/server graphql

二、认证逻辑改造(替代REST中间件)

REST风格的(req,res,next)中间件不适用于Apollo Server默认的standalone运行模式,Apollo通过**上下文(Context)**机制在解析器间共享认证信息,这是GraphQL场景下的标准认证方式。

1. 改造认证工具函数

提取原auth中间件的核心逻辑,封装为独立的用户ID提取函数:

// Middleware/Auth.js
const jwt = require('jsonwebtoken');

module.exports = (authHeader) => {
  try {
    if (!authHeader) {
      throw new Error('Not Authorized');
    }
    const token = authHeader.split(' ')[1];
    const decodedToken = jwt.verify(token, process.env.JWT_SECRET);
    if (!decodedToken) {
      throw new Error('Not Authorized');
    }
    return decodedToken.userId;
  } catch (err) {
    err.status = 401; // 建议用401(未授权)替代500(服务器错误)
    throw err;
  }
};

2. 配置Apollo Server上下文

在App.js中使用v4的startStandaloneServer启动服务,并在上下文函数中处理认证:

const mongoose = require('mongoose');
const dotenv = require('dotenv');
const { ApolloServer } = require('@apollo/server');
const { startStandaloneServer } = require('@apollo/server/standalone');
const typeDefs = require('./apolloGraphql/schema');
const resolvers = require('./apolloGraphql/resolvers');
const getUserId = require('./Middleware/Auth');

dotenv.config({ path: './config/.env' });

const server = new ApolloServer({
  typeDefs,
  resolvers,
  // 可选:自定义错误返回格式
  formatError: (err) => {
    return { message: err.message, statusCode: err.extensions?.statusCode || 500 };
  },
});

mongoose.connect(process.env.MONGOURI)
  .then(async () => {
    const { url } = await startStandaloneServer(server, {
      listen: { port: process.env.PORT },
      // 处理认证并生成上下文
      context: async ({ req }) => {
        try {
          const userId = getUserId(req.headers.authorization);
          return { userId };
        } catch (err) {
          // 若不需要强制所有请求认证,可返回空userId,在解析器中判断权限
          // 若需强制认证,直接抛出错误拦截请求
          throw err;
        }
      },
    });
    console.log(`Server is Running at ${url}`);
  })
  .catch((err) => {
    console.log(err);
  });

3. 在解析器中使用认证信息

解析器可通过第三个参数获取上下文里的userId,实现权限控制:

// 示例解析器(apolloGraphql/resolvers.js)
module.exports = {
  Query: {
    getCurrentUser: (parent, args, context) => {
      if (!context.userId) {
        throw new Error('请先登录');
      }
      // 用context.userId查询用户数据
      return User.findById(context.userId);
    },
  },
};

可选:复用Express中间件方案

如果需要和现有REST接口复用同一个auth中间件,可以将Apollo Server挂载到Express上:

// 额外安装依赖:npm install @apollo/server express graphql cors
const express = require('express');
const cors = require('cors');
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const auth = require('./Middleware/Auth');

const app = express();
app.use(cors());
app.use(express.json());
// 挂载原REST风格认证中间件
app.use(auth);

const server = new ApolloServer({ typeDefs, resolvers });

mongoose.connect(process.env.MONGOURI)
  .then(async () => {
    await server.start();
    // 将Apollo挂载到/graphql路径
    app.use('/graphql', expressMiddleware(server, {
      context: ({ req }) => ({ userId: req.userId }), // 从req中获取中间件设置的userId
    }));
    app.listen(process.env.PORT, () => {
      console.log(`Server is Listening at PORT ${process.env.PORT}`);
    });
  })
  .catch(err => console.log(err));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 04:03:13