如何配置Amazon Cognito用户池支持邮箱/手机号作为用户名登录
解决Cognito用户池允许邮箱+姓名或手机号+姓名注册登录的问题
问题描述
当前SAM模板配置下,用户注册时被要求同时提供邮箱和手机号,但实际需求是允许用户仅使用邮箱+姓名或仅使用手机号+姓名完成注册与登录流程。
问题根源
虽然UserPool的Schema中phone_number和email的Required已设为false,但UsernameAttributes同时包含了这两个字段。Cognito默认不会自动校验"二选一"的逻辑,导致注册时可能出现需要同时提供两者的情况,或者用户仅提供一个时无法通过校验。
解决方案
需要通过自定义PreSignUp Lambda触发器实现"邮箱/手机号二选一"的校验逻辑,同时确保用户池配置适配这种场景。
1. 修改SAM模板配置
原有UserPool的Schema和UsernameAttributes配置无需修改,但需要确保PreSignUp Lambda触发器正确关联,并补充Lambda资源定义。以下是完整修改后的SAM模板:
AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: > Passwordless SMS/Email authentication backend using Amazon Cognito User Pools CUSTOM AUTH challenge flow w/ AWS Lambda triggers Metadata: AWS::ServerlessRepo::Application: Name: passwordless-sms-email-auth Description: > Passwordless SMS/Email authentication backend using Amazon Cognito User Pools CUSTOM AUTH challenge flow w/ AWS Lambda triggers SpdxLicenseId: MIT LicenseUrl: LICENSE Labels: ['passwordless', 'authentication', 'cognito', 'auth', 'sms', 'email', 'iOS', 'mobile', 'serverless', 'amplify'] SemanticVersion: 1.14.20 Globals: Function: Timeout: 3 Runtime: nodejs18.x Parameters: UserPoolName: Type: String Description: The name you want the User Pool to be created with Default: rafaelTest Resources: # 自定义PreSignUp Lambda触发器 PreSignUp: Type: AWS::Serverless::Function Properties: Handler: index.handler CodeUri: ./pre-sign-up/ # 指向存放Lambda代码的目录 Policies: - AWSLambdaBasicExecutionRole CreateAuthChallenge: Type: AWS::Serverless::Function Properties: Handler: index.handler CodeUri: ./create-auth-challenge/ Policies: - AWSLambdaBasicExecutionRole - AmazonSNSFullAccess # 若使用SNS发送短信验证码 DefineAuthChallenge: Type: AWS::Serverless::Function Properties: Handler: index.handler CodeUri: ./define-auth-challenge/ Policies: - AWSLambdaBasicExecutionRole VerifyAuthChallengeResponse: Type: AWS::Serverless::Function Properties: Handler: index.handler CodeUri: ./verify-auth-challenge-response/ Policies: - AWSLambdaBasicExecutionRole UserPool: Type: "AWS::Cognito::UserPool" Properties: UserPoolName: !Ref UserPoolName Schema: - Name: name AttributeDataType: String Mutable: true Required: true - Name: phone_number AttributeDataType: String Mutable: true Required: false - Name: email AttributeDataType: String Mutable: true Required: false Policies: PasswordPolicy: MinimumLength: 6 RequireLowercase: false RequireNumbers: false RequireSymbols: false RequireUppercase: false UsernameAttributes: - phone_number - email MfaConfiguration: "OFF" LambdaConfig: CreateAuthChallenge: !GetAtt CreateAuthChallenge.Arn DefineAuthChallenge: !GetAtt DefineAuthChallenge.Arn PreSignUp: !GetAtt PreSignUp.Arn VerifyAuthChallengeResponse: !GetAtt VerifyAuthChallengeResponse.Arn UserPoolClient: Type: "AWS::Cognito::UserPoolClient" Properties: ClientName: sms-email-auth-client GenerateSecret: false UserPoolId: !Ref UserPool ExplicitAuthFlows: - CUSTOM_AUTH_FLOW_ONLY Outputs: UserPoolId: Description: ID of the User Pool Value: !Ref UserPool UserPoolClientId: Description: ID of the User Pool Client Value: !Ref UserPoolClient
2. 实现PreSignUp Lambda校验逻辑
在./pre-sign-up/index.js中编写以下代码,确保用户至少提供邮箱或手机号中的一个,并自动确认用户:
exports.handler = async (event) => { const { userAttributes } = event.request; // 强制校验:必须提供邮箱或手机号其中一个 if (!userAttributes.email && !userAttributes.phone_number) { throw new Error("请提供邮箱或手机号"); } // 自动确认用户(若需验证邮箱/手机号可关闭此选项) event.response.autoConfirmUser = true; // 显式设置用户名(可选,Cognito会自动处理,显式设置更清晰) if (userAttributes.email) { event.request.userAttributes.username = userAttributes.email; } else if (userAttributes.phone_number) { event.request.userAttributes.username = userAttributes.phone_number; } return event; };
3. 适配CreateAuthChallenge触发器逻辑
确保CreateAuthChallenge能根据用户提供的字段发送对应渠道的验证码,核心逻辑示例:
exports.handler = async (event) => { const { userAttributes } = event.request; const challengeCode = Math.floor(100000 + Math.random() * 900000).toString(); // 根据用户属性选择验证码发送渠道 if (userAttributes.email) { // 调用SES发送邮件验证码的逻辑 console.log(`发送验证码${challengeCode}到邮箱${userAttributes.email}`); } else if (userAttributes.phone_number) { // 调用SNS发送短信验证码的逻辑 console.log(`发送验证码${challengeCode}到手机号${userAttributes.phone_number}`); } // 存储验证码供后续校验使用 event.response.privateChallengeParameters = { challengeCode: challengeCode }; event.response.challengeMetadata = `CODE_${userAttributes.email || userAttributes.phone_number}`; return event; };
验证
部署修改后的模板后,测试注册流程:
- 仅填写姓名+邮箱:成功注册并收到邮箱验证码
- 仅填写姓名+手机号:成功注册并收到短信验证码
- 同时填写姓名+邮箱+手机号:正常注册(若需禁止此场景,可在PreSignUp中补充逻辑)
内容的提问来源于stack exchange,提问作者Rafael de Carvalho
相关产品推荐
相关产品推荐

