基于AWS Cognito OAuth2实现React网站与API的身份认证问询
问题描述
我需要控制公开AWS API的访问权限,仅允许我的React网站和移动应用调用该API,其他请求均被拒绝。采用的方案是通过AWS Cognito结合OAuth2权限范围保护API,网站无需用户注册,仅用Cognito实现API安全。
目前已能通过Cognito托管UI获取code,再换取id_token,携带该token请求API可正常访问,无有效token则被拒绝,但存在以下问题:
- 已将token有效期设为30天,React网站如何自动刷新token?网站无需用户登录,只需携带有效id_token请求API,是否需要使用
amazon-cognito-identity-jsSDK获取token? - 是否需要设置回调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-jsSDK,它封装了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完全一致。
方案中的其他问题
- CORS配置风险:当前API Gateway设置了
allowOrigins: ApiGateway.Cors.ALL_ORIGINS,意味着任何域名都能发起跨域请求到你的API。虽然有token验证做兜底,但建议将允许的源限制为你的React网站和移动应用的域名,减少不必要的暴露面。 - 客户端Secret不适合SPA:你给Cognito用户池客户端设置了
generateSecret: true,但React是单页应用,无法安全存储客户端Secret(前端代码中的Secret极易被提取)。对于SPA,应创建无Secret的客户端(generateSecret: false),避免Secret泄露带来的安全风险。 - 授权流程选型不符:Authorization Code Flow本质是面向用户身份的授权流程,但你的需求是无需用户登录的API访问,更适合使用客户端凭证流(Client Credentials Flow)——该流程直接用客户端ID和Secret换取token,无需用户交互,更贴合你的场景。
- API授权器未绑定:你创建了Cognito授权器,但未将其绑定到API的具体资源方法上。需要在API的资源方法配置中设置
authorizationType: COGNITO_USER_POOLS并指定授权器ID,否则授权器不会生效。 - Token有效期过长:id_token设为30天有效期过长,会增加token泄露后的风险。建议缩短id_token有效期(例如1小时),用refresh_token定期刷新,缩小token泄露后的危害时长。
- OAuth范围缺失权限控制:当前仅设置了
OPENID范围,若API需要细粒度的权限控制,应自定义OAuth范围,并在授权器中验证这些范围,确保只有携带正确范围的token才能访问对应API资源。
内容的提问来源于stack exchange,提问作者Yang Liu
相关产品推荐
相关产品推荐

