graphql - 使用 makeRemoteExecutableSchema 拼接安全订阅
问题描述
我们已经实现了模式拼接,其中 GraphQL 服务器从两个远程服务器获取模式并将它们拼接在一起。当我们只使用 Query 和 Mutations 时,一切都运行良好,但现在我们有一个用例,我们甚至需要缝合 Subscriptions,并且远程模式已经在其上实现了身份验证。
我们很难弄清楚如何通过网关将连接参数中收到的授权令牌从客户端传递到远程服务器。
这就是我们自省模式的方式:
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,
})
});
有什么方法可以实现这一点graphql-tools
吗?任何帮助表示赞赏。
解决方案
这是一个远程模式的工作示例,通过 webscoket 订阅,通过 http 进行查询和变异。它可以通过自定义标头(参数)进行保护,并在此示例中显示。
流动
客户端请求 ->context
通过读取req
或创建connection
(jwt 被解码并在上下文中创建用户对象)
-> 执行远程模式 ->link
被调用 ->link
通过操作拆分(wsLink
用于订阅、httpLink
查询和突变)-> wsLink 或httpLink 访问context
上面创建的 (=graphqlContext) -> wsLink 或 httpLinkcontext
用于为远程模式创建标头(在此示例中具有签名 jwt 的授权标头)。-> “订阅”或“查询或突变”被转发到远程服务器。
笔记
- 目前,ContextLink 对 WebsocketLink 没有任何影响。因此,
concat
我们应该创建原始的 ApolloLink,而不是 。 - 创建上下文时, checkout
connection
,不仅仅是req
. 如果请求是 websocket,前者将可用,并且它包含用户发送的元信息,如身份验证令牌。 - HttpLink 期望使用标准规范进行全局获取。因此,不要使用
node-fetch
,其规范不兼容(尤其是与打字稿)。相反,使用cross-fetch
.
const wsLink = new ApolloLink(operation => {
// This is your context!
const context = operation.getContext().graphqlContext
// Create a new websocket link per request
return new WebSocketLink({
uri: "<YOUR_URI>",
options: {
reconnect: true,
connectionParams: { // give custom params to your websocket backend (e.g. to handle auth)
headers: {
authorization: jwt.sign(context.user, process.env.SUPER_SECRET),
foo: 'bar'
}
},
},
webSocketImpl: ws,
}).request(operation)
// Instead of using `forward()` of Apollo link, we directly use websocketLink's request method
})
const httpLink = setContext((_graphqlRequest, { graphqlContext }) => {
return {
headers: {
authorization: jwt.sign(graphqlContext.user, process.env.SUPER_SECRET),
},
}
}).concat(new HttpLink({
uri,
fetch,
}))
const link = split(
operation => {
const definition = getMainDefinition(operation.query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink, // <-- Executed if above function returns true
httpLink, // <-- Executed if above function returns false
)
const schema = await introspectSchema(link)
const executableSchema = makeRemoteExecutableSchema({
schema,
link,
})
const server = new ApolloServer({
schema: mergeSchemas([ executableSchema, /* ...anotherschemas */]),
context: ({ req, connection }) => {
let authorization;
if (req) { // when query or mutation is requested by http
authorization = req.headers.authorization
} else if (connection) { // when subscription is requested by websocket
authorization = connection.context.authorization
}
const token = authorization.replace('Bearer ', '')
return {
user: getUserFromToken(token),
}
},
})
推荐阅读
- python - # 5次,每个句子用一个空格隔开
- python - 在 try 语句中输入空白值
- java - 有没有办法在java(Android sdk)中将带有hypen的字符串转换为整数
- java - 我可以仅对 wro4j 中的 TS 文件启用打字稿处理吗?
- javascript - 如何通过图标验证更改复选框
- javascript - 获取解析错误:意外的令牌,预期的“,”在 Vue
- php - WordPress Rest API 响应计数
- ionic-framework - Ionic 和 phonegap 条码扫描仪应用程序在 android 上崩溃
- asp.net - aspx dropdownList 不维护所选值
- javascript - 在 Javascript 中显示基于数字范围的图像