如何通过PassportJS实现Apollo GraphQL Server的接口认证?
在Apollo Server中结合PassportJS实现/graphql端点前置认证
我之前也踩过这个坑,核心是要理清Express中间件的执行顺序,以及如何把Passport认证后的用户信息传递给Apollo的上下文。下面一步步给你拆解:
1. 中间件的正确顺序
首先必须明确:bodyParser要在Passport认证中间件之前执行。原因有两个:
- Apollo Server处理GraphQL请求需要解析JSON格式的请求体,bodyParser是基础依赖;
- 哪怕你用的是Header传递认证凭证(比如JWT的Authorization头),先执行bodyParser也不会有问题,反而能保证请求体被正确解析,不影响后续流程。如果是需要读取请求体里的凭证(比如表单提交的token),Passport更是必须在bodyParser之后才能拿到解析后的数据。
2. 全局路由级别的认证(推荐方案)
如果想让所有访问/graphql的请求都先经过认证,最直接的方式是在Apollo Server挂载前,给/graphql路由添加Passport的认证中间件。给你一个完整的可运行示例:
const express = require('express'); const { ApolloServer, gql } = require('apollo-server-express'); const passport = require('passport'); const bodyParser = require('body-parser'); const JwtStrategy = require('passport-jwt').Strategy; const ExtractJwt = require('passport-jwt').ExtractJwt; const app = express(); // 第一步:解析请求体,必须放在最前面 app.use(bodyParser.json()); // 第二步:初始化Passport并配置认证策略(这里以JWT为例) const jwtOptions = { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: 'your-strong-secret-key' }; passport.use(new JwtStrategy(jwtOptions, (jwtPayload, done) => { // 这里写你的用户查询逻辑,比如从数据库根据payload里的id查找用户 User.findById(jwtPayload.id, (err, user) => { if (err) return done(err, false); if (user) return done(null, user); return done(null, false, { message: '用户不存在' }); }); })); app.use(passport.initialize()); // 第三步:给/graphql路由添加Passport认证中间件 // session: false 因为我们用的是无状态认证(比如JWT),不需要维护会话 app.use('/graphql', passport.authenticate('jwt', { session: false })); // 第四步:配置Apollo Server并挂载到Express const typeDefs = gql` type Query { currentUser: User } type User { id: ID! email: String! username: String! } `; const resolvers = { Query: { currentUser: (_, __, context) => { // 这里的context.user就是Passport认证后挂载到req上的用户对象 return context.user; } } }; const apolloServer = new ApolloServer({ typeDefs, resolvers, // 把Express的req对象传递到上下文,从而拿到认证后的user context: ({ req }) => ({ user: req.user }) }); apolloServer.applyMiddleware({ app }); app.listen(4000, () => { console.log(`🚀 Server running at http://localhost:4000${apolloServer.graphqlPath}`); });
3. 关键细节说明
- Passport的认证结果传递:当
passport.authenticate执行成功后,会自动把认证后的用户对象挂载到req.user上,我们只需要在Apollo的context配置里把这个req.user传递给解析器即可。 - 自定义认证失败响应:默认情况下,Passport认证失败会直接返回401状态码。如果你想让失败信息以GraphQL错误的形式返回,可以自定义认证回调:
然后在Apollo的上下文里抛出错误:app.use('/graphql', (req, res, next) => { passport.authenticate('jwt', { session: false }, (err, user, info) => { if (err) return next(err); if (!user) { // 把错误信息挂载到req上,后续在上下文里处理 req.authError = info?.message || '认证失败,请检查凭证'; } else { req.user = user; } // 继续执行后续中间件(Apollo的处理逻辑) next(); })(req, res, next); });context: ({ req }) => { if (req.authError) { throw new Error(req.authError); } return { user: req.user }; }
4. 局部认证(仅部分查询/突变需要认证)
如果不需要全局认证,只是某些解析器需要用户信息,可以不用在路由级别加passport.authenticate,而是在Apollo的context里手动调用Passport的认证逻辑,或者在解析器里检查req.user(前提是你已经初始化了Passport)。不过这种方式不如全局路由认证高效,因为每个请求都会进入Apollo的处理流程后才做认证检查。
内容的提问来源于stack exchange,提问作者cyberbeast
相关产品推荐
相关产品推荐

