对于一个SaaS系统来说,“忘记密码”看起来只是一个用户体验功能,实际上却是整个身份认证体系中风险最高的入口之一。
正常登录时,攻击者至少需要面对密码、MFA、登录风控和会话管理等多层控制。密码找回流程却可能绕过其中一部分机制。如果重置链接、验证码、备用邮箱、客服人工恢复或者管理员审批设计不严密,攻击者甚至不需要知道原密码,就可能直接获得一个新的登录凭证。
因此,密码重置不能简单理解成“给用户发一封邮件,让他重新设置密码”。一个成熟的SaaS系统应该把密码找回看成一次高风险身份恢复操作。
第一原则是不要泄露账号是否存在
用户访问“忘记密码”页面后,通常会输入邮箱地址或者用户名。
系统不应该因为账号存在或者不存在而返回明显不同的提示。
例如,不推荐:
这个邮箱不存在
或者:
重置邮件已经发送到该账户
后者也可能让攻击者确认某个邮箱是否注册了账号。
更合理的响应是统一表达,例如:
如果该账户存在,我们会向对应的邮箱发送进一步操作说明。
这样攻击者即使批量提交邮箱,也很难通过响应内容直接枚举有效账户。
当然,防止账户枚举不能只靠文字提示。响应时间、HTTP状态码、错误码、页面行为甚至邮件发送接口的差异,都可能暴露账户是否存在,因此实际系统需要从整体流程控制信息泄露。
重置链接不应该直接暴露密码或身份凭证
最常见的密码重置方案是向用户已经绑定的邮箱发送一个一次性链接。
例如:
https://example.com/reset-password?token=......
这个token应该是高熵、不可预测、一次性的随机值。
服务器生成token时应该使用密码学安全随机数生成器,而不是普通的伪随机函数。
PHP中可以使用:
$token = bin2hex(random_bytes(32));
32字节随机数据经过十六进制编码后形成64个字符。
实际长度可以根据系统设计确定,但核心不是“必须64字符”,而是随机性必须足够高,攻击者不能通过猜测、枚举或者预测生成规律获得有效token。
数据库不一定需要保存原始重置token
一个容易被忽略的安全设计是:服务器没有必要把用户收到的完整token原样保存到数据库。
可以保存token的哈希值。
例如:
$token = bin2hex(random_bytes(32));
$tokenHash = hash('sha256', $token);
数据库保存$tokenHash,邮件中发送原始$token。
用户点击链接以后,服务器再次计算收到token的哈希值,再与数据库中的哈希进行比较。
这样即使攻击者获得数据库内容,也不能直接从数据库字段中拿到可以登录的重置token。
这里与密码存储又有所不同。
密码属于长期秘密,应该使用专门的密码哈希算法,例如Argon2id、bcrypt或者scrypt。
高熵的一次性token本身已经具有很强的随机性,因此使用SHA-256之类的密码学哈希来保存其摘要通常就足够,不需要把一个随机token再用高成本密码哈希算法处理。
Token必须有生命周期
密码重置token不能永久有效。
数据库通常至少需要记录:
token_hash
user_id
created_at
expires_at
used_at
实际有效期应该根据业务风险确定。
很多系统会使用几十分钟以内的短生命周期,但并不存在一个由ISO/IEC 27001统一规定的“必须15分钟”。
ISO 27001是信息安全管理体系标准,并不会替开发者规定一个统一的密码重置token有效期。
对于高风险操作,可以采用更短的有效期。
对于普通消费型SaaS,也可以根据邮件投递延迟和用户体验选择合适的时间窗口。
核心要求是token生命周期有限,并且使用后立即失效。
Token必须一次性使用
即使token还没有过期,只要用户已经成功设置新密码,这个token就应该立即失效。
例如:
用户打开重置链接
↓
服务器验证token
↓
用户设置新密码
↓
更新密码哈希
↓
标记token已经使用
↓
撤销旧会话
这里实际工程中还要防止并发请求造成token重复使用。
密码重置接口应该在服务器端以原子方式检查和消费token,避免攻击者同时发送多个请求,导致同一个token在竞态条件下被重复接受。
不要把“验证邮箱”理解成绝对安全
邮件重置的安全性取决于邮箱账户本身。
如果攻击者已经控制用户邮箱,那么“向邮箱发送重置链接”实际上无法阻止攻击者。
因此,高安全等级SaaS不能只考虑“邮箱是否正确”,还应该根据账户风险决定是否要求额外验证。
例如普通用户可以通过已验证邮箱完成密码重置,而管理员、高权限账户或者检测到异常行为的账户,可以要求额外MFA验证、Passkey或者其他经过注册的恢复因子。
这里需要避免一个常见错误:如果用户已经失去所有MFA设备,就不能简单要求“再输入一次MFA”然后结束流程。系统必须提前设计安全的恢复路径。
MFA恢复比密码重置更加敏感
如果账户启用了MFA,密码重置流程不能直接变成“忘记密码,所以顺便关闭MFA”。
否则攻击者只需要攻破恢复流程,就可以同时绕过密码和MFA。
更合理的设计是区分几个操作:
密码重置。
MFA设备更换。
MFA丢失后的账户恢复。
高权限账户恢复。
这些操作应该具有不同的风险等级。
例如,一个管理员忘记密码,同时又丢失了Authenticator或者Passkey,那么恢复流程可能需要使用预先保存的恢复代码、经过验证的组织管理员审批或者其他强身份恢复机制。
不能使用一个简单的“安全问题”作为最终防线。
不建议使用传统安全问题
“你小学在哪读的?”
“你母亲的名字是什么?”
“你的第一辆车是什么?”
这类安全问题曾经非常常见,但今天已经不适合作为高价值账户的主要恢复因子。
很多答案可以通过社交媒体、公开资料或者数据泄露获得。
如果系统需要备用恢复机制,更合理的方法是使用高熵恢复代码、Passkey、备用验证设备、企业管理员审批或者其他预先注册的恢复因素。
新密码应该怎样设置
原稿要求密码必须同时包含大写、小写、数字和特殊字符,这种规则已经不能代表现代密码安全设计。
更重要的是密码长度、是否属于已知泄露密码、是否容易被猜测,以及系统有没有使用安全的密码哈希。
对于普通SaaS,应该鼓励用户使用足够长的密码或者密码短语,并阻止明显的弱密码和已知泄露密码。
如果业务需要,可以设置合理的最小长度,但没有必要为了满足“复杂度规则”强迫用户组合各种字符。
过度复杂的密码规则经常导致用户选择类似:
Password123!
这种看似复杂、实际非常常见的密码。
密码存储必须使用密码哈希
服务器绝对不应该保存用户明文密码。
也不应该使用AES之类的可逆加密方式保存密码。
推荐使用Argon2id、bcrypt或者scrypt等专门设计用于密码存储的算法。
例如PHP现代应用可以使用:
$hash = password_hash(
$password,
PASSWORD_ARGON2ID
);
验证密码时使用:
if (password_verify($password, $hash)) {
// 密码正确
}
具体的内存、时间和并行参数应该通过服务器实际性能测试确定,而不是机械采用某个固定数字。
密码哈希成本需要在安全性和服务器资源之间取得平衡。
密码重置后应该处理旧会话
这是很多简单SaaS系统容易遗漏的地方。
假设攻击者之前已经窃取了用户的Session Cookie。
用户后来通过“忘记密码”成功修改密码,如果系统只修改密码哈希,却继续保留攻击者原来的会话,那么攻击者仍然可能保持登录状态。
因此密码重置成功以后,应该考虑撤销现有会话。
一种常见设计是给用户维护:
session_id
user_id
created_at
last_seen_at
revoked_at
密码修改后,将其他活动会话全部撤销,或者提高账户的session版本号,使旧会话在下一次请求时失效。
对于高风险系统,还可以向用户发送“密码已经修改”的安全通知。
不要把IP地址当成永久身份
密码重置和风险控制可以记录IP地址、设备信息、User-Agent以及其他风险信号。
但不要把IP地址当成用户身份本身。
移动网络、VPN、公司网络、NAT和IPv6环境都会导致IP变化。
设备指纹也不应该被当作永久身份认证因素。
这些信息更适合作为风险评分的一部分。
例如:
正常设备
正常地理位置
正常时间
正常邮箱
风险可能较低。
如果突然出现:
新设备
异常位置
大量重置请求
短时间多次验证码失败
系统可以要求更高等级的验证。
重置接口必须防止滥用
攻击者不一定想偷走某一个账户。
他们也可能把“忘记密码”接口当成资源消耗入口。
例如不断要求发送邮件或短信,就可能造成邮件服务费用、短信费用和服务器资源消耗。
因此应该实施多维度速率限制。
可以同时考虑:
IP地址
账户标识
设备或请求特征
邮箱域名
整体系统发送量
不要只设置一个“每10分钟5次”的固定规则,然后认为问题解决了。
不同接口也应该采用不同限制。
“请求重置邮件”和“验证重置token”本身就是两个不同的攻击面。
验证码也不是万能的
如果系统使用OTP,需要确保验证码具有足够的随机性、短生命周期和有限尝试次数。
例如短信或者邮件验证码可以设置:
短时间有效
单次使用
失败次数限制
重新发送限制
同时需要考虑验证码爆破和短信、邮件轰炸。
OTP本身也不能被理解成绝对安全。
实时钓鱼攻击可以诱骗用户把刚收到的验证码告诉攻击者。
因此,对于高价值账户,Passkey/WebAuthn这类抗钓鱼认证机制通常比单纯OTP更加可靠。
日志应该记录安全事件,但不能记录秘密
密码重置属于安全敏感操作,因此应该产生审计事件。
例如:
用户标识
操作时间
操作类型
请求来源
结果
风险信号
IP地址和User-Agent等信息是否长期保存,则应该根据业务、隐私要求和适用法律确定。
更重要的是,日志绝对不应该记录完整的:
密码
Session Token
Reset Token
Refresh Token
OTP
否则安全日志本身可能变成攻击者获取身份凭证的目标。
可以记录token的哈希、截断后的标识或者事件ID,用于调查和关联,而不是把可直接使用的秘密写进日志。
GDPR和SOC 2不能被理解成一个固定日志保存期限
原稿把GDPR第30条解释成“日志必须保存12个月”,这是不准确的。
GDPR并没有规定所有密码重置日志统一保存12个月。
日志保存时间应该根据合法目的、必要性、数据最小化原则、企业政策、合同以及具体行业法规确定。
SOC 2也不是一个简单规定“日志必须保存多少个月”的技术配置清单。
企业真正需要做的是建立明确的日志策略、访问控制、完整性保护、审计机制和合理保存期限,并能够证明这些控制措施在持续运行。
管理员人工恢复是最后一道高风险边界
SaaS企业经常遇到这种情况:
用户失去邮箱。
手机丢失。
Authenticator无法使用。
Passkey也没有备用设备。
这时候可能需要客服或者管理员介入。
问题是,人工恢复本身可能成为账户接管入口。
如果客服只要求:
“告诉我你的姓名和公司名称,我帮你重置。”
那么攻击者很可能通过社交工程绕过整个认证体系。
高价值账户的人工恢复应该有明确的审批流程、身份验证要求、操作日志以及双人审批等机制。
对于企业SaaS,还可以由组织管理员按照既定权限完成用户恢复,而不是让普通客服直接修改账户安全因素。
密码重置和账户恢复必须分开设计
这是整个系统设计中非常重要的一点。
“忘记密码”通常意味着用户仍然拥有某个已经注册的验证因子,例如经过验证的邮箱。
“账户恢复”则可能意味着用户已经失去了原来的认证因素。
两者风险等级完全不同。
例如:
忘记密码
已验证邮箱仍然可用
|
| 低风险恢复路径
|
重新设置密码
而:
密码忘记
邮箱失效
MFA丢失
Passkey丢失
|
| 高风险身份恢复
|
额外验证或管理员审批
后者绝不能简单地复制前者的流程。
一个成熟的SaaS密码重置流程
一个比较完整的设计可以是:
用户提交邮箱或者用户名。
系统使用统一响应,避免暴露账户是否存在。
服务器生成高熵、一次性的随机token。
数据库保存token的哈希和有限的过期时间。
通过已经验证的邮箱发送HTTPS重置链接。
用户打开链接。
服务器验证token是否存在、是否过期、是否已经使用。
验证成功后允许用户设置新的密码。
服务器使用Argon2id等密码哈希算法保存新密码。
立即使重置token失效。
撤销旧Session或者提高Session版本。
根据风险等级重新处理MFA或者要求额外验证。
记录安全审计事件。
向用户发送密码已经修改的通知。
如果用户没有发起这次操作,则应该能够进一步进入账户安全事件处理流程。
这个流程的关键并不是步骤越多越安全,而是每一个恢复步骤都必须建立在一个可信的认证因素之上。
真正需要防的是恢复流程绕过认证体系
密码重置系统最危险的设计,不一定是token长度不够。
更常见的问题是认证链被绕开。
例如正常登录要求:
密码 + MFA
结果忘记密码以后变成:
输入邮箱
收到邮件
关闭MFA
重新设置密码
那么攻击者只需要攻破邮箱或者恢复流程,就等于绕过了原来的MFA。
更严重的是,如果管理员恢复流程又比用户流程更宽松,攻击者可能进一步通过社交工程攻击客服或者管理员。
因此,密码重置应该被视为身份认证体系的一部分,而不是单独的“找回密码功能”。
一个成熟的SaaS身份系统至少需要把密码哈希、Session管理、MFA、Passkey、密码重置、账户恢复、风险控制、速率限制和审计日志放在同一个安全模型里考虑。
密码重置token应该短期、随机、一次性,并且服务器最好只保存其哈希。密码应该使用Argon2id等专用密码哈希算法,而不是可逆加密。重置成功以后应该处理旧会话,并根据账户风险决定是否需要重新验证MFA。
对于普通用户,流程应该尽可能简单,让用户能够快速恢复账户。对于管理员、财务账户、企业所有者等高价值身份,则应该提高恢复门槛。
好的SaaS密码找回系统,并不是让用户经历最多的验证步骤,而是在正确的位置设置正确的身份验证强度。正常情况下让恢复流程足够顺畅,遇到异常设备、异常位置、大量请求或者认证因素丢失时,再逐级提高验证要求。这样才能同时解决安全性、可用性和账户恢复三个问题。
SaaS 用户忘记密码怎么办?一个完整的找回密码流程应该包含哪些步骤
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP