기술

문자가 못 갔는데 비밀번호만 바뀌면, 비밀번호 재설정은 롤백해야 합니다

2026.10.159분 읽기

급식서비스의 관리자 웹과 거래처 웹에는 비밀번호를 잊은 사람이 스스로 되찾을 길이 없었습니다. 로그인한 본인이 바꾸는 것과 관리자가 남의 계정을 초기화해 주는 것만 있었습니다. 잊으면 다른 관리자에게 전화를 해야 했습니다.

같은 시스템의 회원 앱은 외부 본인인증으로 이미 풀어 두었습니다. 그런데 관리자 계정은 휴대폰번호 칸이 거의 다 비어 있어 그 길을 쓸 수 없었습니다. 문자 발송 채널이 붙으면서 여섯 시간짜리 작업으로 들어왔습니다.

그 여섯 시간에 코드를 쓴 시간은 얼마 되지 않습니다. 대부분은 '외부 호출이 실패했을 때 내 쪽을 되돌릴지'를 정하는 데 들어갔습니다. 비밀번호 찾기는 한 화면 안에서 그 질문이 일곱 번 연달아 나오는 자리였습니다.


처음 만든 것은 요구가 아니었습니다

들어가서 한 번 크게 틀렸습니다. 대상을 관리자 목록 화면의 '비밀번호 초기화' 버튼으로 잡고 구현까지 갔습니다. 실제 요구는 '본인이 로그인 화면에서 스스로 찾는 것'이었습니다. 초기화 버튼은 원래대로 돌려놓고, 만들어 둔 6자리 발급 로직과 발송 모듈만 찾기 흐름으로 옮겨 그대로 썼습니다.


문자가 못 갔는데 비밀번호만 바뀌면

아이디와 등록된 휴대폰번호가 둘 다 일치하면 숫자 6자리 임시비밀번호를 발급해 문자로 보냅니다. 발급하는 순간 기존 비밀번호는 무효가 됩니다. 임시비밀번호는 24시간 유효하고, 5회 로그인 실패로 무효가 되며, 같은 계정은 1분에 한 번만 재발급됩니다.

첫 갈림길은 '문자 발송이 실패하면 비밀번호 변경을 되돌릴 것인가'였습니다. 되돌리기로 정했습니다.

문자가 못 갔는데 비밀번호만 바뀌면, 사용자는 옛 비밀번호도 새 비밀번호도 모르는 상태가 됩니다. 로그인 수단을 통째로 잃습니다. 되돌리면 사용자는 그냥 안 됐다고 생각하고 다시 누르면 됩니다. 두 실패의 무게가 다릅니다.

그래서 롤백 여부를 트랜잭션 기술로 정하지 않았습니다. **남는 그 상태가 사용자에게 무엇을 뜻하는지**로 정했습니다. 부분 성공이 '덜 된 것'이면 남겨도 되고, '수단을 잃은 것'이면 되돌립니다.

외부 발송 실패 시 비밀번호 변경을 남기는 경우와 되돌리는 경우를 비교한 다이어그램


왜 못 찾았는지는 알려주지 않습니다

다음 갈림길은 '못 찾았을 때 이유를 알려줄 것인가'였습니다. 계정이 없는지 번호가 등록 안 됐는지 구분해 안내하면 사용자는 훨씬 덜 헤맵니다. 그래도 전부 같은 문구로 통일했습니다.

이 요청은 로그인하지 않은 사람이 부르는 공개 API 입니다. 응답이 갈리는 순간 이 API 는 '이 아이디가 존재합니까'를 물어보는 조회 수단이 됩니다. 비밀번호 재설정, 회원가입, 아이디 중복확인이 전부 이 자리에 있습니다.

비슷한 이유로 휴대폰번호만 받지 않고 아이디와 번호를 둘 다 받습니다. 번호만 받으면 남의 번호를 아는 사람이 그 계정의 비밀번호를 임의로 무효화할 수 있습니다. 계정을 빼앗지는 못하더라도 상대를 못 들어오게 만드는 것은 가능해집니다.

다만 계정이 있는지 알아내는 통로를 막은 값은 문의 부하로 치렀습니다. 정상 사용자도 아이디를 잘못 쳤는지 번호가 등록 안 돼 있는지 알 수 없어서 결국 문의로 돌아옵니다. 내부 관리자용이라 문의 받는 곳이 가까워서 그 비용을 감수할 수 있었습니다.


상태 칸을 새로 만들지 않았습니다

임시비밀번호 상태를 표시할 칸도 갈렸습니다. 'is_temp' 같은 플래그 컬럼을 새로 만드는 쪽이 읽기에는 분명합니다. 대신 관리자 테이블에 발급시각과 실패횟수 두 칸만 추가하고, 발급시각이 비어 있는지로 판단합니다. 발급시각이 곧 플래그입니다.

같은 테이블의 세션 관리가 이미 그 방식이었기 때문입니다. 한 시스템 안에 '이 상태인지 아닌지'를 판정하는 규칙이 두 가지 있으면, 언젠가 그 둘을 같은 것으로 보고 틀립니다.

이 방식은 그 칸의 뜻이 하나일 때만 성립합니다. 나중에 '재발급 이력' 같은 다른 뜻이 발급시각에 얹히면 플래그로서의 해석이 무너집니다.


카운터는 트랜잭션을 따로 팠습니다

5회 실패 제한을 걸려면 실패 횟수를 올려야 합니다. 이 갱신을 로그인 처리와 같은 트랜잭션에 두면 동작하지 않습니다. 인증 실패 예외가 롤백을 부르면서 방금 올린 카운터도 같이 되돌아가기 때문입니다. 5회 제한이 영원히 1회에 머무릅니다.

그래서 실패 횟수 증가만 별도 트랜잭션으로 분리했습니다. 카운터, 감사로그, 시도 이력이 전부 같은 자리입니다. '예외가 나면 롤백한다'는 기본 동작이 정확히 그것들을 지웁니다.

이쪽도 대가가 있습니다. 별도 트랜잭션으로 뺀 카운터는 롤백을 타지 않으므로, 잘못 올라간 값은 되돌아오지 않습니다.

하나는 반대로 갔습니다. 임시비밀번호로 들어온 사람에게 현재 비밀번호를 다시 묻지 않습니다. 방금 입력한 것을 한 번 더 치게 하는 것은 절차이지 보안이 아닙니다.

로그인 실패 카운터를 같은 트랜잭션에 둘 때와 분리할 때의 결과 차이를 그린 그림


발송 서비스의 제한이 무엇까지 세는지

붙이면서 알게 된 제약이 하나 있습니다. 그 SMS 발송 대행사는 인증토큰 발급 호출까지 Rate Limit(정해진 시간 안에 부를 수 있는 횟수 제한)에 합산합니다. 발송할 때마다 토큰을 새로 받으면 발송량의 두 배를 쓰는 셈이라 제한에 먼저 걸립니다. 그래서 토큰을 24시간 캐시했습니다.

여기서 캐시는 성능 최적화가 아닙니다. 그게 없으면 기능이 동작하지 않는 조건입니다.

그 대행사는 도달 리포트 수신이 꺼져 있어서 최종 도달 결과가 우리 서버로 오지 않습니다. 그러니 우리가 아는 '발송 성공'은 정확히는 '접수 성공'입니다. 요청은 도달했는데 응답만 못 받은 경우에 되돌리면, 사용자는 문자를 받았는데 그 번호가 안 통합니다. 발송이 비싼 도메인이라면 롤백 대신 보상 트랜잭션이나 멱등 재시도(실패한 만큼만 뒤에 메우거나, 같은 요청을 여러 번 보내도 결과가 같게 만드는 방식)가 맞습니다.


배포는 끝났고 가동은 안 끝났습니다

일곱 결정 그대로 구현해 개발과 운영 양쪽에 배포했습니다. 단위테스트 20건이 새로 붙었고, 전체 테스트는 작업 전 스냅샷과 대조해 회귀 0건입니다. 두 웹 모두 실제 문자 수신에서 강제 변경, 재진입까지 사람 손으로 완주했습니다.

그런데 기능이 켜진 것과 쓰이는 것은 다릅니다. 남은 것이 셋입니다.

운영 관리자 대부분은 휴대폰번호 칸이 비어 있습니다. 번호가 들어오기 전까지 이 화면은 그들에게 아무 일도 하지 않습니다

운영 서버 설정에 발송 서비스 정보와 비밀번호를 넣고 재기동해야 실제로 문자가 나갑니다

초기 비밀번호 발급 방식은 아직 바꾸지 않았습니다. 6자리 난수로 옮길지는 정하지 않았습니다

셋 중 둘은 코드가 아니라 데이터와 설정입니다. 코드 리뷰로는 보이지 않고, 배포 완료로 작업을 마무리하면 아무도 안 보는 곳에 남습니다. 기능은 코드와 설정과 데이터 셋이 다 서야 살아납니다.

코드·설정·데이터 세 다리 중 둘이 비어 기능이 서지 못하는 상태를 그린 그림


같은 자리에 들어가기 전에 볼 것

이번에 쓴 것을 점검 항목으로 남깁니다.

외부 호출이 실패해서 남는 상태가 사용자에게 '덜 된 것'인지 '수단을 잃은 것'인지 적어 본다. 뒤쪽이면 되돌린다

응답이 경우에 따라 갈리는지 본다. 인증 없이 열린 요청에서 응답의 차이는 그대로 정보가 된다

본인 확인에 식별자와 소유물을 함께 받는지 본다. 소유물 하나만 받으면 계정을 무효화시키는 것은 가능해진다

상태 칸을 새로 만들기 전에 같은 시스템에 이미 있는 판정 규칙을 찾아본다

실패 경로에서 살아남아야 하는 갱신이 있으면 트랜잭션을 따로 판다

외부 API 의 호출 제한이 무엇까지 세는지 문서로 확인한다

배포 외에 설정과 데이터가 남았는지 적고, 그것까지 끝날 때 작업을 마무리한다

이 사례는 내부 관리자용이라서 성립했습니다. 임시비밀번호를 문자로 보내는 방식은 비밀번호가 평문으로 단말에 남아 링크형 재설정보다 약합니다. 일반 소비자 서비스였다면 문자 발송부터 다시 따졌을 겁니다. 응답을 전부 같은 문구로 두는 선택도 계산이 달라졌을 겁니다.

여섯 시간짜리 작업에 결정이 일곱 개 들어갔고, 그중 트랜잭션 기술로 정한 것은 하나도 없습니다. 전부 '실패한 채로 남는 그 상태가 사용자에게 무엇인가'로 정했습니다. 그리고 일곱 개를 다 구현해 배포한 뒤에도, 번호 데이터가 들어오기 전까지 이 화면은 대부분의 관리자에게 아무 일도 하지 않습니다.

#비밀번호재설정#트랜잭션#계정열거#SMS발송#상태모델링