계정 복구는 비밀번호를 바꾸는 화면이 아니라, 기존 인증 수단을 잃은 주체에게 새 접근 수단을 발급하는 권한 전이입니다. 따라서 토큰의 일회성·원자 소비, 복구 증거의 신뢰 등급, 복구 직후 민감 기능 제한을 하나의 상태 전이로 설계해야 합니다.
강한 로그인과 약한 계정 복구 경로
보안 키를 가진 사용자만 로그인하게 해도 이메일 링크 하나로 보안 키를 모두 제거하고 새 키를 등록할 수 있다면 이메일 탈취가 다른 입구가 됩니다. 복구는 편의 기능만이 아니라 계정의 접근 수단을 바꾸는 권한 상승 흐름입니다.
그렇다고 기기를 잃은 정상 사용자를 영구 잠그는 것이 좋은 정책은 아닙니다. 계정 접근 복원과 민감한 자격 변경·결제를 분리하고, 어떤 증명으로 어디까지 허용할지 설계해야 합니다. 알림을 보냈다는 사실이나 공개된 개인정보를 맞혔다는 사실은 원래 소유권의 충분한 증명이 아닙니다.
토큰 검증의 기본 상태는 purpose, account, expiresAt, consumedAt, credentialVersion입니다. 링크를 받은 것만으로 비밀번호·MFA·결제 변경을 모두 허용하지 말고, 토큰이 어떤 한 번의 전환에만 사용되는지 명시하면 용도 혼동과 재사용을 분리해 진단할 수 있습니다.
재설정 토큰의 용도·만료·일회성 권한
재설정 토큰은 충분한 엔트로피를 가진 무작위 값으로 만들고, 링크에는 원문을 넣되 서버에는 그 값을 다시 복원할 수 없는 검증용 해시와 대상 계정·용도·만료·소비 상태를 저장합니다. 토큰은 링크를 가진 요청이 특정 재설정 시도에 해당하는지 확인하는 값이고, 비밀번호는 사용자가 고른 값을 반복 추측하는 공격을 견뎌야 하므로 두 값의 검증 방식과 저장 정책이 다릅니다. 따라서 토큰과 비밀번호 사이에 같은 문자열이나 같은 저장 정책을 재사용하지 않습니다.
재설정 링크는 요청의 Host가 아니라 서버 설정에 등록한 서비스 origin으로 만듭니다. URL에 들어간 토큰은 브라우저 기록, 서버 로그, 분석 도구, 외부 자산 요청에 남을 수 있으므로 재설정 화면에서는 외부 리소스를 줄이고 referrer 정책(이전 URL을 다음 요청에 보낼지 정하는 규칙)과 로그 마스킹을 함께 적용합니다. 메일 보안 스캐너가 링크를 미리 열 수 있으니 GET은 변경 화면을 표시하는 단계로 두고, 비밀번호 변경과 토큰 소비는 사용자의 최종 제출에서 수행합니다.
토큰 확인·소비와 자격 변경의 원자 경계
| 순서 | 잘못된 두 단계 구현 | 원자적 전환 |
|---|---|---|
| A·B 동시 요청 | 둘 다 미사용 조회 성공 | 한 요청만 미사용 조건 획득 |
| 비밀번호 저장 | 서로 다른 값으로 두 번 저장 가능 | 토큰 소비와 한 변경을 같은 거래로 확정 |
| 중간 중단 | 변경 후 토큰이 남을 수 있음 | 함께 커밋하거나 함께 취소 |
new_hash = prepare_password_hash_with_bounded_resources(input)
begin transaction
lock reset_record
require correct token hash, purpose, account, unexpired, unused
update password and increment credential/security version
mark reset_record consumed
invalidate older reset attempts according to policy
enqueue notification in durable outbox
commit
비밀번호 해시처럼 시간이 걸리는 계산은 먼저 준비해 두고, 거래 안에서는 재설정 기록을 잠근 뒤 토큰과 계정 상태를 다시 확인하여 잠금 시간을 줄일 수 있습니다. 이 재확인이 통과할 때만 비밀번호 저장과 토큰 소비를 함께 확정해야 하며, 그 사이 재확인 조건에 맞지 않게 계정 상태가 바뀌었다면 저장하지 않습니다. 재시도 요청에 같은 시도의 성공 여부를 알려 주는 정책과, 이미 소비된 토큰으로 새 비밀번호 변경을 허용하는 정책은 별개입니다. 새 토큰을 발급할 때 모든 이전 링크를 폐기할지 최신 링크만 유효하게 할지도 정해야 합니다.
복구 후 제한 세션과 단계별 권한
그림을 글로 읽기
- 복구 증거 수집: 기존 기기·복구 코드·승인
- 제한된 복구 세션: 민감 변경 제한·기존 채널 알림
- 대기·추가 증명·검토
- 정책이 허용한 기능 복원
- 복구 증거 수집 → 제한된 복구 세션: 허용 증거 충족
- 제한된 복구 세션 → 대기·추가 증명·검토: 고위험 기능 보류
- 대기·추가 증명·검토 → 정책이 허용한 기능 복원: 필요 조건 충족
예를 들어 복구된 세션은 기본 계정 정보 확인만 허용하고 결제 수단 변경·새 MFA 등록·연락처 교체는 일정 기간과 추가 증명 뒤에 열 수 있습니다. 기간은 고정된 보편 권고값이 아니라 자산 위험·탐지 시간·정상 사용성으로 정합니다. 기존 이메일을 즉시 바꾼 뒤 새 이메일에만 알림을 보내면 원래 사용자에게 대응 기회를 주지 못합니다.
대기 기간에 이미 탈취된 옛 세션을 계속 허용하면 보호 효과가 약합니다. 의심 세션·refresh 계열 회수와 access token 차단의 실제 지연을 별도로 적용합니다. 알림의 취소 링크 하나가 새로운 무인증 계정 장악 수단이 되지 않도록 취소·동결·복구 재개 각각의 권한을 구분합니다.
피싱 저항성과 새 자격 등록 경로
OTP는 사용자가 코드를 다른 사이트에 입력하도록 속일 수 있지만 패스키의 RP·origin 바인딩은 이런 중계 피싱을 줄입니다. 그러나 이메일만으로 새 패스키를 등록해 주면 강한 자격의 등록 입구가 약해집니다. 기존 강한 자격·등록된 기기·사전 발급 복구 코드 등 가용한 증거를 평가하고, 무엇을 잃었을 때 어떤 신뢰 등급으로 복구되는지 표시합니다.
지원센터에는 최소 권한·사유 코드·이중 승인·감사 기록을 둡니다. 담당자가 원문 비밀을 보거나 임의 로그인 수단을 직접 꽂는 도구를 주지 않습니다. 긴급 해제 역시 대기 기간을 말 한마디로 무효화하는 예외가 아니라 별도의 증거·승인자·허용 범위가 남는 경로여야 합니다. 필요한 개인정보만 수집하고 보관 기간도 제한합니다.
재현 연습으로 같은 토큰을 두 요청이 동시에 제출하면 정확히 한 요청만 password/security version을 변경하고 한 번만 outbox 알림을 만들어야 합니다. 만료 직전 경쟁에서는 거래 안의 재확인 결과에 따라 둘 다 실패하거나 하나만 성공해야 하며, 변경 후 알림 장애는 자격 변경을 되돌리지 않고 durable outbox 재처리로 회복되어야 합니다. 복구 세션에서 새 MFA 등록과 결제 변경이 즉시 열리지 않는지도 기능별로 확인합니다.
복구 성공률과 우회 가능성 검증
합성 계정으로 같은 토큰 두 번 소비, 만료 직전 경쟁, 재발급 후 옛 링크, 변경 커밋 후 알림 장애를 시험합니다. 기대 결과는 하나의 자격 변경과 내구 알림 재처리입니다. 계정 존재 여부를 응답 문구·타이밍으로 쉽게 구분하지 못하게 발급 endpoint의 오류·rate limit도 검토합니다.
이메일만 탈취한 상황, 기존 기기만 가진 상황, 모든 기기를 잃은 정상 사용자, 지원센터 긴급 승인 경로를 각각 따라갑니다. 복구 뒤 어떤 기능이 열렸는지·옛 세션이 언제 막혔는지·원래 사용자가 어떻게 이의를 제기하는지까지 확인해야 합니다. 이 노트는 정책 설계와 시험 절차이며 실제 계정 복구를 수행한 기록은 아닙니다.