因为在订阅的情况下 Req 和 Res 是未定义的,所以当您尝试记录上下文时它是未定义的。
为了使上下文可用,您需要更改您正在使用的保护以返回可以在连接变量中找到的上下文。
基本上总结一下:
- => req, res 用于 http/query & mutation
- => webSockets/订阅中使用的连接
现在要正确获取上下文,您必须准确执行以下步骤:
- 修改 App 模块文件以使用 GraphqlModuleImport
- 修改提取用户防护和身份验证防护(或您正在使用的任何防护)
为查询/变异和订阅情况返回数据。
- 使用订阅中的上下文接收数据。
- 在 Auth 服务中添加 jwtTokenPayload 提取器功能。
- 可选:Typescript 的辅助函数和 DTO。
1-细节:
GraphQLModule.forRootAsync({
//import AuthModule for JWT headers at graphql subscriptions
imports: [AuthModule],
//inject Auth Service
inject: [AuthService],
useFactory: async (authService: AuthService) => ({
debug: true,
playground: true,
installSubscriptionHandlers: true,
// pass the original req and res object into the graphql context,
// get context with decorator `@Context() { req, res, payload, connection }: GqlContext`
// req, res used in http/query&mutations, connection used in webSockets/subscriptions
context: ({ req, res, payload, connection }: GqlContext) => ({
req,
res,
payload,
connection,
}),
// subscriptions/webSockets authentication
typePaths: ["./**/*.graphql"],
resolvers: { ...resolvers },
subscriptions: {
// get headers
onConnect: (connectionParams: ConnectionParams) => {
// convert header keys to lowercase
const connectionParamsLowerKeys: Object = mapKeysToLowerCase(
connectionParams,
);
// get authToken from authorization header
let authToken: string | false = false;
const val = connectionParamsLowerKeys["authorization"];
if (val != null && typeof val === "string") {
authToken = val.split(" ")[1];
}
if (authToken) {
// verify authToken/getJwtPayLoad
const jwtPayload: JwtPayload = authService.getJwtPayLoad(
authToken,
);
// the user/jwtPayload object found will be available as context.currentUser/jwtPayload in your GraphQL resolvers
return {
currentUser: jwtPayload.username,
jwtPayload,
headers: connectionParamsLowerKeys,
};
}
throw new AuthenticationError("authToken must be provided");
},
},
definitions: {
path: join(process.cwd(), "src/graphql.classes.ts"),
outputAs: "class",
},
}),
}),
2-细节:
我的 getRequest 函数示例来自扩展 AuthGuard(jwt) 类的 ExtractUserGuard 类。
更改自:
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext().req;
return request;}
到这里:
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
// req used in http queries and mutations, connection is used in websocket subscription connections, check AppModule
const { req, connection } = ctx.getContext();
// if subscriptions/webSockets, let it pass headers from connection.context to passport-jwt
const requestData =
connection && connection.context && connection.context.headers
? connection.context
: req;
return requestData;
}
3- 现在您可以在解析器中获取此数据。
@Subscription("testSubscription")
@UseGuards(ExtractUserGuard)
async testSubscription(
@Context("connection") connection: any,
): Promise<JSONObject> {
const subTopic = `${Subscriptions_Test_Event}.${connection.context.jwtPayload.email}`;
console.log("Listening to the event:", subTopic);
return this.pubSub.asyncIterator(subTopic);
}
4- 要使用令牌获取 jwtPayload,请将以下函数添加到您的 AuthService。
getJwtPayLoad(token: string): JwtPayload {
const jwtPayload = this.jwtService.decode(token);
return jwtPayload as JwtPayload;
}
5-Helper 函数和 DTO 示例(我在项目中使用)
DTO:
export interface JwtPayload {
username?: string;
expiration?: Date;
}
export interface GqlContext {
req: Request;
res: Response;
payload?: JwtPayload;
// required for subscription
connection: any;
}
export interface ConnectionParams {
authorization: string;
}
辅助功能:
export function mapKeysToLowerCase(
inputObject: Record<string, any>,
): Record<string, any> {
let key;
const keys = Object.keys(inputObject);
let n = keys.length;
const newobj: Record<string, any> = {};
while (n--) {
key = keys[n];
newobj[key.toLowerCase()] = inputObject[key];
}
return newobj;
}