- 示例工程
- 教程
- 后端
【免费下载链接】aws-doc-sdk-examples
Welcome to the AWS Code Examples Repository. This repo contains code examples used in the AWS documentation, AWS SDK Developer Guides, and more. For more information, see the Readme.md file below.
导读
本指南以 kotlin/services/cognito 目录下的 AWS SDK for Kotlin 示例代码为蓝本,系统讲解如何用 Kotlin 调用 Amazon Cognito 的两大核心服务:Cognito Identity(身份池)与Cognito Identity Provider(用户池)。你将掌握身份池的创建、列举、删除与成员查询,用户池的创建、描述、删除、用户管理与注册,以及一套完整的"注册 → 确认 → 登录 → 绑定 TOTP 多因素认证(MFA)"端到端场景,并学会如何在本地运行这些示例、用 JUnit 5 驱动真实 AWS 资源测试。
概述:Amazon Cognito 在 Kotlin 示例中的定位
Amazon Cognito 是一项"用户身份与数据同步"服务,帮助你在移动设备与 Web 应用之间安全地管理、同步用户的身份数据。在本仓库中,Kotlin 示例将其拆分为两个职责互补的 API:
- CognitoIdentityClient:面向身份池(identity pool),用于为未经认证或通过第三方身份提供商认证的用户签发临时 AWS 凭证,让客户端可以安全访问 S3、DynamoDB 等 AWS 资源。
- CognitoIdentityProviderClient:面向用户池(user pool),提供完整的用户目录能力,包括注册、登录、密码管理、属性配置与 MFA 等。
两类客户端在 build.gradle.kts 中分别对应aws.sdk.kotlin:cognitoidentity与aws.sdk.kotlin:cognitoidentityprovider两个依赖,统一由aws.sdk.kotlin:bom:1.5.63平台约束管理版本。
环境准备与重要注意事项
运行本目录示例前,需要先按 AWS SDK for Kotlin 的官方指引完成开发环境配置(包括设置凭证)。示例全部使用CognitoIdentityClient.fromEnvironment { region = "us-east-1" }这种"从环境读取凭证 + 显式指定区域"的客户端构建方式。
⚠️ 以下几点务必留意:
- 这些 Kotlin 示例会针对你凭证所对应的 AWS 账户与区域执行真实操作,运行可能产生AWS 服务费用;运行测试同样可能产生费用。
- 部分示例属于破坏性操作,例如
deleteUserPool(删除用户池)、deleteIdentityPool(删除身份池)。操作前请务必小心,建议使用独立的、仅用于测试的资源,避免影响生产数据。 - 建议遵循least privilege(最小权限)原则,仅授予执行任务所需的最低权限。
- 示例并未在全部 AWS 区域完成测试,
us-east-1之外请自行验证可用性。
一、身份池操作:CognitoIdentityClient 四个示例
以下四个示例均使用CognitoIdentityClient,源码位于 src/main/kotlin/com/kotlin/cognito 包com.kotlin.cognito下。
1. 创建身份池(createIdentityPool)
文件:CreateIdentityPool.kt
命令行运行方式:<identityPoolName>(要创建的身份池名称)。核心实现如下:
suspend fun createIdPool(identityPoolName: String?): String? { val request = CreateIdentityPoolRequest { this.allowUnauthenticatedIdentities = false this.identityPoolName = identityPoolName } CognitoIdentityClient.fromEnvironment { region = "us-east-1" }.use { cognitoIdentityClient -> val response = cognitoIdentityClient.createIdentityPool(request) return response.identityPoolId } }关键参数:
identityPoolName:身份池名称,作为池的唯一业务标识。allowUnauthenticatedIdentities:是否允许未认证身份访问池。示例中设为false,即要求所有访问者都经过认证;如果你的场景需要支持匿名访客(例如公开读区的移动 App),可设为true。- 返回值
identityPoolId形如us-east-1:xxxx-xxxx-xxxx,后续删除身份池、列举身份时都要用到。
2. 删除身份池(deleteIdentityPool)
文件:DeleteIdentityPool.kt
命令行运行方式:<identityPoolName>——注意这里传入的实际是身份池 ID(即上一步返回的identityPoolId)。
suspend fun deleteIdPool(identityPoold: String?) { val request = DeleteIdentityPoolRequest { this.identityPoolId = identityPoold } CognitoIdentityClient.fromEnvironment { region = "us-east-1" }.use { cognitoIdclient -> cognitoIdclient.deleteIdentityPool(request) println("The identity pool was successfully deleted") } }这是一个典型的破坏性操作:删除后该身份池及其关联配置将不可恢复,生产环境请勿随意执行。
3. 列举身份池(listIdentityPools)
文件:ListIdentityPools.kt
无命令行参数,直接运行。实现展示了分页字段maxResults的用法:
suspend fun getPools() { val request = ListIdentityPoolsRequest { maxResults = 10 } CognitoIdentityClient.fromEnvironment { region = "us-east-1" }.use { cognitoIdentityClient -> val response = cognitoIdentityClient.listIdentityPools(request) response.identityPools?.forEach { pool -> println("The identity pool name is ${pool.identityPoolName}") } } }maxResults控制单次返回的池数量上限(示例取 10)。当身份池数量较多时,可从 SDK 分页器进一步翻页获取全部结果。
4. 列举身份池中的身份(listIdentities)
文件:ListIdentities.kt
命令行运行方式:<identityPoolId>,例如us-east-1:00eb915b-c521-417b-af0d-ebad008axxxx。
suspend fun listPoolIdentities(identityPoolId: String?) { val request = ListIdentitiesRequest { this.identityPoolId = identityPoolId maxResults = 15 } CognitoIdentityClient.fromEnvironment { region = "us-east-1" }.use { cognitoIdentityClient -> val response = cognitoIdentityClient.listIdentities(request) response.identities?.forEach { identity -> println("The identity Id value is ${identity.identityId}") } } }ListIdentitiesRequest同时设置了identityPoolId与maxResults(上限 15),返回结果中的identities列表携带每个身份的identityId。这是排查"哪些客户端身份正在使用某个身份池"的常用手段。
二、用户池操作:CognitoIdentityProviderClient 七个示例
以下示例均使用CognitoIdentityProviderClient,管理用户池(user pool)及其用户目录。
1. 创建用户池(createUserPool)
文件:CreateUserPool.kt
命令行运行方式:<userPoolName>(用户池名称)。
suspend fun createPool(userPoolName: String): String? { val request = CreateUserPoolRequest { this.poolName = userPoolName } CognitoIdentityProviderClient.fromEnvironment { region = "us-east-1" }.use { cognitoClient -> val createUserPoolResponse = cognitoClient.createUserPool(request) return createUserPoolResponse.userPool?.id } }CreateUserPoolRequest在示例中仅设置了poolName,其余大量策略参数(密码策略、Schema、MFA 配置等)均采用 AWS 默认值。createUserPoolResponse.userPool?.id返回新池 ID,它是后续所有用户池操作的入参。
2. 删除用户池(deleteUserPool)
文件:DeleteUserPool.kt
命令行运行方式:<userPoolId>。
suspend fun delPool(userPoolId: String) { val request = DeleteUserPoolRequest { this.userPoolId = userPoolId } CognitoIdentityProviderClient.fromEnvironment { region = "us-east-1" }.use { cognitoClient -> cognitoClient.deleteUserPool(request) print("$userPoolId was successfully deleted") } }删除用户池属于高危破坏性操作,会连同池内全部用户一并移除,务必谨慎。
3. 获取用户池信息(describeUserPool)
文件:DescribeUserPool.kt
命令行运行方式:<userPoolId>。
suspend fun describePool(userPoolId: String) { val request = DescribeUserPoolRequest { this.userPoolId = userPoolId } CognitoIdentityProviderClient.fromEnvironment { region = "us-east-1" }.use { cognitoClient -> val response = cognitoClient.describeUserPool(request) val poolARN = response.userPool?.arn println("The user pool ARN is $poolARN") } }describeUserPool返回用户池的完整配置快照,示例仅提取了userPool.arn并打印。该 ARN 常用于给其他服务(如 API Gateway、Lambda)授予访问用户池的权限。
4. 列举用户池(listUserPools)
文件:ListUserPools.kt
无命令行参数,直接运行:
suspend fun getAllPools() { val request = ListUserPoolsRequest { maxResults = 10 } CognitoIdentityProviderClient.fromEnvironment { region = "us-east-1" }.use { cognitoClient -> val response = cognitoClient.listUserPools(request) response.userPools?.forEach { pool -> println("The user pool name is ${pool.name}") } } }与listIdentityPools类似,通过maxResults控制单页返回数量。
5. 列举用户池客户端(listUserPoolClients)
文件:ListUserPoolClients.kt
该文件虽未被 README 单列,但被 CognitoKotlinTest.kt 的测试 4、测试 5 直接调用,属于身份提供商场景的重要补充。命令行运行方式:<userPoolId>。
suspend fun listAllUserPoolClients(userPoolId: String) { val request = ListUserPoolClientsRequest { this.userPoolId = userPoolId } CognitoIdentityProviderClient.fromEnvironment { region = "us-east-1" }.use { cognitoClient -> val response = cognitoClient.listUserPoolClients(request) response.userPoolClients?.forEach { pool -> println("Client ID is ${pool.clientId}") println("Client Name is ${pool.clientName}") } } }clientId与clientName正是后续SignUpUser、CognitoMVP场景中注册和登录所必需的凭据,因此该示例是打通"用户池 → 应用客户端"链路的关键一环。
6. 管理员创建用户(adminCreateUser)
文件:CreateUser.kt
命令行运行方式:<userPoolId> <userName> <email> <password>,共 4 个参数。
suspend fun createNewUser( userPoolId: String, name: String, email: String, password: String, ) { val attType = AttributeType { this.name = "email" value = email } val request = AdminCreateUserRequest { this.userPoolId = userPoolId username = name temporaryPassword = password userAttributes = listOf(attType) } CognitoIdentityProviderClient.fromEnvironment { region = "us-east-1" }.use { cognitoClient -> val response = cognitoClient.adminCreateUser(request) println("User ${response.user?.username} is created. Status is ${response.user?.userStatus}") } }要点说明:
- 通过
AttributeType给新用户附加email属性,用于账户验证。 temporaryPassword表示这是临时密码,密码规则要求包含大写字母、小写字母、数字及至少一个特殊字符;用户首次登录时通常会被要求设置新密码。adminCreateUser是管理员通道,不需要用户先完成自助注册,适合后台批量开号场景。- 返回的
userStatus可用于判断用户当前处于FORCE_CHANGE_PASSWORD等状态。
7. 自助注册用户(signUp)
文件:SignUpUser.kt
命令行运行方式:<clientId> <secretkey> <userName> <password> <email>,共 5 个参数。clientId与secretkey(App Client Secret)均可从 AWS 管理控制台的应用客户端设置中获取。
suspend fun signUp( clientIdVal: String, secretKey: String, userName: String, passwordVal: String, email: String, ) { val attributeType = AttributeType { this.name = "email" this.value = email } val attrs = mutableListOf<AttributeType>() attrs.add(attributeType) val secretVal = calculateSecretHash(clientIdVal, secretKey, userName) val request = SignUpRequest { userAttributes = attrs username = userName clientId = clientIdVal password = passwordVal secretHash = secretVal } CognitoIdentityProviderClient.fromEnvironment { region = "us-east-1" }.use { identityProviderClient -> identityProviderClient.signUp(request) println("User has been signed up") } }与adminCreateUser不同,signUp是面向终端用户的自助注册通道,且当应用客户端配置了 Secret 时,必须在请求中携带secretHash。示例提供了完整的Secret Hash 计算函数,基于 HMAC-SHA256:
fun calculateSecretHash( userPoolClientId: String, userPoolClientSecret: String, userName: String, ): String { val macSha256Algorithm = "HmacSHA256" val signingKey = SecretKeySpec( userPoolClientSecret.toByteArray(StandardCharsets.UTF_8), macSha256Algorithm, ) try { val mac = Mac.getInstance(macSha256Algorithm) mac.init(signingKey) mac.update(userName.toByteArray(StandardCharsets.UTF_8)) val rawHmac = mac.doFinal(userPoolClientId.toByteArray(StandardCharsets.UTF_8)) return Base64.getEncoder().encodeToString(rawHmac) } catch (e: UnsupportedEncodingException) { println(e.message) } return "" }实现原理:以userPoolClientSecret作为 HMAC 密钥,将userName与userPoolClientId拼接后经HmacSHA256计算摘要,再以 Base64 编码输出。凡是注册、确认注册、登录等涉及客户端 Secret 的请求,都必须附带这个 hash。
三、端到端场景:CognitoMVP 与 TOTP MFA
文件:CognitoMVP.kt 是本目录唯一的完整场景示例,演示"注册新用户 + 为 MFA 绑定认证器 App"的完整流程。其注释明确说明:运行前需要先用仓库提供的 AWS CDK 脚本resources/cdk/cognito_scenario_user_pool_with_mfa创建好带 MFA 的用户池,并从中取得clientId与poolId作为命令行参数。
场景执行流程
命令行运行方式:<clientId> <poolId>。程序随后通过控制台交互依次完成以下 9 个步骤:
- signUp:注册用户(携带 email 属性),见
signUp(clientId, userName, password, email); - adminGetUser:查询用户确认状态,见
getAdminUser; - resendConfirmationCode:若用户要求重新发送验证码则调用,见
resendConfirmationCode; - confirmSignUp:输入邮箱收到的确认码完成确认,见
confirmSignUp; - adminInitiateAuth:发起管理员认证登录,此时返回的 Challenge 为
MFA_SETUP(提示需要配置 TOTP),见checkAuthMethod; - associateSoftwareToken:生成 TOTP 私钥(可用于 Google Authenticator),见
getSecretForAppMFA; - verifySoftwareToken:输入认证器显示的 6 位动态码,完成 TOTP 校验并登记 MFA,见
verifyTOTP; - adminInitiateAuth(再次登录):此时 Challenge 变为
SOFTWARE_TOKEN_MFA; - adminRespondToAuthChallenge:提交 6 位动态码,换取认证令牌,见
adminRespondToAuthChallenge。
关键认证代码
管理员密码认证,注意AuthFlowType.AdminUserPasswordAuth与USERNAME/PASSWORD参数对:
suspend fun checkAuthMethod( clientIdVal: String, userNameVal: String, passwordVal: String, userPoolIdVal: String, ): AdminInitiateAuthResponse { val authParas = mutableMapOf<String, String>() authParas["USERNAME"] = userNameVal authParas["PASSWORD"] = passwordVal val authRequest = AdminInitiateAuthRequest { clientId = clientIdVal userPoolId = userPoolIdVal authParameters = authParas authFlow = AuthFlowType.AdminUserPasswordAuth } CognitoIdentityProviderClient.fromEnvironment { region = "us-east-1" }.use { identityProviderClient -> val response = identityProviderClient.adminInitiateAuth(authRequest) println("Result Challenge is ${response.challengeName}") return response } }关联 TOTP 软件令牌,返回的session需要传递给后续校验步骤:
suspend fun getSecretForAppMFA(sessionVal: String?): String? { val softwareTokenRequest = AssociateSoftwareTokenRequest { session = sessionVal } CognitoIdentityProviderClient.fromEnvironment { region = "us-east-1" }.use { identityProviderClient -> val tokenResponse = identityProviderClient.associateSoftwareToken(softwareTokenRequest) val secretCode = tokenResponse.secretCode println("Enter this token into Google Authenticator") println(secretCode) return tokenResponse.session } }校验 TOTP 并登记 MFA:
suspend fun verifyTOTP( sessionVal: String?, codeVal: String?, ) { val tokenRequest = VerifySoftwareTokenRequest { userCode = codeVal session = sessionVal } CognitoIdentityProviderClient.fromEnvironment { region = "us-east-1" }.use { identityProviderClient -> val verifyResponse = identityProviderClient.verifySoftwareToken(tokenRequest) println("The status of the token is ${verifyResponse.status}") } }响应SOFTWARE_TOKEN_MFA挑战并换取最终认证结果:
suspend fun adminRespondToAuthChallenge( userName: String, clientIdVal: String?, mfaCode: String, sessionVal: String?, ) { println("SOFTWARE_TOKEN_MFA challenge is generated") val challengeResponsesOb = mutableMapOf<String, String>() challengeResponsesOb["USERNAME"] = userName challengeResponsesOb["SOFTWARE_TOKEN_MFA_CODE"] = mfaCode val adminRespondToAuthChallengeRequest = AdminRespondToAuthChallengeRequest { challengeName = ChallengeNameType.SoftwareTokenMfa clientId = clientIdVal challengeResponses = challengeResponsesOb session = sessionVal } CognitoIdentityProviderClient.fromEnvironment { region = "us-east-1" }.use { identityProviderClient -> val respondToAuthChallengeResult = identityProviderClient.adminRespondToAuthChallenge(adminRespondToAuthChallengeRequest) println("respondToAuthChallengeResult.getAuthenticationResult() ${respondToAuthChallengeResult.authenticationResult}") } }该场景完整覆盖了 Cognito 用户池最常见的生产链路:自助注册 → 邮件确认 → 管理员登录 → TOTP 绑定 → MFA 挑战应答,是构建移动端/Web 端安全登录体系的直接参考实现。
四、运行 Kotlin 示例
README 建议使用Gradle搭建 AWS SDK for Kotlin 项目的构建与运行环境(gradlew或本地 Gradle)。本目录的 build.gradle.kts 提供了可直接复用的构建配置:
- 使用 Kotlin JVM 插件(Kotlin 2.1.0)与
application插件; - 目标/源兼容 Java 17(
jvmTarget = "17"); - 通过
aws.sdk.kotlin:bom:1.5.63统一管理 AWS SDK 版本; - 依赖
cognitoidentityprovider、cognitoidentity、secretsmanager及 OkHttp/CRT HTTP 客户端引擎; - 测试侧引入 JUnit Jupiter 5.9.2,并启用
useJUnitPlatform()。
运行单个示例时,传入对应命令行参数即可,例如:
./gradlew run --args="my-identity-pool-name" ./gradlew run --args="us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx user1 user1@example.com Password123!"五、用 JUnit 5 测试这些示例
测试文件与运行方式
测试类位于 src/test/kotlin/CognitoKotlinTest.kt,名为CognitoKotlinTest,基于 JUnit 5 编写。你既可以在 IntelliJ 等 IDE 中直接执行,也可以在命令行用 Maven 运行:
mvn test每个测试通过logger.info输出"Test N passed",例如:
Test 3 passed测试类使用@TestInstance(PER_CLASS)、@TestMethodOrder(OrderAnnotation::class)与@Order(n)控制执行顺序,共 11 个用例:创建用户池 → 管理员建用户 → 列举用户池 → 列举/描述用户池客户端 → 删除用户池 → 创建身份池 → 列举身份池 → 列举身份 → 删除身份池,覆盖了身份池与用户池的完整生命周期。
⚠️警告:这些 JUnit 测试会操纵真实的 AWS 资源,可能产生账户费用,请仅在测试专用环境中运行。
测试配置:config.properties 与 Secrets Manager
README 要求测试前在resources 文件夹的 config.properties中定义以下键值,缺失任一键都会导致测试失败:
| 配置键 | 用途 |
|---|---|
userPoolName | 待创建的用户池名称(CreateUserPool 测试) |
username | CreateAdminUser 测试使用的用户名 |
email | CreateAdminUser 测试使用的用户邮箱 |
clientName | CreateUserPoolClient 测试使用的客户端名称 |
identityPoolName | CreateIdentityPool 测试使用的身份池名称 |
confirmationCode | ConfirmSignUp 测试使用的确认码 |
值得补充的是,从当前仓库源码看,CognitoKotlinTest.kt 的setup()已演进为从 AWS Secrets Manager 读取test/cognito密钥(经getSecretValues()+ Gson 反序列化为SecretValues数据类),所需字段包括userPoolName、username、email、clientName、identityPoolName、identityId、appId、existingUserPoolId、existingIdentityPoolId、providerName、existingPoolName、clientId、secretkey、password以及 MVP 场景专用的poolIdMVP/clientIdMVP/userNameMVP/passwordMVP/emailMVP。也就是说,当前版本的测试配置以 Secrets Manager 为准,config.properties 可视为该方案的早期形式——两种方式都要求提前准备真实可用的资源与凭据。
测试还演示了username拼接UUID.randomUUID()的技巧,避免并发或重复运行时用户名冲突。
六、深入学习路径
- 逐一阅读 kotlin/services/cognito/src/main/kotlin/com/kotlin/cognito 下 13 个 Kotlin 源文件,代码中均带有
snippet-start/snippet-end标记,方便在文档系统中按片段引用; - 结合 build.gradle.kts 理解 SDK 版本与依赖管理方式;
- 对照 CognitoKotlinTest.kt 的用例顺序,理解各 API 调用之间的依赖关系与资源生命周期;
- 如需深入理解 Amazon Cognito 用户池的架构与概念(身份池、用户池、客户端、MFA 等),可查阅 AWS 官方 Amazon Cognito 开发者指南与 AWS SDK for Kotlin 开发者指南。
版权说明:本目录示例代码版权归 Amazon.com, Inc. 或其关联公司所有,基于 Apache-2.0 许可发布(SPDX-License-Identifier: Apache-2.0)。
- 示例工程
- 教程
- 后端
【免费下载链接】aws-doc-sdk-examples
Welcome to the AWS Code Examples Repository. This repo contains code examples used in the AWS documentation, AWS SDK Developer Guides, and more. For more information, see the Readme.md file below.
相关推荐
AWS SDK for Java V2 操作 Amazon Cognito:用户池、身份池与 MFA 场景实战指南
AWS SDK for Java V2 操作 Amazon Cognito:用户池、身份池与 MFA 场景实战指南 导读 本文基于 aws doc sdk ex
示例工程教程后端AWS SDK for Java(v1)为 Amazon Cognito 用户池启用短信 MFA 的完整实战指南
AWS SDK for Java(v1)为 Amazon Cognito 用户池启用短信 MFA 的完整实战指南 导读 Amazon Cognito 用户池(U
示例工程教程后端使用 AWS SDK for JavaScript (v3) 实战 Amazon Cognito Identity Provider:用户池认证、MFA 与 Lambda 触发器
使用 AWS SDK for JavaScript v3 实战 Amazon Cognito Identity Provider:用户池认证、MFA 与 Lam
示例工程教程后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考