如何将认证中间件传递至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
相关产品推荐
相关产品推荐

