我正试图从我使用IAM授权器保护的lambda函数调用AWS ApiGateway HTTP端点,但我一生都无法从lambda函数中获得任何东西。
我已经用Postman测试了端点,当我选择";AWS签名";作为Authorization类型,并放入我的本地凭据中,因此如何设置端点不是问题。这一定是我如何发送Lambda的请求的问题。额外的挑战是将头添加到GraphQL API请求中。
这就是我的lambda函数的样子:
import { ApolloServer } from 'apollo-server-lambda';
import { APIGatewayProxyEvent, Callback, Context } from 'aws-lambda';
import { ApolloGateway, RemoteGraphQLDataSource } from '@apollo/gateway';
import aws4 from 'aws4';
const userServiceUrl = process.env.USER_SERVICE_URL;
const {hostname, pathname} = new URL(userServiceUrl);
class AuthenticatedDataSource extends RemoteGraphQLDataSource {
willSendRequest({request}) {
console.log('request is: ', request);
const opts: Record<string, any> = {
service: 'execute-api',
region: process.env.AWS_REGION,
host: hostname,
path: pathname,
body: JSON.stringify({query: request.query}),
method: 'POST'
}
aws4.sign(opts);
console.log('opts are: ', opts);
request.http.headers.set('X-Amz-Date', opts.headers['X-Amz-Date']);
request.http.headers.set('Authorization', opts.headers['Authorization']);
request.http.headers.set('X-Amz-Security-Token', opts.headers['X-Amz-Security-Token']);
}
}
无论我尝试什么,我总是会得到一个403禁止的错误,并且请求永远不会到达授权人后面的实际端点。我试过删除body,我试过将我的本地凭据硬编码到aws4调用中,但都不起作用。我的直觉是,我的签名电话在某种程度上是错误的,但当我将其与我在互联网上发现的几个例子进行比较时,我看不出有什么明显的错误。
任何能为我指明正确方向的资源都将不胜感激。我发现的大多数例子都是针对前端的,所以我知道这可能是我的错误。
willSendRequest
函数不是签署请求的最佳位置,因为在调用willSendRequest
后,apollo服务器可以修改请求对象。相反,您应该实现一个自定义获取并将其传递给RemoteGraphQLDataSource
构造函数,以确保在发送最终请求之前对其进行签名。
您的自定义GraphQLDataSource
和自定义提取将是类似的东西:
import { Request, RequestInit, Response, fetch, Headers } from "apollo-server-env";
import aws4 from 'aws4';
import { RemoteGraphQLDataSource } from '@apollo/gateway';
class AuthenticatedDataSource extends RemoteGraphQLDataSource {
public constructor(
url: string,
) {
super({
url: url,
fetcher: doFetch,
});
}
async doFetch(
input?: string | Request | undefined,
init?: RequestInit | undefined
): Promise<Response> {
const url = new URL(input as string);
const opts: Record<string, any> = {
service: 'execute-api',
region: process.env.AWS_REGION,
host: url.hostname,
path: url.pathname,
body: init?.body,
method: init?.method
}
aws4.sign(opts);
init.headers.set('X-Amz-Date', opts.headers['X-Amz-Date']);
init.headers.set('Authorization', opts.headers['Authorization']);
init.headers.set('X-Amz-Security-Token', opts.headers['X-Amz-Security-Token']);
const response = await fetch(input, init);
return response;
}
}
为了繁荣,这就是我最终所做的,并且工作完美(非常感谢Glen Thomas!(
import { Request, RequestInit, Response, fetch } from "apollo-server-env";
import { ApolloServer } from 'apollo-server-lambda';
import { APIGatewayProxyEvent, Callback, Context } from 'aws-lambda';
import { ApolloGateway, RemoteGraphQLDataSource } from '@apollo/gateway';
import aws4 from 'aws4';
const userServiceUrl = process.env.USER_SERVICE_URL;
async function doFetch(
input?: Request | string,
init?: RequestInit
): Promise<Response> {
const urlString = typeof input === 'string' ? input : input.url;
const url = new URL(urlString);
const opts: Record<string, any> = {
service: 'execute-api',
region: process.env.AWS_REGION,
host: url.hostname,
path: url.pathname,
body: init?.body,
method: init?.method
}
aws4.sign(opts);
init.headers = opts.headers;
const response = await fetch(input, init);
return response;
}
class AuthenticatedDataSource extends RemoteGraphQLDataSource {
constructor(url: string) {
super({
url,
fetcher: doFetch
})
}
}
const server = new ApolloServer({
gateway: new ApolloGateway({
serviceList: [
{ name: 'users', url: userServiceUrl }
],
buildService({url}) {
return new AuthenticatedDataSource(url)
}
}),
subscriptions: false,
introspection: true,
playground: true,
});
export const handler = (event: APIGatewayProxyEvent, context: Context, callback: Callback) => {
console.log('event is: ', JSON.stringify(event, null, 2))
return server.createHandler({
cors: {
origin: '*'
}
})(event, context, callback);
}
实际上,willSendRequest
工作得很好,而且为了简单起见,它是首选。对于您希望通过简单的设置签署特定数据源的请求的情况。
记录的尝试失败的原因是请求签名需要精确:Path必须同时包含路径&查询参数。CCD_ 6也被剥离,因为相应的AWS签名错误指示主机没有。
GET请求
willSendRequest(request: RequestOptions) {
const query = request.params ? `?${request.params.toString()}` : "";
const opts: Record<string, any> = {
service: "execute-api",
region: process.env.AWS_REGION,
host: this.baseURL?.replace("https://", ""),
path: request.path + query,
method: request.method,
};
sign(opts);
request.headers.set("X-Amz-Date", opts.headers["X-Amz-Date"]);
request.headers.set("Authorization", opts.headers["Authorization"]);
request.headers.set(
"X-Amz-Security-Token",
opts.headers["X-Amz-Security-Token"]
);
}
POST请求
片段示例,来自另一个具有主体的JS lambda(而不是apollo graphql(;不确定apollo是否会自动解码身体,不管这是一个很好的例子。
if (Object.hasOwnProperty.call(request, 'body')) {
const { data, encoding } = request.body;
const buffer = Buffer.from(data, encoding);
const decodedBody = buffer.toString('utf8');
if (decodedBody !== '') {
normalisedRequest.body = decodedBody;
normalisedRequest.headers = { 'content-type': request.headers['content-type'][0].value };
}
}