

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

# 使用 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 主控台中使用 AWS CLI或 AWS SDK 建立 Lambda 授權方。

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

下圖說明 Lambda 授權方的授權工作流程。

![\[API Gateway Lambda 授權工作流程\]](http://docs.aws.amazon.com/zh_tw/apigateway/latest/developerguide/images/custom-auth-workflow.png)


**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 Token (JWT) 或 OAuth 字符。  
如果您開啟 `TOKEN` 授權方的授權快取，則字符來源中指定的標頭名稱會成為快取金鑰。  
此外，您可以使用字符驗證來輸入 RegEx 陳述式。API Gateway 會對此表達式執行輸入字符的初始驗證，並在成功驗證時調用 Lambda 授權方函數。這樣做有助於減少對 API 的呼叫。  
`IdentityValidationExpression` 屬性僅支援 `TOKEN` 授權方。如需詳細資訊，請參閱[x-amazon-apigateway-authorizer 物件](api-gateway-swagger-extensions-authorizer.md)。

**注意**  
建議您使用 `REQUEST` 授權方來控制對 API 的存取。與使用 `TOKEN` 授權方時的單一身分來源相比，您可以在使用 `REQUEST` 授權方時，根據多個身分來源控制對 API 的存取。此外，您可以使用 `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.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html)，因為這些函數不會呼叫其他 AWS 服務。如果您的 Lambda 函數呼叫其他 AWS 服務，您將需要將 IAM 執行角色指派給 Lambda 函數。若要建立角色，請依照 [AWS Lambda 執行角色](https://docs.aws.amazon.com/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 藍圖，您可以選擇 **Use a blueprint (使用藍圖)**，並選擇 **api-gateway-authorizer-python** 藍圖來使用此藍圖。