You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AWS CDK v2配置Cognito双认证流后API Gateway客户端凭证授权失败

问题

我的API需要同时支持OAuth用户认证流(网页用户注册)和客户端凭证流(如Grafana等应用直接调用),已通过AWS CDK v2部署基础设施,但仅用户登录能正常授权,使用客户端凭证获取的access_token调用API Gateway的/my-resource GET端点时返回401 Unauthorized。请问如何解决该问题?是否与ResourceServerScope配置有关?遗漏了什么?

AuthStack 代码

export class AuthStack extends Stack {
    public userPool!: UserPool;
    private userPoolClient!: UserPoolClient;

    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props);

        this.userPool = new UserPool(this, "MyUserPool", {
            selfSignUpEnabled: true,
            signInAliases: {
                username: true,
                email: true
            }
        });

        this.userPool.addDomain("MyUserPoolDomain", {
            cognitoDomain: {
                domainPrefix: "my-auth-prototype"
            }
        });

        new CfnUserPoolGroup(this, "MyAdminPoolGroup", {
            userPoolId: this.userPool.userPoolId,
            groupName: "admin"
        });

        const clientReadServerScope = new ResourceServerScope({
            scopeName: "client.read",
            scopeDescription: "client read scope",
        });

        const resourceServer = new UserPoolResourceServer(this, "ClientCredentialsResourceServer", {
            identifier: "client-credentials-resource-server",
            userPool: this.userPool,
            scopes: [clientReadServerScope],
        });

        this.userPoolClient = this.userPool.addClient("MyUserPoolClient", {
            accessTokenValidity: Duration.minutes(60),
            generateSecret: true,
            refreshTokenValidity: Duration.days(1),
            enableTokenRevocation: true,
            oAuth: {
                flows: {
                    clientCredentials: true,
                },
                scopes: [OAuthScope.resourceServer(resourceServer, clientReadServerScope)],
            },
            authFlows: {
                adminUserPassword: true,
                custom: true,
                userPassword: true,
                userSrp: true
            }
        });
    }    
}

ApiStack 代码

export class ApiStack extends Stack {
    constructor(scope: Construct, id: string, props: ApiStackProps) {
        super(scope, id, props);

        const api = new RestApi(this, "MyApi", {
            binaryMediaTypes: ["*/*"]
        });

        //Cognito authorizor
        const authorizer = new CognitoUserPoolsAuthorizer(this, "MyApiAuthorizor", {
            cognitoUserPools: [props.userPool],
            identitySource: "method.request.header.Authorization"
        });
        authorizer._attachToApi(api);

        //Cognito options
        const optionsWithAuth: MethodOptions = {
            authorizationType: AuthorizationType.COGNITO,
            authorizer: {
                authorizerId: authorizer.authorizerId
            }
        };

        //CORS options
        const optionsWithCors: ResourceOptions = {
            defaultCorsPreflightOptions: {
                allowOrigins: Cors.ALL_ORIGINS,
                allowMethods: Cors.ALL_METHODS
            }
        };

        //base resource
        const dataPipelineResource = api.root.addResource("my-resource", optionsWithCors);

        dataPipelineResource.addMethod("GET", props.lambdaIntegration, optionsWithAuth);
    }
}

获取 access_token 的代码

export const getClientToken = async (clientId: string, clientSecret: string): Promise<string | null> => {
    const authEndpoint = "https://my-auth-prototype.auth.us-east-1.amazoncognito.com/oauth2/token";
    const res = await fetch(authEndpoint, {
        method: "POST",
        body: new URLSearchParams({
            "grant_type": "client_credentials",
        }),
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
            "Authorization": "Basic " + Buffer.from(`${clientId}:${clientSecret}`).toString("base64")
        }
    });
    const data = await res.json();
    return data.access_token;
};

解决方案

问题出在两个核心环节:API Gateway授权器未验证客户端凭证流的token权限,以及获取token时未指定所需的资源服务器scope。以下是具体修复步骤:

1. 为API Gateway授权器添加Scope验证

当前的Cognito授权器仅验证token是否来自指定用户池,未校验客户端凭证流token包含的资源服务器权限范围。需要修改ApiStack中的授权器配置,添加scopes参数:

const authorizer = new CognitoUserPoolsAuthorizer(this, "MyApiAuthorizor", {
    cognitoUserPools: [props.userPool],
    identitySource: "method.request.header.Authorization",
    // 添加Scope验证,格式为「资源服务器identifier/scopeName」
    scopes: ["client-credentials-resource-server/client.read"]
});

2. 获取客户端凭证Token时指定Scope

当前的token请求未包含所需的Scope参数,导致返回的access_token未关联资源服务器权限。需要修改getClientToken函数的请求Body,添加scope字段:

export const getClientToken = async (clientId: string, clientSecret: string): Promise<string | null> => {
    const authEndpoint = "https://my-auth-prototype.auth.us-east-1.amazoncognito.com/oauth2/token";
    const res = await fetch(authEndpoint, {
        method: "POST",
        body: new URLSearchParams({
            "grant_type": "client_credentials",
            // 添加Scope参数,与资源服务器配置一致
            "scope": "client-credentials-resource-server/client.read"
        }),
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
            "Authorization": "Basic " + Buffer.from(`${clientId}:${clientSecret}`).toString("base64")
        }
    });
    const data = await res.json();
    return data.access_token;
};

3. 验证用户池客户端权限配置(可选但重要)

确认用户池客户端的OAuth配置已正确关联资源服务器Scope,你的现有代码已配置正确,但可在AWS控制台的Cognito用户池客户端设置中检查:

  • 「客户端凭证」流已启用
  • 已关联client-credentials-resource-server/client.read这个Scope

修复逻辑说明

  • 客户端凭证流的Token必须包含资源服务器的Scope,才能被API Gateway授权器识别为有权访问指定资源
  • API Gateway的Cognito授权器需要明确指定要验证的Scope,否则只会校验Token有效性,不会校验权限范围
  • 两个环节的Scope格式必须完全一致:资源服务器identifier/scopeName

内容的提问来源于stack exchange,提问作者Tsar Bomba

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 06:34:53