Node.js 如何从lambda调用IAM授权的AWS ApiGateway端点?
我正在尝试从lambda函数调用AWS ApiGateway HTTP端点,我已经使用IAM授权程序对其进行了保护,但是我无法从lambda函数中获得任何工作 我已经用Postman测试了端点,当我选择“AWS签名”作为授权类型并放入本地凭据时,可以确认它是否有效,因此端点的设置方式没有问题。这一定是我如何从Lambda发送请求的问题。另一个挑战是将头添加到GraphQLAPI请求中 这就是我的lambda函数的样子:Node.js 如何从lambda调用IAM授权的AWS ApiGateway端点?,node.js,aws-lambda,aws-api-gateway,amazon-iam,apollo-server,Node.js,Aws Lambda,Aws Api Gateway,Amazon Iam,Apollo Server,我正在尝试从lambda函数调用AWS ApiGateway HTTP端点,我已经使用IAM授权程序对其进行了保护,但是我无法从lambda函数中获得任何工作 我已经用Postman测试了端点,当我选择“AWS签名”作为授权类型并放入本地凭据时,可以确认它是否有效,因此端点的设置方式没有问题。这一定是我如何从Lambda发送请求的问题。另一个挑战是将头添加到GraphQLAPI请求中 这就是我的lambda函数的样子: import { ApolloServer } from 'apollo-s
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']);
}
}
从“阿波罗服务器lambda”导入{ApolloServer};
从“aws lambda”导入{APIGatewayProxyEvent,回调,Context};
从“@apollo/gateway”导入{ApolloGateway,RemoteGraphQLDataSource};
从“aws4”导入aws4;
const userServiceUrl=process.env.USER\u SERVICE\u URL;
const{hostname,pathname}=新URL(userServiceUrl);
类AuthenticatedDataSource扩展了RemoteGraphQLDataSource{
willSendRequest({request}){
log('请求为:',请求);
常数选项:记录={
服务:“执行api”,
地区:process.env.AWS_地区,
主机名:,
路径:路径名,
body:JSON.stringify({query:request.query}),
方法:“发布”
}
aws4.标志(opts);
log('opts为:',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禁止的错误,并且请求从未到达授权者后面的实际端点。我尝试过移除尸体,我尝试过将我的本地凭证硬编码到aws4调用中,但都不起作用。我的直觉是,我的签名电话不知何故是错的,但当我把它与我在互联网上找到的几个例子进行比较时,我看不出有什么明显的错误
任何能为我指明正确方向的资源都将不胜感激。我发现的大多数示例都是前端特定的,因此我知道这可能会引导我出错。调用
willSendRequest
函数不是签署请求的最佳位置,因为apollo server可以在调用willSendRequest
后修改请求对象。
相反,您应该实现一个自定义获取,并将其传递给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;
}
}
import{Request,RequestInit,Response,fetch,Headers}来自“apollo服务器环境”;
从“aws4”导入aws4;
从“@apollo/gateway”导入{RemoteGraphQLDataSource};
类AuthenticatedDataSource扩展了RemoteGraphQLDataSource{
公共构造函数(
url:string,
) {
超级({
url:url,
fetcher:doFetch,
});
}
异步doFetch(
输入?:字符串|请求|未定义,
init?:RequestInit |未定义
):承诺{
const url=新url(作为字符串输入);
常数选项:记录={
服务:“执行api”,
地区:process.env.AWS_地区,
主机:url.hostname,
路径:url.pathname,
body:init?.body,
方法:init?方法
}
aws4.标志(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=等待获取(输入,初始化);
返回响应;
}
}
为了繁荣,我最终做到了这一点,并且工作完美无瑕(非常感谢格伦·托马斯!)
import{Request,RequestInit,Response,fetch}来自“阿波罗服务器环境”;
从'apollo server lambda'导入{apollo server};
从“aws lambda”导入{APIGatewayProxyEvent,回调,Context};
从“@apollo/gateway”导入{ApolloGateway,RemoteGraphQLDataSource};
从“aws4”导入aws4;
const userServiceUrl=process.env.USER\u SERVICE\u URL;
异步函数doFetch(
输入?:请求|字符串,
init?:RequestInit
):承诺{
const urlString=typeof input=='string'?input:input.url;
const url=新url(urlString);
常数选项:记录={
服务:“执行api”,
地区:process.env.AWS_地区,
主机:url.hostname,
路径:url.pathname,
body:init?.body,
方法:init?方法
}
aws4.标志(opts);
init.headers=opts.headers;
const response=等待获取(输入,初始化);
返回响应;
}
类AuthenticatedDataSource扩展了RemoteGraphQLDataSource{
构造函数(url:string){
超级({
网址,
取数器:doFetch
})
}
}
const server=新服务器({
网关:新阿波罗网关({
服务列表:[
{name:'users',url:userServiceUrl}
],
构建服务({url}){
返回新的AuthenticatedDataSource(url)
}
}),
订阅:false,
反省:没错,
操场:没错,
});
导出常量处理程序=(事件:APIGatewayProxyEvent,上下文:上下文,回调:回调)=>{
log('event is:',JSON.stringify(event,null,2))
return server.createHandler({
cors:{
来源:'*'
}
})(事件、上下文、回调);
}
非常感谢,我终于让它工作了!在将其更改为此之后,我能够通过覆盖所有标题而不是仅覆盖我设置的3个标题使其工作。(请参阅我的答案了解我所做的更改)对于发现此问题的任何人来说,另一个建议是始终使用通配符IAM策略进行测试,然后在其正常工作后使其更加严格。我想我可能是因为使用了我认为有效的资源限定符而把事情搞砸了。
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);
}