NestJS GraphQL订阅在含Guard的Resolver中失效,报logIn未定义错误
NestJS GraphQL订阅鉴权失败问题解决
问题根源
订阅基于WebSocket协议,和HTTP请求的上下文结构完全不同。常规Guard如果只处理HTTP请求的request对象,在订阅场景下会因为获取不到有效上下文而抛出Cannot read properties of undefined (reading 'logIn')错误——本质是Guard中依赖的某个对象(比如HTTP请求实例)未被正确初始化。
解决步骤
1. 修改Guard适配双上下文
更新Guard代码,同时支持HTTP请求和WebSocket订阅的上下文解析,从对应位置提取认证凭证(比如JWT Token):
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; import { AuthService } from './auth.service'; // 替换为你的认证服务 @Injectable() export class AuthGuard implements CanActivate { constructor(private readonly authService: AuthService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const gqlCtx = GqlExecutionContext.create(context).getContext(); let token: string; // 处理HTTP请求(查询/突变) if (gqlCtx.req) { token = gqlCtx.req.headers.authorization?.split(' ')[1]; } // 处理WebSocket订阅 else if (gqlCtx.connectionParams) { token = gqlCtx.connectionParams.authorization?.split(' ')[1]; } // 无有效凭证直接拒绝 else { return false; } if (!token) return false; try { // 验证Token并挂载用户信息到上下文 const user = await this.authService.verifyToken(token); gqlCtx.user = user; return true; } catch (err) { return false; } } }
2. 客户端传递订阅连接参数
确保客户端在建立WebSocket连接时,将认证凭证放在connectionParams中(以Apollo Client为例):
import { ApolloClient, InMemoryCache, split, HttpLink } from '@apollo/client'; import { getMainDefinition } from '@apollo/client/utilities'; import { WebSocketLink } from '@apollo/client/link/ws'; const httpLink = new HttpLink({ uri: 'http://localhost:3000/graphql' }); const wsLink = new WebSocketLink({ uri: 'ws://localhost:3000/graphql', options: { reconnect: true, // 传递认证Token connectionParams: { authorization: `Bearer ${localStorage.getItem('your_token_key')}`, }, }, }); // 根据操作类型自动切换HTTP/WSS链路 const splitLink = split( ({ query }) => { const definition = getMainDefinition(query); return ( definition.kind === 'OperationDefinition' && definition.operation === 'subscription' ); }, wsLink, httpLink, ); const client = new ApolloClient({ link: splitLink, cache: new InMemoryCache(), });
3. 配置GraphQL模块的订阅上下文
在NestJS的GraphQL模块配置中,确保订阅上下文能正确传递connectionParams:
import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ GraphQLModule.forRoot<ApolloDriverConfig>({ driver: ApolloDriver, autoSchemaFile: join(process.cwd(), 'src/schema.gql'), subscriptions: { 'graphql-ws': { path: '/graphql', // 可选:提前在连接时做初步校验 onConnect: (context) => { if (!context.connectionParams?.authorization) { throw new Error('认证凭证缺失'); } return context; }, }, }, // 统一处理HTTP和订阅的上下文 context: ({ req, connection }) => { return connection ? { connectionParams: connection.context } : { req }; }, }), ], }) export class AppModule {}
验证
完成上述配置后,重新启动服务,客户端发起订阅请求时会自动携带认证凭证,Guard能正确解析上下文并完成鉴权,订阅即可正常工作。
内容的提问来源于stack exchange,提问作者Israel Okorafor
相关产品推荐
相关产品推荐

