Graphql 使用MakeMoteExecutableSchema缝合安全订阅

Graphql 使用MakeMoteExecutableSchema缝合安全订阅,graphql,apollo,apollo-server,graphql-subscriptions,graphql-tools,Graphql,Apollo,Apollo Server,Graphql Subscriptions,Graphql Tools,我们已经实现了模式拼接,GraphQL服务器从两个远程服务器获取模式并将它们缝合在一起。当我们只处理查询和突变时,一切都很好,但现在我们有了一个用例,我们甚至需要缝合订阅,远程模式已经通过它实现了auth 我们很难弄清楚如何通过网关将connectionParams中接收到的授权令牌从客户端传递到远程服务器 这就是我们反思模式的方式: API网关代码: const getLink = async(): Promise<ApolloLink> => { const http =

我们已经实现了模式拼接,GraphQL服务器从两个远程服务器获取模式并将它们缝合在一起。当我们只处理查询和突变时,一切都很好,但现在我们有了一个用例,我们甚至需要缝合订阅,远程模式已经通过它实现了auth

我们很难弄清楚如何通过网关将connectionParams中接收到的授权令牌从客户端传递到远程服务器

这就是我们反思模式的方式:

API网关代码:

const getLink = async(): Promise<ApolloLink> => {
const http = new HttpLink({uri: process.env.GRAPHQL_ENDPOINT, fetch:fetch})

const link = setContext((request, previousContext) => {
    if (previousContext
        && previousContext.graphqlContext
        && previousContext.graphqlContext.request
        && previousContext.graphqlContext.request.headers
        && previousContext.graphqlContext.request.headers.authorization) {
        const authorization = previousContext.graphqlContext.request.headers.authorization;
        return {
            headers: {
                authorization
            }
        }
    }
    else {
        return {};
    }
}).concat(http);

const wsLink: any = new WebSocketLink(new SubscriptionClient(process.env.REMOTE_GRAPHQL_WS_ENDPOINT, {
    reconnect: true,
    // There is no way to update connectionParams dynamically without resetting connection
    // connectionParams: () => { 
    //     return { Authorization: wsAuthorization }
    // }
}, ws));


// Following does not work
const wsLinkContext = setContext((request, previousContext) => {
    let authToken = previousContext.graphqlContext.connection && previousContext.graphqlContext.connection.context ? previousContext.graphqlContext.connection.context.Authorization : null
    return {
        context: {
            Authorization: authToken
        }
    }
}).concat(<any>wsLink);

const url = split(({query}) => {
    const {kind, operation} = <any>getMainDefinition(<any>query);
    return kind === 'OperationDefinition' && operation === 'subscription'
},
wsLinkContext,
link)

return url;
}

const getSchema = async (): Promise < GraphQLSchema > => {
  const link = await getLink();
  return makeRemoteExecutableSchema({
    schema: await introspectSchema(link),
    link,
  });
}
const linkSchema = `
  extend type UserPayload {
    user: User
  }
`;
const schema: any = mergeSchemas({
  schemas: [linkSchema, getSchema],
});
const server = new GraphQLServer({
  schema: schema,
  context: req => ({
    ...req,
  })
});
const getLink=async():Promise=>{
consthttp=newhttplink({uri:process.env.GRAPHQL_ENDPOINT,fetch:fetch})
const link=setContext((请求,以前的上下文)=>{
如果(以前的上下文)
&&previousContext.graphqlContext
&&previousContext.graphqlContext.request
&&previousContext.graphqlContext.request.headers
&&previousContext.graphqlContext.request.headers.authorization){
const authorization=previousContext.graphqlContext.request.headers.authorization;
返回{
标题:{
授权
}
}
}
否则{
返回{};
}
}).concat(http);
const wsLink:any=新的WebSocketLink(新的SubscriptionClient(process.env.REMOTE\u GRAPHQL\u WS\u端点{
对,,
//如果不重置连接,就无法动态更新connectionParams
//connectionParams:()=>{
//返回{Authorization:wsAuthorization}
// }
},ws));
//下面的方法不起作用
const wsLinkContext=setContext((请求,以前的上下文)=>{
让authToken=previousContext.graphqlContext.connection&&previousContext.graphqlContext.connection.context?previousContext.graphqlContext.connection.context.Authorization:null
返回{
背景:{
授权:authToken
}
}
}).concat(wsLink);
常量url=split({query})=>{
const{kind,operation}=getMainDefinition(查询);
返回种类=='OperationDefinition'&&operation=='subscription'
},
wsLinkContext,
链接)
返回url;
}
const getSchema=async():Promise=>{
const link=wait getLink();
返回makeRemoteExecutableSchema({
模式:等待内省模式(链接),
链接
});
}
常量链接模式=`
扩展类型用户负载{
用户:用户
}
`;
const schema:any=mergeSchemas({
模式:[linkSchema,getSchema],
});
const server=new GraphQLServer({
schema:schema,
上下文:req=>({
…请求,
})
});

使用
graphql工具
有什么方法可以实现这一点吗?感谢您的帮助。

我有一个可行的解决方案:不要为整个应用程序创建一个
SubscriptionClient
实例。相反,我正在为每个到代理服务器的连接创建客户端:

server.start({
    port: 4000,
    subscriptions: {
      onConnect: (connectionParams, websocket, context) => {
        return {
          subscriptionClients: {
            messageService: new SubscriptionClient(process.env.MESSAGE_SERVICE_SUBSCRIPTION_URL, {
              connectionParams,
              reconnect: true,
            }, ws)
          }
        };
      },
      onDisconnect: async (websocket, context) => {
        const params = await context.initPromise;
        const { subscriptionClients } = params;
        for (const key in subscriptionClients) {
          subscriptionClients[key].close();
        }
      }
    }
  }, (options) => console.log('Server is running on http://localhost:4000'))
如果您想要有更多的远程模式,只需在
subscriptionClients
映射中创建更多的
SubscriptionClient
实例即可

要在远程架构中使用这些客户端,您需要做两件事:

  • 在上下文中公开它们:

    const server = new GraphQLServer({
      schema,
      context: ({ connection }) => {
        if (connection && connection.context) {
          return connection.context;
        }
      }
    });
    
  • 使用自定义链接实现而不是WsLink

    (operation, forward) => {
        const context = operation.getContext();
        const { graphqlContext: { subscriptionClients } } = context;
        return subscriptionClients && subscriptionClients[clientName] && subscriptionClients[clientName].request(operation);
    };
    
  • 这样,整个连接参数将被传递到远程服务器

    整个示例可在此处找到:

    免责声明:


    代码不是我的,因为@josephktcheung提供了一个例子,超过了我。我只是帮了点忙。以下是原始讨论:

    这是一个远程模式的工作示例,通过WebCoket进行订阅,通过http进行查询和变异。它可以通过自定义头(参数)进行保护,如本例所示

    流量

    客户请求 ->
    context
    是通过读取
    req
    connection
    创建的(jwt被解码并在上下文中创建用户对象)
    ->执行远程模式 ->调用
    链接
    ->
    link
    按操作拆分(
    wsLink
    用于订阅,
    httpLink
    用于查询和转换) ->wsLink或httpLink访问上面创建的上下文(=graphqlContext) ->wsLink或httpLink使用
    context
    为远程架构创建头(在本例中为带有签名的jwt的授权头)。 ->“订阅”或“查询或变异”被转发到远程服务器

    注意

  • 目前,ContextLink对WebsocketLink没有任何影响。因此,我们应该创建原始链接,而不是
    concat
  • 创建上下文时,签出
    连接
    ,而不仅仅是
    请求
    。如果请求是websocket,并且包含用户发送的元信息(如身份验证令牌),则前者可用
  • HttpLink要求使用标准规范进行全局获取。因此,不要使用规范不兼容的
    节点获取
    (尤其是与typescript不兼容)。相反,请使用
    交叉提取
    const wsLink=new ApolloLink(操作=>{
    //这就是你的背景!
    const context=operation.getContext().graphqlContext
    //根据请求创建新的websocket链接
    返回新的WebSocketLink({
    uri:“”,
    选项:{
    对,,
    connectionParams:{//为websocket后端提供自定义参数(例如,处理身份验证)
    标题:{
    授权:jwt.sign(context.user,process.env.SUPER_SECRET),
    福:“酒吧”
    }
    },
    },
    webSocketImpl:ws,
    }).请求(操作)
    //我们不使用Apollo链接的'forward()',而是直接使用websocketLink的请求方法
    })
    const httpLink=setContext((_graphqlRequest,{graphqlContext})=>{
    返回{
    标题:{
    授权:jwt.sign(graphqlContext.user,process.env.SUPER_SECRET),
    },
    }
    }).concat(新HttpLink)({
    乌里,
    取来
    }))
    常量链接=s