如何在AWS CDK v2的ApiGatewayV2 Websocket中添加自定义授权器?
我来帮你一步步拆解这个问题,结合你现有的代码梳理清楚每个关键点:
一、能不能复用现有的自定义授权器函数?
完全可以!你之前写的Auth0验证Lambda函数逻辑可以直接复用,只需要调整CDK中授权器的配置——因为WebSocket API的授权器和REST API的CfnAuthorizer结构略有不同,但核心的认证逻辑(比如验证JWT)不需要改动。
二、WebSocket授权的特殊之处?
WebSocket的授权逻辑只在$connect路由触发——这是客户端建立连接时的唯一校验机会,一旦连接成功建立,后续的自定义路由(比如你写的completions)不会再自动触发授权(如果需要二次校验,得自己在业务Lambda里处理)。
另外,WebSocket的身份信息来源和REST API不同,它支持从握手请求的header或者query参数里取认证token,因为WebSocket的连接本质是一次HTTP握手请求。
三、前端怎么授权WebSocket连接?
和HTTP请求类似,前端建立WebSocket连接时可以通过两种方式传认证信息:
- 查询参数:比如
wss://your-api.execute-api.region.amazonaws.com/stage?Authorization=Bearer<你的token> - 请求头:部分WebSocket客户端(比如
ws库)支持自定义握手header,写法类似headers: { Authorization: 'Bearer <你的token>' }(注意:浏览器原生WebSocket API不支持自定义header,这种情况只能用查询参数)
四、具体的CDK v2代码修改步骤
结合你现有的代码,我们一步步调整:
1. 为WebSocket创建专属的CfnAuthorizer
在你的addWebsocket方法中,添加WebSocket授权器的配置(复用之前的Lambda函数和角色):
// 复用你之前定义的authorizerHandler和role const websocketAuthorizer = new CfnAuthorizer(this, 'WebsocketThingAuthoriser', { apiId: websocketApi.api.ref, // 这里必须填WebSocket API的ref,不是REST API的 name: 'websocket-thing-authoriser', type: 'REQUEST', // WebSocket自定义授权器必须用REQUEST类型,不能用REST API的TOKEN类型 authorizerUri: `arn:aws:apigateway:${environment.REGION}:lambda:path/2015-03-31/functions/${authorizerHandler.functionArn}/invocations`, authorizerCredentials: role.roleArn, // 复用之前创建的role即可 identitySource: [ '$request.header.Authorization', // 支持从header取token '$request.querystring.Authorization' // 同时支持从查询参数取token,可按需选择 ] });
2. 修改$connect路由的授权配置
调整你的WebsocketApi类中的addLambdaIntegration方法,让它支持传入授权器ID,然后在创建$connect路由时关联授权器:
addLambdaIntegration(handler: IFunction, routeKey: string, operationName: string, apiKeyRequired?: boolean, authorizationType?: string, authorizerId?: 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}/*/*` } } })); const routeProps: CfnRouteProps = { apiId: this.api.ref, routeKey, apiKeyRequired, authorizationType: authorizationType || "NONE", operationName, target: `integrations/${integration.ref}` }; // 如果传入授权器ID,添加到路由配置 if (authorizerId) { routeProps.authorizerId = authorizerId; } this.deployment.addDependency(new CfnRoute(this, `${operationName}Route`, routeProps)); }
然后在创建$connect路由时,指定授权类型和授权器:
// 在WebsocketApi构造函数的$connect路由处修改: this.addLambdaIntegration( props.connectHandler, "$connect", "ConnectionRoute", false, "CUSTOM", // 指定授权类型为自定义 websocketAuthorizer.ref // 关联我们创建的WebSocket授权器 );
3. 调整授权器Lambda函数的返回格式
WebSocket授权器的返回格式和REST API的不一样,需要改成{ isAuthorized: boolean, context?: object }的结构,所以你的auth0-authoriser.ts需要调整:
export const handler = async (event: any) => { // 从header或query参数中获取token const token = event.headers?.Authorization || event.queryStringParameters?.Authorization; if (!token) { return { isAuthorized: false, context: {} }; } // 保留你原来的Auth0验证逻辑... const isValid = await validateToken(token); return { isAuthorized: isValid, context: { // 可选:返回上下文信息,后续业务Lambda可通过event.requestContext.authorizer获取 userId: 'xxx' } }; };
五、验证和测试
- 部署修改后的CDK栈
- 前端连接WebSocket时,通过header或查询参数带上Authorization token
- 如果授权失败,API Gateway会直接拒绝连接并返回403
- 授权成功后,连接建立,后续的消息会携带
requestContext.authorizer里的上下文信息
备注:内容来源于stack exchange,提问作者OrderAndChaos

