我想控制对公共AWS API的访问,这意味着只有我的网站和移动应用程序可以使用这个API,否则API将拒绝请求。
我的方法是使用AWS Cognito与OAuth2作用域来保护API,在这里查看更多细节。该网站不需要用户注册,所以我使用cognito只是为了保护API.
到目前为止,我能够在AWS Cognito的App client settings
下找到Hosted UI
。点击链接登录,并获得一个"代码":https://example.com/?code=f27b2de0-1111-1111-1111-11111111。然后按照"如何使用从Cognito返回的代码来获取AWS凭据?"得到id_token
。最后发送一个报头为key=Authorization
和value=<id_token>
的HTTP请求。这对我很有效。我看到API在没有有效令牌时拒绝请求,并在存在有效令牌时返回预期的结果。
然而,我有一些问题:
- 令牌在30天内到期(我配置为如此),我的网站是一个reactJS应用程序,网站如何刷新令牌?我的网站不应该在这里关心任何登录,它只需要发送一个有效的ID令牌作为授权头来访问API。用户不应该看到任何登录页面。我应该在我的react应用程序中使用Cognito SDK amazon-cognito-identity-js来获取ID令牌吗?
- 我需要在这里设置回调url吗?所有我需要的是确保API将拒绝请求,如果令牌丢失或无效,我不知道在这种情况下有回调url的目的是什么。
请指出我在这里犯的任何错误。
示例代码这是我的CDK代码来设置API + Cognito。
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),
}
);
}
}
Cognito是用于登录您的用户并为您提供access token
和id token
作为结果。您的用户必须登录。如果他们不登录,那么你就不能使用cognito来保护你的API。此外,如果你不需要登录,那么世界上任何人都可以调用你的API。
没有办法确保只有你的网站和你的移动应用程序可以与你的API交谈,这不是API的工作方式。api对全世界都是可用的,它们都是公共的,你不能阻止对它们的请求,除非你的用户单独认证。
这样做的原因是任何人都可以欺骗你的网站或移动应用程序,因为没有安全机制存在,可以限制访问。
所以要么你的API是世界可写/可读的,要么你要求你的用户登录。好消息是有很多方法可以轻松做到这一点,现在AuthN提供商的日志支持webauthn/fido2/passkeys。
你也许可以在谷歌上找到一个提供你所需要的服务的提供商列表,但是一个入门列表是关于如何选择最好的认证提供商的。