

# 使用 AWS Lambda 授权方控制对 HTTP API 的访问
<a name="http-api-lambda-authorizer"></a>

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

## 负载格式版本
<a name="http-api-lambda-authorizer.payload-format"></a>

授权方负载格式版本指定 API Gateway 发送到 Lambda 授权方的数据格式，以及 API Gateway 如何解释来自 Lambda 的响应。如果未指定负载格式版本，则默认情况下 AWS 管理控制台 使用最新版本。如果您通过使用 AWS CLI、CloudFormation 或开发工具包创建 Lambda 授权方，则必须指定 `authorizerPayloadFormatVersion`。支持的值是 `1.0` 和 `2.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 授权方响应格式
<a name="http-api-lambda-authorizer.payload-format-response"></a>

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

### 格式 1.0 的 Lambda 函数响应
<a name="http-api-lambda-authorizer.v1"></a>

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

**Example**    
****  

```
{
  "principalId": "abcdef", 
  "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 函数响应
<a name="http-api-lambda-authorizer.v2"></a>

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

------
#### [ Simple response ]

```
{
  "isAuthorized": true/false,
  "context": {
    "exampleKey": "exampleValue"
  }
}
```

------
#### [ IAM policy ]

****  

```
{
  "principalId": "abcdef", 
  "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 授权方函数示例
<a name="http-api-lambda-authorizer.example-code"></a>

以下示例 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
```

------

## 身份来源
<a name="http-api-lambda-authorizer.identity-sources"></a>

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

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


| **Type** | **示例** | **备注**： | 
| --- | --- | --- | 
| 标头值 | \$1request.header.name | 标头名称不区分大小写。 | 
| 查询字符串值 | \$1request.querystring.name | 查询字符串名称区分大小写。 | 
| 上下文变量 | \$1context.variableName | 受支持的[上下文变量](http-api-logging-variables.md)的值。 | 
| 阶段变量 | \$1stageVariables.variableName | [阶段变量](http-api-stages.stage-variables.md)的值。 | 

也可以直接从 Lambda 函数返回 ` {"errorMessage" : "Unauthorized"}`，来向您的客户端返回 `401` 错误。如果您直接从 Lambda 函数向客户端返回 `401` 错误，则在创建 Lambda 授权方时不要指定任何身份来源。

## 缓存授权方响应
<a name="http-api-lambda-authorizer.caching"></a>

您可以通过指定 [authorizerResultTtlInSeconds](https://docs.aws.amazon.com/apigatewayv2/latest/api-reference/apis-apiid-authorizers.html#apis-apiid-authorizers-prop-createauthorizerinput-authorizerresultttlinseconds) 为 Lambda 授权方启用缓存。为授权方启用缓存时，API Gateway 使用授权方的身份来源作为缓存密钥。如果客户端在配置的 TTL 内在身份来源中指定了相同的参数，则 API Gateway 使用缓存的授权方结果，而不调用 Lambda 函数。

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

如果为授权方启用简单响应，则授权方的响应将完全允许或拒绝与缓存的身份来源值匹配的所有 API 请求。要获得更精细的权限，请禁用简单响应并返回 IAM 策略。根据授权方，IAM 策略可能需要控制对多个身份来源的访问权限。

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

## 创建 Lambda 授权方
<a name="http-api-lambda-authorizer.example-create"></a>

创建 Lambda 授权方时，需要指定供 API Gateway 使用的 Lambda 函数。您必须使用函数的资源策略或 IAM 角色向 API Gateway 授予调用 Lambda 函数的权限。以下 [create-authorizer](https://docs.aws.amazon.com/cli/latest/reference/apigatewayv2/create-authorizer.html) 命令创建 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
```

以下 [add-permission](https://docs.aws.amazon.com/cli/latest/reference/lambda/add-permission.html) 命令更新 Lambda 函数的资源策略，来授予 API Gateway 调用函数的权限。如果 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 调用授权方的权限后，请更新您的路由以使用此授权方。以下 [update-route](https://docs.aws.amazon.com/cli/latest/reference/apigatewayv2/update-route.html) 命令将 Lambda 授权方添加到路由中。如果 Lambda 授权方使用策略缓存，请务必更新策略以控制其它路由的访问权限。

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

## Lambda 授权方故障排除
<a name="http-api-lambda-authorizer.troubleshooting"></a>

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

要排除错误，请为 API 阶段[启用访问日志记录](http-api-logging.md)。以 `$context.authorizer.error` 日志格式包含日志记录变量。

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

如果日志表明您的 Lambda 函数返回无效响应，请验证您的 Lambda 函数以[所需格式](#http-api-lambda-authorizer.payload-format-response)返回响应。