为什么我的 API Gateway 代理资源使用已激活缓存的 Lambda 授权方返回 HTTP 403“User is not authorized to access this resource”(用户无权访问此资源)错误?

上次更新日期:2022 年 2 月 4 日

我的 Amazon API Gateway 代理资源使用已激活缓存的 AWS Lambda 授权方返回以下 HTTP 403 错误消息:“User is not authorized to access this resource”(用户无权访问此资源)。为什么会出现这种情况,我该怎样解决这个错误呢?

简短描述

注意:API Gateway 可以因各种原因返回 403 User is not authorized to access this resource(用户无权访问此资源)错误。本文解决了与使用仅激活缓存的 Lambda 授权方的 API Gateway 代理资源相关的 403 错误。有关排查其他类型 403 错误的信息,请参阅如何排查来自 API Gateway 的 HTTP 403 错误?

Lambda 授权方的输出会向 API Gateway 返回 AWS Identity and Access Management (IAM) 策略。IAM 策略包含一个显式 API Gateway API“Resource”(资源)元素,其格式如下:

"arn:aws:execute-api:<region>:<account>:<API_id>/<stage>/<httpVerb>/[<resource-name>/[<child-resources>]]"

在 Lambda 授权方激活 Authorization Caching(授权缓存)时,此 IAM 策略将被缓存。然后,缓存的策略将应用于在缓存的指定存活时间 (TTL) 期限内发出的任何其他 API 请求。

如果 API 的代理资源的贪婪路径变量为 {proxy+},则第一次授权成功。在缓存的 TTL 周期内向其他路径发出的任何其他 API 请求都会失败,并返回以下错误:

"message": "User is not authorized to access this resource"(“消息”:“用户无权访问此资源”)

额外的请求会失败,因为路径与缓存的 IAM 策略中定义的显式 API Gateway API “Resource”(资源)元素不匹配。

要解决此问题,您可以修改 Lambda 授权方函数的代码,改为在输出中返回通配符 (*/*) 资源。 有关更多信息,请参阅 Lambda 操作的资源和条件

解决方法

注意:您可能需要修改本文中的示例 Lambda 授权方函数代码段以适配您的使用案例。

在以下示例设置中,Lambda 函数从方法的 Amazon Resource Name (ARN) (“event.methodArn”) 中提取 API 的 id。然后,函数通过将方法 ARN 的路径与 API 的 id 值和通配符 (*/*) 组合来定义通配符 “Resource”(资源)变量。

返回通配符“Resource”(资源)变量且基于令牌的 Lambda 授权方函数代码示例

exports.handler =  function(event, context, callback) {
    var token = event.authorizationToken;
    var tmp = event.methodArn.split(':');
    var apiGatewayArnTmp = tmp[5].split('/');
    
    // Create wildcard resource
    var resource = tmp[0] + ":" + tmp[1] + ":" + tmp[2] + ":" + tmp[3] + ":" + tmp[4] + ":" + apiGatewayArnTmp[0] + '/*/*'; 
    switch (token) {
        case 'allow':
            callback(null, generatePolicy('user', 'Allow', resource));
            break;
        case 'deny':
            callback(null, generatePolicy('user', 'Deny', resource));
            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;
}

返回通配符“Resource”(资源)变量且基于请求参数的 Lambda 授权方函数代码示例

exports.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('/');

    // Create wildcard resource
    var resource = tmp[0] + ":" + tmp[1] + ":" + tmp[2] + ":" + tmp[3] + ":" + tmp[4] + ":" + apiGatewayArnTmp[0] + '/*/*'; 
    console.log("resource: " + resource);
    // if (apiGatewayArnTmp[3]) {
    //     resource += apiGatewayArnTmp[3];
    // }
        
    // Perform authorization to return the Allow policy for correct parameters and 
    // the 'Unauthorized' error, otherwise.
    var authResponse = {};
    var condition = {};
    condition.IpAddress = {};
     
    
    if (headers.headerauth1 === "headerValue1"
        && queryStringParameters.QueryString1 === "queryValue1"
        && stageVariables.StageVar1 === "stageValue1") {
        callback(null, generateAllow('me', resource));
    }  else {
        callback("Unauthorized");
    }
}
     
// Help function to generate an IAM policy
var generatePolicy = function(principalId, effect, resource) {
    // Required output:
    console.log("Resource in generatePolicy(): " + resource);
    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;
        console.log("***Resource*** " + resource);
        policyDocument.Statement[0] = statementOne;
        console.log("***Generated Policy*** ");
        console.log(policyDocument);
        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);
}

有关如何编辑 Lambda 函数代码的更多信息,请参阅创建定义为.zip 文件归档的 Lambda 函数。另请参阅 AWS Lambda 开发人员指南中的使用控制台编辑器编辑代码


这篇文章对您有帮助吗?


您是否需要账单或技术支持?