使用 AWS Lambda 授权方控制对 HTTP API 的访问 - Amazon API Gateway

使用 AWS Lambda 授权方控制对 HTTP API 的访问

您可以使用 Lambda 授权方以通过 Lambda 函数控制对 HTTP API 的访问。然后,当客户端调用您的 API 时,API Gateway 会调用您的 Lambda 函数。API Gateway 使用来自 Lambda 函数的响应来确定客户端是否可以访问您的 API。

负载格式版本

授权方负载格式版本指定 API Gateway 发送到 Lambda 授权方的数据格式,以及 API Gateway 如何解释来自 Lambda 的响应。如果未指定负载格式版本,则默认情况下 AWS Management Console 使用最新版本。如果您通过使用 AWS CLI、AWS CloudFormation 或开发工具包创建 Lambda 授权方,则必须指定 authorizerPayloadFormatVersion。支持的值是 1.02.0

如果您需要与 REST API 兼容,请使用版本 1.0

以下示例显示了每个负载格式版本的结构。

2.0
{ "version": "2.0", "type": "REQUEST", "routeArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request", "identitySource": ["user1", "123"], "routeKey": "$default", "rawPath": "/my/path", "rawQueryString": "parameter1=value1&parameter1=value2&parameter2=value", "cookies": ["cookie1", "cookie2"], "headers": { "header1": "value1", "header2": "value2" }, "queryStringParameters": { "parameter1": "value1,value2", "parameter2": "value" }, "requestContext": { "accountId": "123456789012", "apiId": "api-id", "authentication": { "clientCert": { "clientCertPem": "CERT_CONTENT", "subjectDN": "www.example.com", "issuerDN": "Example issuer", "serialNumber": "1", "validity": { "notBefore": "May 28 12:30:02 2019 GMT", "notAfter": "Aug 5 09:36:04 2021 GMT" } } }, "domainName": "id.execute-api.us-east-1.amazonaws.com", "domainPrefix": "id", "http": { "method": "POST", "path": "/my/path", "protocol": "HTTP/1.1", "sourceIp": "IP", "userAgent": "agent" }, "requestId": "id", "routeKey": "$default", "stage": "$default", "time": "12/Mar/2020:19:03:58 +0000", "timeEpoch": 1583348638390 }, "pathParameters": { "parameter1": "value1" }, "stageVariables": { "stageVariable1": "value1", "stageVariable2": "value2" } }
1.0
{ "version": "1.0", "type": "REQUEST", "methodArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request", "identitySource": "user1,123", "authorizationToken": "user1,123", "resource": "/request", "path": "/request", "httpMethod": "GET", "headers": { "X-AMZ-Date": "20170718T062915Z", "Accept": "*/*", "HeaderAuth1": "headerValue1", "CloudFront-Viewer-Country": "US", "CloudFront-Forwarded-Proto": "https", "CloudFront-Is-Tablet-Viewer": "false", "CloudFront-Is-Mobile-Viewer": "false", "User-Agent": "..." }, "queryStringParameters": { "QueryString1": "queryValue1" }, "pathParameters": {}, "stageVariables": { "StageVar1": "stageValue1" }, "requestContext": { "path": "/request", "accountId": "123456789012", "resourceId": "05c7jb", "stage": "test", "requestId": "...", "identity": { "apiKey": "...", "sourceIp": "...", "clientCert": { "clientCertPem": "CERT_CONTENT", "subjectDN": "www.example.com", "issuerDN": "Example issuer", "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1", "validity": { "notBefore": "May 28 12:30:02 2019 GMT", "notAfter": "Aug 5 09:36:04 2021 GMT" } } }, "resourcePath": "/request", "httpMethod": "GET", "apiId": "abcdef123" } }

Lambda 授权方响应格式

负载格式版本还确定您必须从 Lambda 函数返回的响应的结构。

格式 1.0 的 Lambda 函数响应

如果您选择 1.0 格式版本, Lambda 授权方必须返回允许或拒绝访问您的 API 路由的 IAM 策略。您可以在策略中使用标准 IAM 语法。有关 IAM 策略的示例,请参阅 针对调用 API 的访问控制。您可以使用 $context.authorizer.property 将上下文属性传递到 Lambda 集成或访问日志。context 对象是可选的,claims 是保留的占位符,不能用作上下文对象。要了解更多信息,请参阅 自定义 HTTP API 访问日志

{ "principalId": "abcdef", // The principal user identification associated with the token sent by the client. "policyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": "execute-api:Invoke", "Effect": "Allow|Deny", "Resource": "arn:aws:execute-api:{regionId}:{accountId}:{apiId}/{stage}/{httpVerb}/[{resource}/[{child-resources}]]" } ] }, "context": { "exampleKey": "exampleValue" } }

格式 2.0 的 Lambda 函数响应

如果选择 2.0 格式版本,则可以从 Lambda 函数中返回使用标准 IAM 策略语法的布尔值或 IAM 策略。要返回布尔值,请为授权方启用简单响应。以下示例演示您必须对 Lambda 函数进行编码才能返回的格式。context 对象是可选的。您可以使用 $context.authorizer.property 将上下文属性传递到 Lambda 集成或访问日志。要了解更多信息,请参阅“自定义 HTTP API 访问日志”。

Simple response
{ "isAuthorized": true/false, "context": { "exampleKey": "exampleValue" } }
IAM policy
{ "principalId": "abcdef", // The principal user identification associated with the token sent by the client. "policyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": "execute-api:Invoke", "Effect": "Allow|Deny", "Resource": "arn:aws:execute-api:{regionId}:{accountId}:{apiId}/{stage}/{httpVerb}/[{resource}/[{child-resources}]]" } ] }, "context": { "exampleKey": "exampleValue" } }

Lambda 授权方函数示例

以下示例 Node.js Lambda 函数演示您需要从 2.0 负载格式版本的 Lambda 函数返回的所需响应格式。

Simple response - Node.js
export const handler = async(event) => { let response = { "isAuthorized": false, "context": { "stringKey": "value", "numberKey": 1, "booleanKey": true, "arrayKey": ["value1", "value2"], "mapKey": {"value1": "value2"} } }; if (event.headers.authorization === "secretToken") { console.log("allowed"); response = { "isAuthorized": true, "context": { "stringKey": "value", "numberKey": 1, "booleanKey": true, "arrayKey": ["value1", "value2"], "mapKey": {"value1": "value2"} } }; } return response; };
Simple response - Python
import json def lambda_handler(event, context): response = { "isAuthorized": False, "context": { "stringKey": "value", "numberKey": 1, "booleanKey": True, "arrayKey": ["value1", "value2"], "mapKey": {"value1": "value2"} } } try: if (event["headers"]["authorization"] == "secretToken"): response = { "isAuthorized": True, "context": { "stringKey": "value", "numberKey": 1, "booleanKey": True, "arrayKey": ["value1", "value2"], "mapKey": {"value1": "value2"} } } print('allowed') return response else: print('denied') return response except BaseException: print('denied') return response
IAM policy - Node.js
export const handler = async(event) => { if (event.headers.authorization == "secretToken") { console.log("allowed"); return { "principalId": "abcdef", // The principal user identification associated with the token sent by the client. "policyDocument": { "Version": "2012-10-17", "Statement": [{ "Action": "execute-api:Invoke", "Effect": "Allow", "Resource": event.routeArn }] }, "context": { "stringKey": "value", "numberKey": 1, "booleanKey": true, "arrayKey": ["value1", "value2"], "mapKey": { "value1": "value2" } } }; } else { console.log("denied"); return { "principalId": "abcdef", // The principal user identification associated with the token sent by the client. "policyDocument": { "Version": "2012-10-17", "Statement": [{ "Action": "execute-api:Invoke", "Effect": "Deny", "Resource": event.routeArn }] }, "context": { "stringKey": "value", "numberKey": 1, "booleanKey": true, "arrayKey": ["value1", "value2"], "mapKey": { "value1": "value2" } } }; } };
IAM policy - Python
import json def lambda_handler(event, context): response = { # The principal user identification associated with the token sent by # the client. "principalId": "abcdef", "policyDocument": { "Version": "2012-10-17", "Statement": [{ "Action": "execute-api:Invoke", "Effect": "Deny", "Resource": event["routeArn"] }] }, "context": { "stringKey": "value", "numberKey": 1, "booleanKey": True, "arrayKey": ["value1", "value2"], "mapKey": {"value1": "value2"} } } try: if (event["headers"]["authorization"] == "secretToken"): response = { # The principal user identification associated with the token # sent by the client. "principalId": "abcdef", "policyDocument": { "Version": "2012-10-17", "Statement": [{ "Action": "execute-api:Invoke", "Effect": "Allow", "Resource": event["routeArn"] }] }, "context": { "stringKey": "value", "numberKey": 1, "booleanKey": True, "arrayKey": ["value1", "value2"], "mapKey": {"value1": "value2"} } } print('allowed') return response else: print('denied') return response except BaseException: print('denied') return response

身份来源

您可以选择指定 Lambda 授权方的身份来源。身份来源指定对请求进行授权所需的数据的位置。例如,您可以将标头或查询字符串值指定为身份来源。如果您指定身份来源,则客户端必须将其包含在请求中。如果客户端的请求不包括身份源,则 API Gateway 不会调用您的 Lambda 授权方,客户端会收到 401 错误。

下表描述了 Lambda 授权方支持的身份源。

类型

示例

备注

标头值 $request.header.name 标头名称不区分大小写。
查询字符串值 $request.querystring.name 查询字符串名称区分大小写。
上下文变量 $context.variableName 受支持的上下文变量的值。
阶段变量 $stageVariables.variableName 阶段变量的值。

缓存授权方响应

您可以通过指定 authorizerResultTtlInSeconds 为 Lambda 授权方启用缓存。为授权方启用缓存时,API Gateway 使用授权方的身份来源作为缓存密钥。如果客户端在配置的 TTL 内在身份来源中指定了相同的参数,则 API Gateway 使用缓存的授权方结果,而不调用 Lambda 函数。

要启用缓存,授权方必须至少有一个身份来源。

如果为授权方启用简单响应,则授权方的响应将完全允许或拒绝与缓存的身份来源值匹配的所有 API 请求。要获得更精细的权限,请禁用简单响应并返回 IAM 策略。

默认情况下,API Gateway 对使用授权方的 API 的所有路由使用缓存的授权方响应。要缓存每条路由的响应,请将 $context.routeKey 添加到授权方的身份来源中。

创建 Lambda 授权方

创建 Lambda 授权方时,需要指定供 API Gateway 使用的 Lambda 函数。您必须使用函数的资源策略或 IAM 角色向 API Gateway 授予调用 Lambda 函数的权限。在此示例中,我们更新函数的资源策略,以便它授予 API Gateway 调用 Lambda 函数的权限。

aws apigatewayv2 create-authorizer \ --api-id abcdef123 \ --authorizer-type REQUEST \ --identity-source '$request.header.Authorization' \ --name lambda-authorizer \ --authorizer-uri 'arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-2:123456789012:function:my-function/invocations' \ --authorizer-payload-format-version '2.0' \ --enable-simple-responses

以下命令授予 API Gateway 调用 Lambda 函数的权限。如果 API Gateway 没有调用函数的权限,客户端会收到 500 Internal Server Error

aws lambda add-permission \ --function-name my-authorizer-function \ --statement-id apigateway-invoke-permissions-abc123 \ --action lambda:InvokeFunction \ --principal apigateway.amazonaws.com \ --source-arn "arn:aws:execute-api:us-west-2:123456789012:api-id/authorizers/authorizer-id"

创建授权方并授予 API Gateway 调用授权方的权限后,请更新您的路由以使用此授权方。

aws apigatewayv2 update-route \ --api-id abcdef123 \ --route-id acd123 \ --authorization-type CUSTOM \ --authorizer-id def123

Lambda 授权方故障排除

如果 API Gateway 无法调用 Lambda 授权方,或者您的 Lambda 授权方返回无效格式的响应,则客户端将收到 500 Internal Server Error

要排除错误,请为 API 阶段启用访问日志记录。以 $context.authorizer.error 日志格式包含日志记录变量。

如果日志表明 API Gateway 无权调用您的函数,请更新函数的资源策略或提供 IAM 角色,以授予 API Gateway 调用授权方的权限。

如果日志表明您的 Lambda 函数返回无效响应,请验证您的 Lambda 函数以所需格式返回响应。