

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 使用 AWS Lambda 授權方控制對 HTTP APIs存取
<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或 SDK 建立 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 函數會示範您需要從 Lambda 函數傳回的 `2.0` 承載格式版本的必要回應格式。

------
#### [ 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 授權方的支援身分來源。


| **類型** | **範例** | **備註** | 
| --- | --- | --- | 
| 標頭值 | \$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 函數。您必須授予 API Gateway 使用 Lambda 函數的資源政策或 IAM 角色叫用該函數的許可。以下 [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)的回應。