使用 API Gateway Lambda 授权方 - Amazon API Gateway

使用 API Gateway Lambda 授权方

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

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

Lambda 授权方授权工作流

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

API Gateway Lambda 授权工作流
API Gateway Lambda 授权工作流
  1. 客户端对 API Gateway API 调用方法,以传递持有者令牌或请求参数。

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

  3. Lambda 函数会对调用方进行身份验证。该函数可通过以下方式进行身份验证:

    • 调用 OAuth 提供程序来获取 OAuth 访问令牌。

    • 调用 SAML 提供程序来获取 SAML 断言。

    • 基于请求参数值生成 IAM 策略。

    • 从数据库中检索凭证。

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

  5. API Gateway 会对 IAM 策略进行评估。

    • 如果拒绝访问,API Gateway 将返回一个合适的 HTTP 状态代码,如 403 ACCESS_DENIED

    • 如果允许访问,API Gateway 将调用该方法。

      如果启用了授权缓存,API Gateway 将缓存策略,这样就不会再次调用 Lambda 授权方函数。

您可以自定义 403 ACCESS_DENIED401 UNAUTHORIZED 网关响应。要了解更多信息,请参阅 针对 API Gateway 中 REST API 的网关响应

选择 Lambda 授权方类型

Lambda 授权方有两种类型:

基于请求参数的 Lambda 授权方(REQUEST 授权方)

REQUEST 授权方接收标头、查询字符串参数、stageVariables$context 变量组合中的调用方身份。您可以使用 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 对象

注意

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

REQUEST 授权方 Lambda 函数示例

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

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("Unauthorized"); } } // 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. import json 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') raise Exception('Unauthorized') # Return a 401 Unauthorized 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 策略(类似于以下内容)并且方法请求成功:

    { "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 授权方的输出

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

TOKEN 授权方 Lambda 函数示例

以下代码示例创建一个 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 策略(类似于以下内容)并且方法请求成功:

    { "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 策略(类似于以下内容)并且方法请求失败:

    { "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 授权方的输出

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

其他 Lambda 授权方函数示例

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

对于前面的 Lambda 函数示例,您可以使用内置 AWSLambdaBasicExecutionRole,因为这些函数不会调用其他 AWS 服务。如果您的 Lambda 函数调用其他 AWS 服务,您需要为该 Lambda 函数分配 IAM 执行角色。要创建该角色,请按照 AWS Lambda 执行角色中的说明操作。

其他 Lambda 授权方函数示例