AWS Cognito forgot_password Python调用不存在用户未抛异常问题
AWS Cognito forgot_password接口不返回用户不存在异常问题解答
问题原因
- 该现象是AWS Cognito的用户存在性枚举防护机制导致的,这是Cognito默认开启的安全特性,目的是防止攻击者通过批量调用接口遍历判断哪些用户名/手机号在用户池中真实存在,避免账号泄露风险。
- 开启该特性后,不管传入的用户名是否真实存在,
forgot_password、sign_in等公开接口都会统一返回200成功响应,不会抛出UserNotFoundException异常。
掩码邮箱说明
你看到的n***@y***.com这类掩码邮箱是Cognito基于输入的用户名生成的响应内容:
- 对于真实存在的用户,返回的是用户真实绑定邮箱/手机号的掩码
- 对于不存在的用户,Cognito会基于输入的用户名做哈希运算生成对应的虚假掩码,因此不同的不存在用户名会返回不同的虚假掩码,完全模拟真实用户的响应逻辑,攻击者无法通过返回内容区分用户是否真实存在。
修复方案
你可以根据业务安全需求选择以下两种方案:
方案1:关闭用户存在性枚举防护(存在安全风险)
如果业务不需要防护用户枚举攻击,可以手动关闭该特性:
- 打开AWS Cognito控制台,进入对应的用户池
- 切换到「应用集成」标签页,找到你使用的应用客户端
- 关闭「防止用户存在性错误」(Prevent user existence errors)选项即可
关闭后你的现有代码不需要修改,不存在的用户调用接口时会正常抛出UserNotFoundException,你的异常捕获逻辑可以正常触发。
方案2:增加用户存在性校验逻辑(推荐,保留安全特性)
如果要保留Cognito的安全防护能力,可以在调用forgot_password前先通过管理员接口校验用户是否存在,需要先给Lambda执行角色添加cognito-idp:AdminGetUser的权限,修改后的代码如下:
import json import boto3 import botocore.exceptions import hmac import hashlib import base64 import uuid USER_POOL_ID = 'us-east-X_XXX' CLIENT_ID = 'XXXXXXXXXX' CLIENT_SECRET = 'XXXXXXXXXXXXX' def get_secret_hash(username): msg = username + CLIENT_ID dig = hmac.new(str(CLIENT_SECRET).encode('utf-8'), msg=str(msg).encode('utf-8'), digestmod=hashlib.sha256).digest() d2 = base64.b64encode(dig).decode() return d2 def lambda_handler(event, context): for field in ["username"]: if not event.get(field): return {"error": False, "success": True, 'message': f"{field} is not present", "data": None} client = boto3.client('cognito-idp') username = event['username'] # 新增:先校验用户是否存在 try: client.admin_get_user( UserPoolId=USER_POOL_ID, Username=username ) except client.exceptions.UserNotFoundException: return {"error": True, "success": False, "message": "Username doesnt exists"} except Exception as e: return {"error": True, "success": False, "message": f"Check user exist error {e.__str__()} "} try: print('username - ', username) response = client.forgot_password( ClientId=CLIENT_ID, SecretHash=get_secret_hash(username), Username=username, ) except client.exceptions.InvalidParameterException: return {"error": True, "success": False, "data": None, "message": f"User <{username}> is not confirmed yet"} except Exception as e: return {"error": True, "success": False, "message": f"Unknown error {e.__str__()} "} print(response) return {"error": False, "success": True, "message": f"Please check your Registered email id for validation code", "data": None}
内容的提问来源于stack exchange,提问作者Serhii Kushchenko
相关产品推荐
相关产品推荐

