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

基于AWS Cognito OAuth2实现React网站与API的身份认证问询

问题描述

我需要控制公开AWS API的访问权限,仅允许我的React网站和移动应用调用该API,其他请求均被拒绝。采用的方案是通过AWS Cognito结合OAuth2权限范围保护API,网站无需用户注册,仅用Cognito实现API安全。

目前已能通过Cognito托管UI获取code,再换取id_token,携带该token请求API可正常访问,无有效token则被拒绝,但存在以下问题:

  1. 已将token有效期设为30天,React网站如何自动刷新token?网站无需用户登录,只需携带有效id_token请求API,是否需要使用amazon-cognito-identity-js SDK获取token?
  2. 是否需要设置回调URL?仅需确保API拒绝无效或缺失token的请求,不清楚回调URL的作用。

另外请指出我方案中的其他问题。

我的CDK代码

import * as CDK from "aws-cdk-lib";

import * as CertificateManager from "aws-cdk-lib/aws-certificatemanager";
import * as Route53 from "aws-cdk-lib/aws-route53";
import * as Route53Targets from "aws-cdk-lib/aws-route53-targets";
import * as ApiGateway from "aws-cdk-lib/aws-apigateway";

import * as ELBv2 from "aws-cdk-lib/aws-elasticloadbalancingv2";
import { Construct } from "constructs";
import { StageInfo } from "../config/stage-config";
import * as Cognito from "aws-cdk-lib/aws-cognito";

export interface ApigatewayStackProps extends CDK.StackProps {
  readonly packageName: string;
  readonly stageInfo: StageInfo;
}

export class ApigatewayStack extends CDK.Stack {
  // Prefix for CDK constrcut ID
  private readonly constructIdPrefix: string;
  private readonly pandaApiCognitoUserPool: Cognito.UserPool;
  private readonly domainCertificate: CertificateManager.Certificate;
  private readonly apiAuthorizer: ApiGateway.CfnAuthorizer;
  private readonly pandaApi: ApiGateway.RestApi;
  constructor(scope: Construct, id: string, props: ApigatewayStackProps) {
    super(scope, id, props);

    this.constructIdPrefix = `${props.packageName}-${props.stageInfo.stageName}`;

    const hostedZone: Route53.IHostedZone = Route53.HostedZone.fromLookup(
      this,
      `${this.constructIdPrefix}-HostedZoneLookup`,
      {
        domainName: props.stageInfo.domainName,
      }
    );
    this.domainCertificate = new CertificateManager.Certificate(
      this,
      `${this.constructIdPrefix}-pandaApiCertificate`,
      {
        domainName: props.stageInfo.domainName,
        validation:
          CertificateManager.CertificateValidation.fromDns(hostedZone),
      }
    );

    this.pandaApi = new ApiGateway.RestApi(
      this,
      `${this.constructIdPrefix}-pandaApi`,
      {
        description: "The centralized API for panda.com",
        domainName: {
          domainName: props.stageInfo.domainName,
          certificate: this.domainCertificate,
          //mappingKey: props.pipelineStageInfo.stageName
        },

        defaultCorsPreflightOptions: {
          allowOrigins: ApiGateway.Cors.ALL_ORIGINS,
          allowMethods: [...ApiGateway.Cors.DEFAULT_HEADERS],
        },
      }
    );

    new Route53.ARecord(this, "AliasRecord", {
      zone: hostedZone,
      target: Route53.RecordTarget.fromAlias(
        new Route53Targets.ApiGateway(this.pandaApi)
      ),
      // or - route53.RecordTarget.fromAlias(new alias.ApiGatewayDomain(domainName)),
    });

    this.pandaApiCognitoUserPool = new Cognito.UserPool(this, "UserPool", {
      userPoolName: `pandaApiUserPool`,
      selfSignUpEnabled: false,
    });

    this.apiAuthorizer = new ApiGateway.CfnAuthorizer(
      this,
      `${this.constructIdPrefix}-pandaApiAuthorizer`,
      {
        name: "pandaApiAuthorizer",
        type: ApiGateway.AuthorizationType.COGNITO,
        identitySource: "method.request.header.Authorization",
        restApiId: this.pandaApi.restApiId,
        providerArns: [this.pandaApiCognitoUserPool.userPoolArn],
      }
    );

    this.addCognitoAuthentication(props);
  }

  private addCognitoAuthentication(props: ApigatewayStackProps) {
    this.pandaApiCognitoUserPool.addDomain("DomainName", {
      cognitoDomain: {
        domainPrefix: `panda-api-user-pool-${props.stageInfo.stageName.toLocaleLowerCase()}`,
      },
      
    });

    this.pandaApiCognitoUserPool.addClient(
      `${this.constructIdPrefix}-pandaApiUserPoolClient`,
      {
        userPoolClientName: `pandaApiUserPoolClient`,
        generateSecret: true,
        oAuth: {
          flows: {
            // It's highly recommend to use only the Authorization code grant flow.
            // https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-idp-settings.html
            authorizationCodeGrant: true,
          },
          scopes: [Cognito.OAuthScope.OPENID],
          //callbackUrls: [props.stageInfo.domainName + '/callback']
        },
        authFlows: {
          userPassword: true,
        },

        refreshTokenValidity: CDK.Duration.days(30),
      }
    );
  }

}

解决方案与问题分析

问题1:React网站自动刷新token及SDK使用

  • 自动刷新逻辑:使用Authorization Code Flow换取token时,除了id_token,还会获得refresh_token。当id_token即将过期时,可携带grant_type=refresh_token、refresh_token、客户端ID(若客户端有secret则需同时携带),向Cognito的/oauth2/token端点请求新的id_token和access_token。
  • SDK选择:可以使用amazon-cognito-identity-js SDK,它封装了token刷新、过期判断、存储管理等逻辑,无需手动编写HTTP请求;若不想引入SDK,也可自行调用Cognito的token端点实现刷新,但需要自己处理token有效期校验、安全存储等细节。
  • 前端存储注意:因为无需用户登录,需将refresh_token安全存储在前端,建议用HttpOnly Cookie存储,避免XSS攻击风险,同时定期检查id_token的过期时间,提前触发刷新。

问题2:回调URL的必要性

  • 回调URL是Authorization Code Flow的必需配置。当用户通过Cognito托管UI完成授权后,Cognito需要将授权code发送回你的应用,这个接收地址就是回调URL。如果不配置,Cognito无法完成授权流程,你根本无法获取到code,也就无法换取token。
  • 配置要求:回调URL必须是你的React应用的合法地址(例如https://your-domain.com/callback),需要在Cognito用户池客户端设置中添加该URL,且请求授权时传入的redirect_uri参数必须与配置的回调URL完全一致。

方案中的其他问题

  1. CORS配置风险:当前API Gateway设置了allowOrigins: ApiGateway.Cors.ALL_ORIGINS,意味着任何域名都能发起跨域请求到你的API。虽然有token验证做兜底,但建议将允许的源限制为你的React网站和移动应用的域名,减少不必要的暴露面。
  2. 客户端Secret不适合SPA:你给Cognito用户池客户端设置了generateSecret: true,但React是单页应用,无法安全存储客户端Secret(前端代码中的Secret极易被提取)。对于SPA,应创建无Secret的客户端(generateSecret: false),避免Secret泄露带来的安全风险。
  3. 授权流程选型不符:Authorization Code Flow本质是面向用户身份的授权流程,但你的需求是无需用户登录的API访问,更适合使用客户端凭证流(Client Credentials Flow)——该流程直接用客户端ID和Secret换取token,无需用户交互,更贴合你的场景。
  4. API授权器未绑定:你创建了Cognito授权器,但未将其绑定到API的具体资源方法上。需要在API的资源方法配置中设置authorizationType: COGNITO_USER_POOLS并指定授权器ID,否则授权器不会生效。
  5. Token有效期过长:id_token设为30天有效期过长,会增加token泄露后的风险。建议缩短id_token有效期(例如1小时),用refresh_token定期刷新,缩小token泄露后的危害时长。
  6. OAuth范围缺失权限控制:当前仅设置了OPENID范围,若API需要细粒度的权限控制,应自定义OAuth范围,并在授权器中验证这些范围,确保只有携带正确范围的token才能访问对应API资源。

内容的提问来源于stack exchange,提问作者Yang Liu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 16:35:20