为什么我的 API Gateway 代理资源使用已激活缓存的 Lambda 授权方返回 HTTP 403“User is not authorized to access this resource(用户无权访问此资源)”错误?
上次更新日期:2022 年 8 月 17 日
我的 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 policy 包含一个显式 API Gateway API“Resource”(资源)元素,其格式如下:
"arn:aws:execute-api:<region>:<account>:<API_id>/<stage>/<http-method>/[<resource-path-name>/[<child-resources-path>]"
在 Lambda 授权方激活 Authorization Caching(授权缓存)时,返回的 IAM policy 将被缓存。然后,缓存的 IAM policy 将应用于在缓存的指定存活时间(TTL)期限内发出的任何其他 API 请求。
如果 API 的代理资源的贪婪路径变量为 {proxy+},则第一次授权成功。在缓存的 TTL 周期内向其他路径发出的任何其他 API 请求都会失败,并返回以下错误:
"message": "User is not authorized to access this resource"(“消息”:“用户无权访问此资源”)
额外的请求会失败,因为路径与缓存的 IAM policy 中定义的显式 API Gateway API“Resource”(资源)元素不匹配。
要解决此问题,您可以修改 Lambda 授权方函数的代码,改为在输出中返回通配符(*/*)资源。有关更多信息,请参阅 Lambda 操作的资源和条件。
注意:要激活授权方缓存,您的授权方必须返回一个适用于 API Gateway 中所有方法的策略。Lambda 授权方函数的代码必须在输出中返回通配符(*/*)资源才能允许所有资源。缓存策略需要缓存相同的资源路径,除非您在同一资源路径上两次发出相同的请求。
解决方法
注意:修改本文中的示例 Lambda 授权方函数代码段以适配您的使用案例。
在以下示例设置中,Lambda 函数从方法的 Amazon 资源名称(ARN)(“event.methodArn”)中提取 API Gateway 的 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) {
// 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 函数。