尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Logto 忘记密码流程全解:从恢复设置门禁到安全密码重置的实现

Logto 忘记密码流程全解:从恢复设置门禁到安全密码重置的实现 Logto 忘记密码流程全解从恢复设置门禁到安全密码重置的实现【免费下载链接】logto‍ Authentication and authorization infrastructure for SaaS and AI apps, built on OIDC and OAuth 2.1 with multi-tenancy, SSO, and RBAC.项目地址: https://gitcode.com/GitHub_Trending/lo/logto本文以仓库中的 忘记密码流程文档 为核心骨架完整还原 LogtoAuthentication and authorization infrastructure基于 OIDC 与 OAuth 2.1 的多租户认证授权基础设施中「忘记密码Forgot Password」端到端流程入口设置门禁、恢复标识符仅邮箱/手机的校验、验证码发送与「不提前泄露账号是否存在」的安全设计、密码重置时的策略校验与缓存清理。读完本文你可以对照 experience 前端应用 与 core 后端服务 的源码理解流程图中每一个节点在代码中的真实落点。一、流程总览原文档用一张 Mermaid 流程图完整刻画了整个忘记密码流程从入口设置检查、恢复标识符输入、验证码发送与校验到密码重置与回跳登录。这里原样保留该图作为全文的导航索引图中四条注释note分别对应四个关键设计决策下文将逐条结合源码展开。二、入口门禁加载并检查忘记密码设置流程图的前三个节点start → fp_settings → fp_enabled构成第一道门禁先加载忘记密码设置只有当忘记密码功能开启且暴露了恢复方式时才允许进入流程否则直接渲染错误页。在前端 ForgotPassword 页面 中可以清楚看到这一实现的对应关系const ForgotPassword () { const { isForgotPasswordEnabled, enabledMethodSet } useForgotPasswordSettings(); // ... if (!isForgotPasswordEnabled) { return ErrorPage /; } return ( SecondaryPageLayout titledescription.reset_password descriptiondescription.reset_password_description descriptionProps{{ types: enabledMethods.map((method) t(identifierInputDescriptionMap[method])), }} ForgotPasswordForm autoFocus defaultValue{prefilledValue} enabledTypes{enabledMethods} / /SecondaryPageLayout ); };几个值得注意的实现细节设置来源useForgotPasswordSettings定义于 use-sie.ts从登录体验Sign-in Experience下称 SIE设置中读取forgotPassword字段。该字段与signIn、signUp、socialSignIn等并列是登录体验响应的一部分见同文件UseSieMethodsReturnType中的forgotPassword: SignInExperienceResponse[forgotPassword] | undefined。这印证了流程图中「Load forgot password settings」节点——恢复能力完全由 SIE 配置驱动而非硬编码。只展示已启用的恢复方式页面把enabledMethodSet展开为enabledMethods数组仅传给表单组件作为enabledTypes并在标题描述中按方法类型动态渲染提示文案。这就是流程图fp_methods节点「Show only enabled recovery methods」的落点。预填标识符页面通过 usePrefilledIdentifier 钩子传入isForgotPassword: true与enabledIdentifiers支持从 URL 查询参数预填邮箱或手机号预填值同样受「已启用方式」约束。三、恢复标识符仅支持邮箱与手机流程图中fp1 {Recovery identifier}分叉出 email 与 phone 两条路径note_methods注释明确指出本流程仅支持 email 与 phone 两种恢复方式用户名、社交账号、企业 SSO、一次性令牌One-time token和 Passkey 均不属于此流程。这一约束在前端由enabledMethods的取值范围天然保证SIE 中forgotPassword配置只会暴露邮箱/手机相关的标识符表单ForgotPasswordForm只会渲染这些已启用的输入框在 VerificationCode 页面 及 usePrefilledIdentifier 等关联代码中也均以SignInIdentifier/VerificationCodeSignInIdentifier类型来收窄标识符种类。两条路径都标注了optional captcha——即发送验证码前可按租户配置叠加人机校验验证码发送链路上的 captcha 校验逻辑位于 core 端的验证码辅助模块如 verification-code-helpers.ts。对集成的实际含义是如果你的租户没有启用邮箱或手机号登录忘记密码入口在前端即被第一道门禁拦截渲染 ErrorPage而不是走到一半才失败。四、验证码发送先发送、后查号的存在性防泄露设计note_existence注释是本流程最核心的安全设计用户提交标识符时不会检查账号是否存在。系统先发送验证码验证码通过校验后才检查是否存在对应用户因此流程不会提前泄露账号存在性。流程图中send_code之后并不是直接查询用户而是进入验证码页面、完成verify[Verify code]再执行found{Verified identifier matches an existing user account}判断不存在时仅显示「identifier not found」存在时才进入重置密码页。从后端源码可以印证这条「校验链」的严格性。profile-verification.ts 中的verifyProfileIdentifiers会逐条核对会话里已验证的标识符与最终提交的画像是否一致if (email) { assertThat( identifiers.some( (identifier) identifier.key emailVerified identifier.value email ), new RequestError({ code: session.verification_session_not_found, status: 404, }) ); }也就是说只有当会话标识符中确实存在emailVerified或phoneVerified且值完全匹配的记录时该标识符才被视为有效否则直接返回 404 的session.verification_session_not_found。这条错误码也正是前端在会话过期场景下要捕获并处理的信号见第六节。五、后端方法守卫在 identifyUser 阶段二次校验note_guard注释揭示了前后端职责的分工时序体验端 UI 在发送前只暴露已启用的忘记密码方式显式的后端忘记密码方式守卫在验证码通过校验之后、identifyUser阶段才执行而不是在发送验证码的接口里执行。从源码结构看identifyUser位于 user-identity-verification.ts是交互流程中「把已验证的标识符解析为具体用户身份」的一步而忘记密码分支的画像校验则在 profile-verification.ts 中完成。二者配合保证发送验证码时不校验方法是否属于忘记密码允许集前端 UI 已收敛了入口验证码校验之后后端再对当前 SIE 配置下的忘记密码方式做一次显式复核。这种「发送宽松、校验后置」的安排与第四节的存在性设计是同一思路把任何可能暴露账号/配置信息的检查都推迟到验证码被证明有效之后。对安全敏感的恢复类流程来说这是一个值得参考的时序设计。六、会话过期回到上一步而非死胡同note_session注释说明若恢复会话在密码重置完成前过期用户会被送回更早的步骤重新继续。这一点在 ResetPassword 页面 的错误处理中有非常直白的实现const errorHandlers: ErrorHandlers useMemo( () ({ session.verification_session_not_found: async (error) { await show({ type: alert, ModalContent: error.message, cancelText: action.got_it }); navigate(-2); }, user.same_password: (error) { setErrorMessage(error.message); }, ...passwordRejectionErrorHandler, }), [navigate, passwordRejectionErrorHandler, show] );当重置密码请求因会话失效即第四节中 404 的session.verification_session_not_found而失败时页面先弹出提示弹窗再执行navigate(-2)——回退两步恰好退回到重新提交邮箱/手机号、重新发起验证码的阶段与流程图中「sent back to resume from an earlier step」的描述一一对应。七、重置密码策略校验、旧密码防重与缓存清理流程图右侧的ResetPassword子图进入新密码页 → 校验密码策略 → 通过则保存 → 清理缓存的恢复标识符 → 显示密码已修改 → 返回登录在前后端各有明确落点。7.1 前端策略校验在前提交在后ResetPassword 页面 的提交逻辑const onSubmitHandler useCallback( async (password: string) { const success await checkPassword(password); // 前端先按密码策略校验 if (!success) { return; // 不通过则停留在本页对应 reset3 --|no| reset1 的循环 } const [error] await asyncResetPassword(password); if (error) { await handleError(error, errorHandlers); return; } // Clear the forgot password identifier input value setForgotPasswordIdentifierInputValue(undefined); setToast(t(description.password_changed)); navigate(/sign-in, { replace: true }); }, // ... );密码策略校验checkPassword来自 usePasswordPolicyChecker其底层依赖logto/core-kit导出的PasswordPolicyChecker与passwordPolicyGuard见 use-sie.ts 第 1 行的导入策略参数同样取自 SIE 设置。前端校验失败即返回对应流程图中Password accepted --|no| Enter new password的自环。服务端拒绝策略passwordRejectionErrorHandler兜底处理服务端因密码不合规而拒绝的情况对应...passwordRejectionErrorHandler合并进errorHandlers。成功收尾三动作清除缓存的恢复标识符 → 弹出description.password_changed提示 →navigate(/sign-in, { replace: true })回到登录页与reset5 → success → done三个节点严格对应。7.2 后端画像守卫与「不可与旧密码相同」校验后端对忘记密码分支的画像结构有专门的 zod 守卫定义于 types/guard.tsexport const forgotPasswordProfileGuard z.object({ password: z.string(), });在 profile-verification.ts 的verifyProfile中InteractionEvent既不是注册也不是登录时即进入忘记密码分支// Forgot Password const passwordProfileResult forgotPasswordProfileGuard.safeParse(profile); assertThat( passwordProfileResult.success, new RequestError({ code: user.new_password_required_in_profile, status: 422 }) ); const { passwordEncrypted: oldPasswordEncrypted, passwordEncryptionMethod } await findUserById(accountId); // Only compare password if the encryption method (algorithm) is Argon2i // if the user is migrated, this check will be skipped assertThat( !oldPasswordEncrypted || passwordEncryptionMethod ! UsersPasswordEncryptionMethod.Argon2i || !(await argon2Verify({ password: passwordProfile.password, hash: oldPasswordEncrypted })), new RequestError({ code: user.same_password, status: 422 }) );这段代码揭示了两条容易被忽略的后端规则画像必须包含新密码forgotPasswordProfileGuard强制要求password字段缺失即返回 422user.new_password_required_in_profile。这是流程图「Validate password policy / Save new password」之前的结构级前置条件。新密码不得与旧密码相同当用户旧密码以 Argon2i 加密时后端会用argon2Verifyhash-wasm直接比对新旧密码相同则返回 422user.same_password——前端errorHandlers中对user.same_password的分支将其消息展示为表单错误正是为此设计。注释也说明了边界若用户密码是迁移而来的、加密方法不是 Argon2i则跳过该比对因为无法安全验证哈希这是一个明确的能力限制。八、流程关键节点与源码落点速查流程图节点 / 注释行为源码落点fp_settings/fp_enabled加载 SIE 忘记密码设置未启用则渲染错误页ForgotPassword/index.tsx、use-sie.tsfp_methods/note_methods仅展示已启用的邮箱/手机恢复方式ForgotPassword/index.tsxenabledTypes、usePrefilledIdentifiersend_code/note_existence先发送验证码验证码校验通过后才检查账号是否存在profile-verification.tsemailVerified/phoneVerified核对fp_method_backend/note_guard后端忘记密码方式守卫在验证码校验后、identifyUser阶段执行user-identity-verification.tsnote_session会话过期则回退到更早步骤继续ResetPassword/index.tsxsession.verification_session_not_found→navigate(-2)reset2/reset3密码策略校验不通过则循环重填use-password-policy-checker、logto/core-kit的PasswordPolicyCheckerreset4保存新密码画像须含密码且不得与旧密码相同profile-verification.ts、guard.tsforgotPasswordProfileGuardreset5/success/done清除缓存标识符 → 密码已修改提示 → 返回登录页ResetPassword/index.tsx九、小结两条贯穿流程的安全设计线索把原文档流程图与源码对照后可以提炼出 Logto 忘记密码流程的两条主线设计信息不提前泄露提交标识符时不暴露账号是否存在先发码、后查号见note_existence方法是否可用不在发送接口上校验而推迟到验证码通过后的identifyUser阶段见note_guard与 user-identity-verification.ts。状态可恢复且收敛会话过期时通过session.verification_session_not_found错误码把用户平滑送回上一步ResetPassword/index.tsx 中的navigate(-2)成功重置后主动清除会话中缓存的恢复标识符使恢复凭据「一次性」失效。此外能力边界也被明确约束仅邮箱与手机参与本流程新密码必须通过与旧密码Argon2i 场景的去重校验与 SIE 密码策略校验。以上行为均以当前仓库版本为准若升级版本建议以 end-user-flows 目录 下的流程文档与各包CHANGELOG.md的变更记录为准。【免费下载链接】logto‍ Authentication and authorization infrastructure for SaaS and AI apps, built on OIDC and OAuth 2.1 with multi-tenancy, SSO, and RBAC.项目地址: https://gitcode.com/GitHub_Trending/lo/logto创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表