

# 使用 API Gateway Lambda 授权方
<a name="apigateway-use-lambda-authorizer"></a>

使用 *Lambda 授权方*（以前称为*自定义授权方*）控制对 API 的访问。当客户端提出对 API 方法的请求时，API Gateway 会调用 Lambda 授权方。Lambda 授权方将调用方的身份作为输入，并返回 IAM 策略作为输出。

使用 Lambda 授权方实施自定义授权方案。该方案可以使用请求参数来确定调用方的身份或使用持有者令牌身份验证策略（例如 OAuth 或 SAML）。在 API Gateway REST API 控制台中、使用 Amazon CLI 或 Amazon SDK 创建 Lambda 授权方。

## Lambda 授权方授权工作流
<a name="api-gateway-lambda-authorizer-flow"></a>

下图展示了 Lambda 授权方的授权工作流。

**API Gateway Lambda 授权工作流**

1. 客户端对 API Gateway API 调用方法，以传递持有者令牌或请求参数。

1. API Gateway 检查是否为方法请求配置了 Lambda 授权方。如果已配置，API Gateway 将调用 Lambda 函数。

1. Lambda 函数会对调用方进行身份验证。该函数可通过以下方式进行身份验证：
   + 调用 OAuth 提供程序来获取 OAuth 访问令牌。
   + 调用 SAML 提供程序来获取 SAML 断言。
   + 基于请求参数值生成 IAM 策略。
   + 从数据库中检索凭证。

1. Lambda 函数返回一个 IAM 策略和一个主体标识符。如果 Lambda 函数不返回该信息，表明调用失败。

1. API Gateway 会对 IAM 策略进行评估。
   + 如果拒绝访问，API Gateway 将返回一个合适的 HTTP 状态代码，如 `403 ACCESS_DENIED`。
   + 如果允许访问，API Gateway 将调用该方法。

     如果启用了授权缓存，API Gateway 将缓存策略，这样就不会再次调用 Lambda 授权方函数。确保策略适用于 API 中的所有资源和方法。

您可以自定义 `403 ACCESS_DENIED` 或 `401 UNAUTHORIZED` 网关响应。要了解更多信息，请参阅[针对 API Gateway 中 REST API 的网关响应](api-gateway-gatewayResponse-definition.md)。

## 选择 Lambda 授权方类型
<a name="api-gateway-lambda-authorizer-choose"></a>

Lambda 授权方有两种类型：

**基于请求参数的 Lambda 授权方（`REQUEST` 授权方）**  
`REQUEST` 授权方以标头、查询字符串参数、[`stageVariables`](api-gateway-mapping-template-reference.md#stagevariables-template-reference) 和 [`$context`](api-gateway-mapping-template-reference.md#context-variable-reference) 变量的组合接收调用方的身份。您可以使用 `REQUEST` 授权方，根据来自多个身份来源（例如 `$context.path` 和 `$context.httpMethod` 上下文变量）的信息创建精细策略。  
如果您为 `REQUEST` 授权方开启授权缓存，API Gateway 会验证请求中是否存在所有指定的身份来源。如果指定的身份来源缺失、为 null 或为空，API Gateway 将返回 `401 Unauthorized` HTTP 响应，而不调用 Lambda 授权方函数。如果定义了多个身份来源，它们都将用于派生授权方的缓存键并保持相应顺序。您可以使用多个身份来源定义精细缓存键。  
如果您更改缓存键的任意部分并重新部署 API，授权方将丢弃缓存策略文档并生成新的文档。  
如果您为 `REQUEST` 授权方关闭授权缓存，API Gateway 会直接将请求传递给 Lambda 函数。

**基于令牌的 Lambda 授权方（`TOKEN` 授权方）**  
`TOKEN` 授权方接收持有者令牌（例如 JSON Web 令牌（JWT）或 OAuth 令牌）中的调用方身份。  
如果您为 `TOKEN` 授权方开启授权缓存，令牌来源中指定的标头名称将成为缓存键。  
此外，您还可以使用令牌验证来输入正则表达式语句。API Gateway 将针对此表达式执行输入令牌的初始验证并在成功验证后调用 Lambda 授权方函数。这有助于减少对您的 API 的调用。  
仅 `TOKEN` 授权方支持 `IdentityValidationExpression` 属性。有关更多信息，请参阅 [x-amazon-apigateway-authorizer 对象](api-gateway-swagger-extensions-authorizer.md)。

**注意**  
建议您使用 `REQUEST` 授权方来控制对 API 的访问。使用 `REQUEST` 授权方时，您可以基于多个身份来源控制对 API 的访问，而使用 `TOKEN` 授权方时则基于单一身份来源。此外，您还可以使用 `REQUEST` 授权方的多个身份来源分离缓存键。

## `REQUEST` 授权方 Lambda 函数示例
<a name="api-gateway-lambda-authorizer-request-lambda-function-create"></a>

以下代码示例创建一个 Lambda 授权方函数，如果客户端提供的 `HeaderAuth1` 标头、`QueryString1` 查询参数和 `StageVar1` 阶段变量都分别与 `headerValue1`、`queryValue1` 和 `stageValue1` 的指定值相匹配，该函数将允许调用请求。

------
#### [ Node.js ]

```
// A simple request-based authorizer example to demonstrate how to use request 
// parameters to allow or deny a request. In this example, a request is  
// authorized if the client-supplied HeaderAuth1 header, QueryString1
// query parameter, and stage variable of StageVar1 all match
// specified values of 'headerValue1', 'queryValue1', and 'stageValue1',
// respectively.
    
export const handler = function(event, context, callback) {
    console.log('Received event:', JSON.stringify(event, null, 2));
    
    // Retrieve request parameters from the Lambda function input:
    var headers = event.headers;
    var queryStringParameters = event.queryStringParameters;
    var pathParameters = event.pathParameters;
    var stageVariables = event.stageVariables;
        
    // Parse the input for the parameter values
    var tmp = event.methodArn.split(':');
    var apiGatewayArnTmp = tmp[5].split('/');
    var awsAccountId = tmp[4];
    var region = tmp[3];
    var restApiId = apiGatewayArnTmp[0];
    var stage = apiGatewayArnTmp[1];
    var method = apiGatewayArnTmp[2];
    var resource = '/'; // root resource
    if (apiGatewayArnTmp[3]) {
        resource += apiGatewayArnTmp[3];
    }
        
    // Perform authorization to return the Allow policy for correct parameters and 
    // the 'Unauthorized' error, otherwise.

     
    if (headers.HeaderAuth1 === "headerValue1"
        && queryStringParameters.QueryString1 === "queryValue1"
        && stageVariables.StageVar1 === "stageValue1") {
        callback(null, generateAllow('me', event.methodArn));
    }  else {
        callback(null, generateDeny('me', event.methodArn));
    }
}
     
// Help function to generate an IAM policy
var generatePolicy = function(principalId, effect, resource) {
    // Required output:
    var authResponse = {};
    authResponse.principalId = principalId;
    if (effect && resource) {
        var policyDocument = {};
        policyDocument.Version = '2012-10-17'; // default version
        policyDocument.Statement = [];
        var statementOne = {};
        statementOne.Action = 'execute-api:Invoke'; // default action
        statementOne.Effect = effect;
        statementOne.Resource = resource;
        policyDocument.Statement[0] = statementOne;
        authResponse.policyDocument = policyDocument;
    }
    // Optional output with custom properties of the String, Number or Boolean type.
    authResponse.context = {
        "stringKey": "stringval",
        "numberKey": 123,
        "booleanKey": true
    };
    return authResponse;
}
     
var generateAllow = function(principalId, resource) {
    return generatePolicy(principalId, 'Allow', resource);
}
     
var generateDeny = function(principalId, resource) {
    return generatePolicy(principalId, 'Deny', resource);
}
```

------
#### [ Python ]

```
# A simple request-based authorizer example to demonstrate how to use request
# parameters to allow or deny a request. In this example, a request is
# authorized if the client-supplied headerauth1 header, QueryString1
# query parameter, and stage variable of StageVar1 all match
# specified values of 'headerValue1', 'queryValue1', and 'stageValue1',
# respectively.

def lambda_handler(event, context):
    print(event)

    # Retrieve request parameters from the Lambda function input:
    headers = event['headers']
    queryStringParameters = event['queryStringParameters']
    pathParameters = event['pathParameters']
    stageVariables = event['stageVariables']

    # Parse the input for the parameter values
    tmp = event['methodArn'].split(':')
    apiGatewayArnTmp = tmp[5].split('/')
    awsAccountId = tmp[4]
    region = tmp[3]
    restApiId = apiGatewayArnTmp[0]
    stage = apiGatewayArnTmp[1]
    method = apiGatewayArnTmp[2]
    resource = '/'

    if (apiGatewayArnTmp[3]):
        resource += apiGatewayArnTmp[3]

    # Perform authorization to return the Allow policy for correct parameters
    # and the 'Unauthorized' error, otherwise.

    if (headers['HeaderAuth1'] == "headerValue1" and queryStringParameters['QueryString1'] == "queryValue1" and stageVariables['StageVar1'] == "stageValue1"):
        response = generateAllow('me', event['methodArn'])
        print('authorized')
        return response
    else:
        print('unauthorized')
        response = generateDeny('me', event['methodArn'])
        return response
    # Help function to generate IAM policy


def generatePolicy(principalId, effect, resource):
    authResponse = {}
    authResponse['principalId'] = principalId
    if (effect and resource):
        policyDocument = {}
        policyDocument['Version'] = '2012-10-17'
        policyDocument['Statement'] = []
        statementOne = {}
        statementOne['Action'] = 'execute-api:Invoke'
        statementOne['Effect'] = effect
        statementOne['Resource'] = resource
        policyDocument['Statement'] = [statementOne]
        authResponse['policyDocument'] = policyDocument

    authResponse['context'] = {
        "stringKey": "stringval",
        "numberKey": 123,
        "booleanKey": True
    }

    return authResponse


def generateAllow(principalId, resource):
    return generatePolicy(principalId, 'Allow', resource)


def generateDeny(principalId, resource):
    return generatePolicy(principalId, 'Deny', resource)
```

------

在此示例中，Lambda 授权方函数将检查输入参数并按如下所示操作：
+ 如果所有必需的参数值匹配预期值，该授权方函数将返回 `200 OK` HTTP 响应和 IAM 策略（类似于以下内容）并且方法请求成功：

------
#### [ JSON ]

****  

  ```
  {
    "Version":"2012-10-17",		 	 	 
    "Statement": [
      {
        "Action": "execute-api:Invoke",
        "Effect": "Allow",
        "Resource": "arn:aws:execute-api:us-east-1:123456789012:ivdtdhp7b5/ESTestInvoke-stage/GET/"
      }
    ]
  }
  ```

------
+ 否则，授权方函数将返回 `401 Unauthorized` HTTP 响应并且方法请求将失败。

除了返回 IAM 策略之外，Lambda 授权方函数还必须返回调用方的委托人标识符。它还可以选择返回一个 `context` 对象，其中包含可传入集成后端的其他信息。有关更多信息，请参阅 [来自 API Gateway Lambda 授权方的输出](api-gateway-lambda-authorizer-output.md)。

在生产代码中，您可能需要先对用户进行身份验证，然后才能授予授权。您可以通过调用身份验证提供程序，在 Lambda 函数中添加身份验证逻辑，如该提供程序的文档中的指示。

## `TOKEN` 授权方 Lambda 函数示例
<a name="api-gateway-lambda-authorizer-token-lambda-function-create"></a>

以下代码示例创建一个 `TOKEN` Lambda 授权方函数，如果客户端提供的令牌值为 `allow`，该函数将允许调用方调用方法。如果令牌值为 `deny`，则不允许调用方调用请求。如果令牌值为 `unauthorized` 或空字符串，该授权方函数将返回 `401 UNAUTHORIZED` 响应。

------
#### [ Node.js ]

```
// A simple token-based authorizer example to demonstrate how to use an authorization token 
// to allow or deny a request. In this example, the caller named 'user' is allowed to invoke 
// a request if the client-supplied token value is 'allow'. The caller is not allowed to invoke 
// the request if the token value is 'deny'. If the token value is 'unauthorized' or an empty
// string, the authorizer function returns an HTTP 401 status code. For any other token value, 
// the authorizer returns an HTTP 500 status code. 
// Note that token values are case-sensitive.

export const handler =  function(event, context, callback) {
    var token = event.authorizationToken;
    switch (token) {
        case 'allow':
            callback(null, generatePolicy('user', 'Allow', event.methodArn));
            break;
        case 'deny':
            callback(null, generatePolicy('user', 'Deny', event.methodArn));
            break;
        case 'unauthorized':
            callback("Unauthorized");   // Return a 401 Unauthorized response
            break;
        default:
            callback("Error: Invalid token"); // Return a 500 Invalid token response
    }
};

// Help function to generate an IAM policy
var generatePolicy = function(principalId, effect, resource) {
    var authResponse = {};
    
    authResponse.principalId = principalId;
    if (effect && resource) {
        var policyDocument = {};
        policyDocument.Version = '2012-10-17'; 
        policyDocument.Statement = [];
        var statementOne = {};
        statementOne.Action = 'execute-api:Invoke'; 
        statementOne.Effect = effect;
        statementOne.Resource = resource;
        policyDocument.Statement[0] = statementOne;
        authResponse.policyDocument = policyDocument;
    }
    
    // Optional output with custom properties of the String, Number or Boolean type.
    authResponse.context = {
        "stringKey": "stringval",
        "numberKey": 123,
        "booleanKey": true
    };
    return authResponse;
}
```

------
#### [ Python ]

```
# A simple token-based authorizer example to demonstrate how to use an authorization token
# to allow or deny a request. In this example, the caller named 'user' is allowed to invoke
# a request if the client-supplied token value is 'allow'. The caller is not allowed to invoke
# the request if the token value is 'deny'. If the token value is 'unauthorized' or an empty
# string, the authorizer function returns an HTTP 401 status code. For any other token value,
# the authorizer returns an HTTP 500 status code.
# Note that token values are case-sensitive.

import json


def lambda_handler(event, context):
    token = event['authorizationToken']
    if token == 'allow':
        print('authorized')
        response = generatePolicy('user', 'Allow', event['methodArn'])
    elif token == 'deny':
        print('unauthorized')
        response = generatePolicy('user', 'Deny', event['methodArn'])
    elif token == 'unauthorized':
        print('unauthorized')
        raise Exception('Unauthorized')  # Return a 401 Unauthorized response
        return 'unauthorized'
    try:
        return json.loads(response)
    except BaseException:
        print('unauthorized')
        return 'unauthorized'  # Return a 500 error


def generatePolicy(principalId, effect, resource):
    authResponse = {}
    authResponse['principalId'] = principalId
    if (effect and resource):
        policyDocument = {}
        policyDocument['Version'] = '2012-10-17'
        policyDocument['Statement'] = []
        statementOne = {}
        statementOne['Action'] = 'execute-api:Invoke'
        statementOne['Effect'] = effect
        statementOne['Resource'] = resource
        policyDocument['Statement'] = [statementOne]
        authResponse['policyDocument'] = policyDocument
    authResponse['context'] = {
        "stringKey": "stringval",
        "numberKey": 123,
        "booleanKey": True
    }
    authResponse_JSON = json.dumps(authResponse)
    return authResponse_JSON
```

------

在此示例中，当 API 接收方法请求时，API Gateway 将源令牌传递到 `event.authorizationToken` 属性中的此 Lambda 授权方函数。Lambda 授权方函数将读取令牌并按下所示做出行为：
+ 如果令牌值为 `allow`，该授权方函数将返回 `200 OK` HTTP 响应和 IAM 策略（类似于以下内容）并且方法请求成功：

------
#### [ JSON ]

****  

  ```
  {
    "Version":"2012-10-17",		 	 	 
    "Statement": [
      {
        "Action": "execute-api:Invoke",
        "Effect": "Allow",
        "Resource": "arn:aws:execute-api:us-east-1:123456789012:ivdtdhp7b5/ESTestInvoke-stage/GET/"
      }
    ]
  }
  ```

------
+ 如果令牌值为 `deny`，该授权方函数将返回 `200 OK` HTTP 响应和 `Deny` IAM 策略（类似于以下内容）并且方法请求失败：

------
#### [ JSON ]

****  

  ```
  {
    "Version":"2012-10-17",		 	 	 
    "Statement": [
      {
        "Action": "execute-api:Invoke",
        "Effect": "Deny",
        "Resource": "arn:aws:execute-api:us-east-1:123456789012:ivdtdhp7b5/ESTestInvoke-stage/GET/"
      }
    ]
  }
  ```

------
**注意**  
在测试环境之外，API Gateway 返回 `403 Forbidden` HTTP 响应并且方法请求将失败。
+ 如果令牌值为 `unauthorized` 或空字符串，该授权方函数将返回 `401 Unauthorized` HTTP 响应并且方法调用失败。
+ 如果令牌为任何其他内容，客户端将收到 `500 Invalid token` 响应并且方法调用失败。

除了返回 IAM 策略之外，Lambda 授权方函数还必须返回调用方的委托人标识符。它还可以选择返回一个 `context` 对象，其中包含可传入集成后端的其他信息。有关更多信息，请参阅 [来自 API Gateway Lambda 授权方的输出](api-gateway-lambda-authorizer-output.md)。

在生产代码中，您可能需要先对用户进行身份验证，然后才能授予授权。您可以通过调用身份验证提供程序，在 Lambda 函数中添加身份验证逻辑，如该提供程序的文档中的指示。

## 其他 Lambda 授权方函数示例
<a name="api-gateway-lambda-authorizer-lambda-function-create"></a>

以下列表显示了 Lambda 授权方函数的其他示例。您可以在创建 API 的相同账户或不同账户中创建 Lambda 函数。

对于前面的 Lambda 函数示例，您可以使用内置 [AWSLambdaBasicExecutionRole](https://docs.amazonaws.cn/lambda/latest/dg/lambda-intro-execution-role.html)，因为这些函数不会调用其他 Amazon 服务。如果您的 Lambda 函数调用其他 Amazon 服务，您需要为该 Lambda 函数分配 IAM 执行角色。要创建该角色，请按照 [Amazon Lambda 执行角色](https://docs.amazonaws.cn/lambda/latest/dg/lambda-intro-execution-role.html)中的说明操作。

**其他 Lambda 授权方函数示例**
+  有关应用程序示例，请参阅 GitHub 中的 [Open Banking Brazil - Authorization Samples](https://github.com/aws-samples/openbanking-brazilian-auth-samples)。
+  有关更多示例 Lambda 函数，请参阅 GitHub 上的 [ aws-apigateway-lambda-authorizer-blueprints](https://github.com/awslabs/aws-apigateway-lambda-authorizer-blueprints)。
+ 您可以创建 Lambda 授权方，该授权方使用 Amazon Cognito 用户池对用户进行身份验证，并使用 Verified Permissions 根据策略存储对调用方进行授权。有关更多信息，请参阅 [使用 Verified Permissions 根据身份的属性控制访问](apigateway-lambda-authorizer-verified-permissions.md)。
+ Lambda 控制台提供一个 Python 蓝图，您可以通过选择**使用蓝图**并选择 **api-gateway-authorizer-python** 蓝图来使用该蓝图。