AWS API 网关/Lambda 自定义授权方中的 AuthorizerConfigurationException
AuthorizerConfigurationException in AWS API Gateway / Lambda custom authorizer
我正在 AWS Lambda 上使用无服务器框架构建 REST 服务。我已经创建了一个自定义授权方,它在我的 lambda 的任何调用之前被调用。当我 运行 serverless-offline 时,一切正常。部署时,AP 网关出现错误。我在 API 网关中启用了登录,但没有任何内容写入日志。
这是我的 serverless.yml 文件:
provider:
name: aws
runtime: nodejs12.x
stage: ${opt:stage, "dev"}
region: eu-west-1
functions:
authorize:
handler: src/handlers/authorization.authorize
listAlerts:
handler: src/handlers/alert-handler.listAlerts
events:
- http:
path: /alerts
method: GET
authorizer: ${self:custom.authorization.authorize}
custom:
stage: ${opt:stage, self:provider.stage}
authorization:
authorize:
name: authorize
type: TOKEN
identitySource: method.request.header.Authorization
identityValidationExpression: Bearer (.*)
plugins:
- serverless-plugin-typescript
- serverless-offline
package:
include:
- src/**.*
我的授权处理程序如下所示。该方法获取我的身份验证令牌并使用 JOSE 对其进行验证,并且 return 是用户和某些角色的 principalId:
import jwksClient from "jwks-rsa";
import { JWT } from "jose";
export const authorize = async (event: CustomAuthorizerEvent): Promise<CustomAuthorizerResult> => {
const prefix = "bearer ";
if (!event.authorizationToken?.toLowerCase().startsWith(prefix)) {
return Promise.reject("Unauthorized");
}
const token = event.authorizationToken?.substring(prefix.length);
const signingKey = await getCachedSigningKey()
const jwt = JWT.verify(token, signingKey);
if ((typeof jwt) !== "object") {
throw "Unauthorized";
}
const userId = jwt["sub"];
const expires = Number(jwt["exp"]);
const roles = jwt["assumed-roles"] as string[];
if (Date.now() > expires * 1000 ) {
throw "Unauthorized";
}
const principalId = userId;
const policyDocument: PolicyDocument = {
Version: "2012-10-17",
Statement: [
{
Action: "execute-api:Invoke",
Effect: "Allow",
Resource: event.methodArn,
}
]
};
return {
principalId,
policyDocument,
context: {
userId,
roles
}
};
};
如果 serverless offline start
,模拟的 API-网关在端口 3000 上启动。我调用 API:
❯ http :3000/alerts/5480e8a1-e3d4-432d-985e-9542c91a49ce Authorization:"Bearer eyJraWQiO.......LHA12jM2UEXFy76dhKUj_iX6SXQQ"
HTTP/1.1 200 OK
Connection: keep-alive
Date: Wed, 04 Mar 2020 21:29:07 GMT
accept-ranges: bytes
access-control-allow-origin: *
cache-control: no-cache
content-length: 174
content-type: application/json; charset=utf-8
{
"id": "5480e8a1-e3d4-432d-985e-9542c91a49ce",
"message": "test",
"subjects": [
{
"id": "6ee2c07d-6486-4601-9b4b-05f61c0d0caf",
"referenceId": "5480e8a1-e3d4-432d-985e-9542c91a49ce"
}
]
}
所以,它在本地工作,我的处理程序记录了用户 ID 和角色。然后我使用 serverless deplpy
部署到 AWS,没有任何警告。我尝试调用我的 API-网关端点:
❯ http https://og8...<bla-bla>..kcc.execute-api.eu-west-1.amazonaws.com/dev/alerts/alerts/ Authorization:"Bearer eyJraWQiOiJkZXYiL.....VOPI2LHA12jM2UEXFy76dhKUj_iX6SXQQ"
HTTP/1.1 500 Internal Server Error
Connection: keep-alive
Content-Length: 16
Content-Type: application/json
Date: Wed, 04 Mar 2020 21:32:22 GMT
Via: 1.1 e31ab4c27d99cec62ef37e2607db9b45.cloudfront.net (CloudFront)
X-Amz-Cf-Id: kfIhCZHCGoL3OcjPSX4QWdtK1Qequ2vJe9RNst4wBEf_d90fJLjWgQ==
X-Amz-Cf-Pop: ARN1-C1
X-Cache: Error from cloudfront
x-amz-apigw-id: I4mu_HOFjoEF6SQ=
x-amzn-ErrorType: AuthorizerConfigurationException
x-amzn-RequestId: 3cb89b9a-26db-4890-8edb-6aedcc51c09e
{
"message": null
}
调用不会立即return,而是超时。这是怎么回事?
- 您的配置中没有这样的路由/alerts/5480e8a1-e3d4-432d-985e-9542c91a49c
- 请打开 API GW 控制台并使用测试来调试您的问题
好吧,回答我自己的问题,我在上面遇到了多个问题:
- 无法调用异步函数。
const signingKey = await getCachedSigningKey()
会失败(这可能是因为在 VPC 之外没有执行 HTTP 的权限)。
- 上下文只能包含简单映射。我试图在这里设置一个数组 (
x
),但这是不允许的。仅限简单值。
我的最终工作解决方案如下所示,如果它可以帮助任何人的话:
import { CustomAuthorizerEvent, CustomAuthorizerResult, PolicyDocument } from "aws-lambda";
import { JWT } from "jose";
export enum Role {
User = "ROLE_USER",
Admin = "ROLE_ADMIN",
}
export const getAuthorizerForRoles = (requiredRoles: Role[]) => async (event: CustomAuthorizerEvent): Promise<CustomAuthorizerResult> => {
try {
const prefix = "bearer ";
if (!event.authorizationToken?.toLowerCase().startsWith(prefix)) {
throw new Error(`Token does not start with ${prefix.trim()}`);
}
const token = event.authorizationToken?.substring(prefix.length);
const signingKey = process.env.AUTH_SIGNING_KEY;
const issuer = process.env.AUTH_ISSUER;
if (!signingKey || !issuer) {
throw new Error(`Auth properties not configure correct`);
}
const jwt = JWT.verify(token, signingKey, {
issuer,
});
if ((typeof jwt) !== "object") {
throw new Error(`The JWT has a unexpected structure`);
}
const userId = jwt["sub"];
const expiresEpochMilliSec = Number(jwt["exp"]) * 1000;
const expires = new Date(expiresEpochMilliSec);
const assumedRoles = jwt["assumed-roles"] as string[];
requiredRoles.forEach((requiredRole) => {
if (!assumedRoles.includes(requiredRole)) {
throw new Error(`The token does not contain the required role ${requiredRole}`);
}
});
if (Date.now() > expires.valueOf()) {
throw new Error(`The expired at ${expires}`);
}
const principalId = userId;
const policyDocument: PolicyDocument = {
Version: "2012-10-17",
Statement: [
{
Action: "execute-api:Invoke",
Effect: "Allow",
Resource: event.methodArn,
}
]
};
const authResponse: CustomAuthorizerResult = {
principalId,
policyDocument,
context: {
userId,
roles: assumedRoles.join(","),
}
};
return authResponse;
} catch (error) {
console.log(error);
throw "Unauthorized";
}
};
export const authorizeUser = getAuthorizerForRoles([Role.User]);
export const authorizeAdmin = getAuthorizerForRoles([Role.Admin]);
像往常一样,来自 AWS 的文档和日志消息是最低限度的..
我正在 AWS Lambda 上使用无服务器框架构建 REST 服务。我已经创建了一个自定义授权方,它在我的 lambda 的任何调用之前被调用。当我 运行 serverless-offline 时,一切正常。部署时,AP 网关出现错误。我在 API 网关中启用了登录,但没有任何内容写入日志。
这是我的 serverless.yml 文件:
provider:
name: aws
runtime: nodejs12.x
stage: ${opt:stage, "dev"}
region: eu-west-1
functions:
authorize:
handler: src/handlers/authorization.authorize
listAlerts:
handler: src/handlers/alert-handler.listAlerts
events:
- http:
path: /alerts
method: GET
authorizer: ${self:custom.authorization.authorize}
custom:
stage: ${opt:stage, self:provider.stage}
authorization:
authorize:
name: authorize
type: TOKEN
identitySource: method.request.header.Authorization
identityValidationExpression: Bearer (.*)
plugins:
- serverless-plugin-typescript
- serverless-offline
package:
include:
- src/**.*
我的授权处理程序如下所示。该方法获取我的身份验证令牌并使用 JOSE 对其进行验证,并且 return 是用户和某些角色的 principalId:
import jwksClient from "jwks-rsa";
import { JWT } from "jose";
export const authorize = async (event: CustomAuthorizerEvent): Promise<CustomAuthorizerResult> => {
const prefix = "bearer ";
if (!event.authorizationToken?.toLowerCase().startsWith(prefix)) {
return Promise.reject("Unauthorized");
}
const token = event.authorizationToken?.substring(prefix.length);
const signingKey = await getCachedSigningKey()
const jwt = JWT.verify(token, signingKey);
if ((typeof jwt) !== "object") {
throw "Unauthorized";
}
const userId = jwt["sub"];
const expires = Number(jwt["exp"]);
const roles = jwt["assumed-roles"] as string[];
if (Date.now() > expires * 1000 ) {
throw "Unauthorized";
}
const principalId = userId;
const policyDocument: PolicyDocument = {
Version: "2012-10-17",
Statement: [
{
Action: "execute-api:Invoke",
Effect: "Allow",
Resource: event.methodArn,
}
]
};
return {
principalId,
policyDocument,
context: {
userId,
roles
}
};
};
如果 serverless offline start
,模拟的 API-网关在端口 3000 上启动。我调用 API:
❯ http :3000/alerts/5480e8a1-e3d4-432d-985e-9542c91a49ce Authorization:"Bearer eyJraWQiO.......LHA12jM2UEXFy76dhKUj_iX6SXQQ"
HTTP/1.1 200 OK
Connection: keep-alive
Date: Wed, 04 Mar 2020 21:29:07 GMT
accept-ranges: bytes
access-control-allow-origin: *
cache-control: no-cache
content-length: 174
content-type: application/json; charset=utf-8
{
"id": "5480e8a1-e3d4-432d-985e-9542c91a49ce",
"message": "test",
"subjects": [
{
"id": "6ee2c07d-6486-4601-9b4b-05f61c0d0caf",
"referenceId": "5480e8a1-e3d4-432d-985e-9542c91a49ce"
}
]
}
所以,它在本地工作,我的处理程序记录了用户 ID 和角色。然后我使用 serverless deplpy
部署到 AWS,没有任何警告。我尝试调用我的 API-网关端点:
❯ http https://og8...<bla-bla>..kcc.execute-api.eu-west-1.amazonaws.com/dev/alerts/alerts/ Authorization:"Bearer eyJraWQiOiJkZXYiL.....VOPI2LHA12jM2UEXFy76dhKUj_iX6SXQQ"
HTTP/1.1 500 Internal Server Error
Connection: keep-alive
Content-Length: 16
Content-Type: application/json
Date: Wed, 04 Mar 2020 21:32:22 GMT
Via: 1.1 e31ab4c27d99cec62ef37e2607db9b45.cloudfront.net (CloudFront)
X-Amz-Cf-Id: kfIhCZHCGoL3OcjPSX4QWdtK1Qequ2vJe9RNst4wBEf_d90fJLjWgQ==
X-Amz-Cf-Pop: ARN1-C1
X-Cache: Error from cloudfront
x-amz-apigw-id: I4mu_HOFjoEF6SQ=
x-amzn-ErrorType: AuthorizerConfigurationException
x-amzn-RequestId: 3cb89b9a-26db-4890-8edb-6aedcc51c09e
{
"message": null
}
调用不会立即return,而是超时。这是怎么回事?
- 您的配置中没有这样的路由/alerts/5480e8a1-e3d4-432d-985e-9542c91a49c
- 请打开 API GW 控制台并使用测试来调试您的问题
好吧,回答我自己的问题,我在上面遇到了多个问题:
- 无法调用异步函数。
const signingKey = await getCachedSigningKey()
会失败(这可能是因为在 VPC 之外没有执行 HTTP 的权限)。 - 上下文只能包含简单映射。我试图在这里设置一个数组 (
x
),但这是不允许的。仅限简单值。
我的最终工作解决方案如下所示,如果它可以帮助任何人的话:
import { CustomAuthorizerEvent, CustomAuthorizerResult, PolicyDocument } from "aws-lambda";
import { JWT } from "jose";
export enum Role {
User = "ROLE_USER",
Admin = "ROLE_ADMIN",
}
export const getAuthorizerForRoles = (requiredRoles: Role[]) => async (event: CustomAuthorizerEvent): Promise<CustomAuthorizerResult> => {
try {
const prefix = "bearer ";
if (!event.authorizationToken?.toLowerCase().startsWith(prefix)) {
throw new Error(`Token does not start with ${prefix.trim()}`);
}
const token = event.authorizationToken?.substring(prefix.length);
const signingKey = process.env.AUTH_SIGNING_KEY;
const issuer = process.env.AUTH_ISSUER;
if (!signingKey || !issuer) {
throw new Error(`Auth properties not configure correct`);
}
const jwt = JWT.verify(token, signingKey, {
issuer,
});
if ((typeof jwt) !== "object") {
throw new Error(`The JWT has a unexpected structure`);
}
const userId = jwt["sub"];
const expiresEpochMilliSec = Number(jwt["exp"]) * 1000;
const expires = new Date(expiresEpochMilliSec);
const assumedRoles = jwt["assumed-roles"] as string[];
requiredRoles.forEach((requiredRole) => {
if (!assumedRoles.includes(requiredRole)) {
throw new Error(`The token does not contain the required role ${requiredRole}`);
}
});
if (Date.now() > expires.valueOf()) {
throw new Error(`The expired at ${expires}`);
}
const principalId = userId;
const policyDocument: PolicyDocument = {
Version: "2012-10-17",
Statement: [
{
Action: "execute-api:Invoke",
Effect: "Allow",
Resource: event.methodArn,
}
]
};
const authResponse: CustomAuthorizerResult = {
principalId,
policyDocument,
context: {
userId,
roles: assumedRoles.join(","),
}
};
return authResponse;
} catch (error) {
console.log(error);
throw "Unauthorized";
}
};
export const authorizeUser = getAuthorizerForRoles([Role.User]);
export const authorizeAdmin = getAuthorizerForRoles([Role.Admin]);
像往常一样,来自 AWS 的文档和日志消息是最低限度的..