我在AWS CDK v2中有一个带有自定义授权器的ApiGateway RestApi。现在我想创建一个带有授权器的WebSocket。
我从Stack 3: Api Gateway Websocket API AWS CDK Stack Walk-thru指南开始,它让我创建了ApiGatewayV2 WebSocket。我正在努力弄清楚如何为它创建一个自定义授权器。
我有一些问题:
- 我可以使用ApiGatewayV2 CfnAuthoriser的相同授权器功能吗?
- 授权是否需要以某种WebSocket风格进行?
- 如何从前端应用程序使用WebSocket进行授权?它只是像HTTP请求中的身份验证头吗?
我有一个艰难的时间谷歌它,不断得到CDK v1的文章。如果有人有一些时间来指出我在正确的方向,我真的很感激它。
- 主栈 *
export class ThingCdkStack extends Stack {
private authoriserLogicalId: string;
constructor(scope: Construct, id: string, props: StackProps, private envs: Environment) {
super(scope, id, props);
const api = new RestApi(this, 'ThingApi');
const role = new Role(this, 'ThingRole', {
roleName: 'thing-role',
assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
inlinePolicies: {
allowLambdaInvocation: PolicyDocument.fromJson({
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Action: ['lambda:InvokeFunction', 'lambda:InvokeAsync'],
Resource: `arn:aws:lambda:${envs.REGION}:${envs.ACCOUNT}:function:*`,
},
],
}),
},
});
const authorizerHandler = new NodejsFunction(this, 'ThingCustomAuthorizer', {
entry: 'lambda/handlers/auth/auth0-authoriser.ts',
runtime: Runtime.NODEJS_18_X,
environment: {
AUTH0_ISSUER: 'https://my-auth.eu.auth0.com/',
AUTH0_AUDIENCE: 'https://my-demo.com',
REGION: envs.REGION,
ACCOUNT: envs.ACCOUNT,
}
});
const authorizer = new CfnAuthorizer(this, 'ThingAuthoriser', {
restApiId: api.restApiId,
type: 'TOKEN',
name: 'thing-authoriser',
identitySource: 'method.request.header.Authorization',
authorizerUri: `arn:aws:apigateway:${envs.REGION}:lambda:path/2015-03-31/functions/${authorizerHandler.functionArn}/invocations`,
authorizerCredentials: role.roleArn
});
this.authoriserLogicalId = authorizer.logicalId;
const createThingHandler = new NodejsFunction(this, 'CreateThingLambda', {
entry: 'lambda/handlers/thing/create-thing.ts',
runtime: Runtime.NODEJS_18_X,
});
this.addAuthMethod('post', api.addResource('thing'), createThingHandler);
this.addWebsocket(envs, authorizer);
}
private addAuthMethod(method: string, resource: Resource, handler: NodejsFunction, integrationOptions?: LambdaIntegrationOptions) {
const route = resource.addMethod(
method,
new LambdaIntegration(handler, integrationOptions),
{
authorizationType: AuthorizationType.CUSTOM,
}
);
const childResource = route.node.findChild('Resource');
(childResource as CfnResource).addPropertyOverride('AuthorizationType', AuthorizationType.CUSTOM);
(childResource as CfnResource).addPropertyOverride('AuthorizerId', {Ref: this.authoriserLogicalId});
}
private addWebsocket(environment: Environment, authorizer: CfnAuthorizer) {
const connectionsTable = new Table(this, 'ConnectionsTable', {
partitionKey: {name: 'connectionId', type: AttributeType.STRING},
readCapacity: 2,
writeCapacity: 1,
timeToLiveAttribute: "ttl"
});
const commonHandlerProps: NodejsFunctionProps = {
bundling: {minify: true, sourceMap: true, target: 'es2019'},
runtime: Runtime.NODEJS_18_X,
logRetention: RetentionDays.THREE_DAYS
};
const connectHandler = new NodejsFunction(this, 'ConnectHandler', {
...commonHandlerProps,
entry: 'lambda/websocket/connect.ts',
environment: {
CONNECTIONS_TBL: connectionsTable.tableName
}
});
const defaultHandler = new NodejsFunction(this, 'defaultHandler', {
...commonHandlerProps,
entry: 'lambda/websocket/default.ts',
environment: {
CONNECTIONS_TBL: connectionsTable.tableName
}
});
const disconnectHandler = new NodejsFunction(this, 'DisconnectHandler', {
...commonHandlerProps,
entry: 'lambda/websocket/disconnect.ts',
environment: {
CONNECTIONS_TBL: connectionsTable.tableName
}
});
const websocketApi = new WebsocketApi(this, "CompletionWebsocketApi", {
apiName: "completions-api",
apiDescription: "Web Socket API for Completions",
stageName: environment.STAGE,
connectHandler,
disconnectHandler,
defaultHandler,
connectionsTable
});
const CONNECTION_URL = `https://${websocketApi.api.ref}.execute-api.${Aws.REGION}.amazonaws.com/${environment.STAGE}`;
const completionHandler = new NodejsFunction(this, 'CompletionHandler', {
...commonHandlerProps,
entry: 'lambda/websocket/completions.ts',
environment: {
CONNECTION_TBL: connectionsTable.tableName,
CONNECTION_URL: CONNECTION_URL
},
});
websocketApi.addLambdaIntegration(completionHandler, 'completions', 'CompletionsRoute')
const managementApiPolicyStatement = new PolicyStatement({
effect: Effect.ALLOW,
actions: ["execute-api:ManageConnections"],
resources: [`arn:aws:execute-api:${Aws.REGION}:${Aws.ACCOUNT_ID}:${websocketApi.api.ref}/*`]
})
defaultHandler.addToRolePolicy(managementApiPolicyStatement);
completionHandler.addToRolePolicy(managementApiPolicyStatement);
new CfnOutput(this, 'WebsocketConnectionUrl', {value: CONNECTION_URL});
const websocketApiUrl = `${websocketApi.api.attrApiEndpoint}/${environment.STAGE}`
new CfnOutput(this, "websocketUrl", {
value: websocketApiUrl
});
}
}
- WebsocketApi构造 *
import { ITable } from "aws-cdk-lib/aws-dynamodb";
import { IFunction } from "aws-cdk-lib/aws-lambda";
import { CfnApi, CfnIntegration, CfnRoute, CfnStage, CfnDeployment } from "aws-cdk-lib/aws-apigatewayv2";
import { ServicePrincipal } from "aws-cdk-lib/aws-iam";
import { Aws, Stack } from 'aws-cdk-lib';
import { Construct } from 'constructs';
export interface WebsocketApiProps {
readonly apiName: string;
readonly apiDescription: string;
readonly stageName: string;
readonly connectHandler: IFunction;
readonly disconnectHandler: IFunction;
readonly connectionsTable: ITable;
readonly defaultHandler?: IFunction;
}
export class WebsocketApi extends Construct {
readonly props: WebsocketApiProps;
readonly api: CfnApi;
readonly deployment: CfnDeployment;
constructor(parent: Stack, name: string, props: WebsocketApiProps) {
super(parent, name);
this.props = props;
this.api = new CfnApi(this, 'CompletionsWebSocketApi', {
name: props.apiName,
description: props.apiDescription,
protocolType: "WEBSOCKET",
routeSelectionExpression: "$request.body.action",
});
this.deployment = new CfnDeployment(this, "WebsocketDeployment", {
apiId: this.api.ref,
});
new CfnStage(this, "WebsocketStage", {
stageName: props.stageName,
apiId: this.api.ref,
deploymentId: this.deployment.ref,
});
props.connectionsTable.grantWriteData(props.connectHandler);
props.connectionsTable.grantWriteData(props.disconnectHandler);
this.addLambdaIntegration(props.connectHandler, "$connect", "ConnectionRoute");
this.addLambdaIntegration(props.disconnectHandler, "$disconnect", "DisconnectRoute");
if(props.defaultHandler) {
props.connectionsTable.grantWriteData(props.defaultHandler);
this.addLambdaIntegration(props.defaultHandler, "$default", "DefaultRoute");
}
}
addLambdaIntegration(handler: IFunction, routeKey: string, operationName: string, apiKeyRequired?: boolean, authorizationType?: string) {
const integration = new CfnIntegration(this, `${operationName}Integration`, {
apiId: this.api.ref,
integrationType: "AWS_PROXY",
integrationUri: `arn:aws:apigateway:${Aws.REGION}:lambda:path/2015-03-31/functions/${handler.functionArn}/invocations`
});
handler.grantInvoke(new ServicePrincipal('apigateway.amazonaws.com', {
conditions: {
"ArnLike": {
"aws:SourceArn": `arn:aws:execute-api:${Aws.REGION}:${Aws.ACCOUNT_ID}:${this.api.ref}/*/*`
}
}
}));
this.deployment.addDependency(new CfnRoute(this, `${operationName}Route`, {
apiId: this.api.ref,
routeKey,
apiKeyRequired,
authorizationType: authorizationType || "NONE",
operationName,
target: `integrations/${integration.ref}`
}));
}
}
到目前为止,我所拥有的完整工作堆栈可以在这里找到:https://github.com/OrderAndCh4oS/aws-cdk-v2-apigatewayv2-websocket-demo
更新
我发现了一些有用的文档,我现在正在研究:https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-lambda-auth.html
我们还找到了一个使用Cognito的示例项目:https://github.com/aws-samples/websocket-api-cognito-auth-sample
1条答案
按热度按时间4dc9hkyq1#
已经取得了一些进展,它仍然是粗糙的边缘,但至少是工作。
我创建了一个ApiGatewayV 2 CfnAuthoriser,并将其与$connect路由上的
deployment.addDependency()
连接起来,并将authorizationType
设置为CUSTOM
。处理程序只是一个常规的自定义授权器lambda,但我必须使用一个querystring参数来传递令牌。
调用WebSocket时,在查询字符串上传入授权令牌
const socket = new WebSocket(
wss://xxxxxxxxxx.execute-api.eu-west-1.amazonaws.com/dev?auth=${token}`);”主栈
WebSocket API栈
授权处理人
我为此做了一个临时演示项目,可能对某人有用,也可能对某人没用:https://github.com/OrderAndCh4oS/aws-cdk-v2-apigatewayv2-websocket-demo
将离开的问题开放的情况下,有人能够改善这一点,它肯定需要工作仍然。