Flutter 로그아웃 뒤 이전 계정 레시피가 복사됐다: 원인과 수정 과정

VidDish를 개발하던 중 실기기에서 정식 계정으로 저장한 레시피가, 로그아웃 뒤 새로 만든 익명 계정에 복사되는 일을 확인했습니다. 당시 수정 기록에는 이전 계정의 레시피 11개가 새 익명 UID로 올라간 것이 남아 있습니다. 새 계정은 그 레시피를 저장한 적이 없는데도 자신의 목록으로 받았습니다.

처음에는 계정별 클라우드 자료를 잘못 읽은 것처럼 보일 수 있습니다. 그러나 이 사례의 출발점은 서버가 다른 사람의 자료를 열어 준 일이 아니었습니다. 앱이 기기에 저장해 둔 목록의 주인을 확인하지 않은 채 새 계정의 자료와 합쳐 업로드한 것이 문제였습니다. 이 글에서 말하는 로컬 목록은 앱이 SharedPreferences에 직접 저장한 값이며, Firestore SDK의 내부 오프라인 캐시와는 별개입니다.

로그아웃했는데 왜 목록이 새 계정으로 갔나

앱은 저장한 레시피 목록을 기기의 공용 키 하나에 보관했습니다. 로그인 계정은 바뀌어도 그 키에 남은 목록은 그대로였습니다. 동기화는 현재 계정의 클라우드 목록을 읽고 기기의 목록과 합친 뒤, 클라우드에 없는 항목을 업로드했습니다.

따라서 정식 계정 A에서 로그아웃해 새 익명 계정 B로 들어가면 다음 일이 생길 수 있었습니다. B의 클라우드는 비어 있지만 기기에는 A가 저장한 레시피가 남아 있습니다. 앱이 두 목록을 합치면 A의 레시피를 B의 새 자료로 판단해 B의 UID로 올립니다. B의 요청 자체는 정상일 수 있으므로, 증상만 보고 Firestore 보안 규칙 문제로 단정하면 수정 지점을 놓칩니다.

문제는 화면에 잠깐 이전 목록이 보이는 데서 끝나지 않습니다. 잘못 합친 뒤 업로드하면 새 계정의 클라우드 자료까지 바뀝니다. 그래서 로그인 화면만 정리하거나 동기화 후 화면을 새로고침하는 방식으로는 충분하지 않았습니다.

왜 로그아웃 때 목록을 모두 지우지 않았나

가장 단순해 보이는 방법은 계정이 바뀔 때마다 기기의 목록을 비우는 것입니다. 하지만 VidDish는 계정 없이 먼저 레시피를 저장한 사용자가 나중에 기존 계정으로 들어갈 때, 그 익명 자료를 가져가는 흐름을 제공합니다. 모든 UID 변경에서 목록을 지우면 사용자가 남기려던 레시피도 사라집니다.

두 상황을 나눠야 했습니다.

기기에 목록을 남긴 계정 새 계정에서의 처리 이유
지금 들어온 계정과 같은 UID 자신의 목록이므로 유지
다른 정식 계정 그 계정의 레시피를 새 계정에 이관하지 않도록 병합에서 제외
기존 익명 계정 앱에서 안내한 자료 이관 흐름에 따라 병합 허용
소유자 정보가 없는 오래된 목록 누구의 것인지 알 수 없으므로 별도 이행 정책 필요

이것은 익명 목록을 언제나 다른 계정으로 옮겨도 된다는 규칙이 아닙니다. 여러 사람이 같은 기기를 쓰는 앱이라면 이관 의사를 더 분명히 확인해야 합니다. 또한 익명 계정에 로그인 수단을 연결해 같은 UID를 유지하는 경우와, 이미 존재하는 다른 UID의 계정에 로그인해 자료를 옮기는 경우는 다릅니다. 뒤의 자료 병합을 Firebase Auth가 대신 해 주지는 않습니다. Firebase 익명 계정 연결 안내

해결은 ‘목록의 주인’을 병합 전에 확인하는 것이었다

이후 앱은 목록을 저장할 때 소유자의 UID와 익명 계정 여부도 함께 기록합니다. 동기화를 시작하면 먼저 현재 UID와 저장된 소유자를 비교합니다. 다른 정식 계정의 목록이라면 새 계정의 클라우드 목록과 합치기 전에 기기 목록에서 제외합니다. 익명 소유자의 자료는 앱의 이관 흐름에 따라 남깁니다.

순서가 중요합니다. 소유자 확인 → 다른 정식 계정의 목록 제외 → 변경된 로컬 목록 저장 → 현재 계정의 클라우드 조회와 병합 순서입니다. 메모리에서만 비우고 클라우드 조회가 성공한 뒤 저장하려 했다면, 오프라인 오류로 그 단계에 도달하지 못할 수 있습니다. 앱을 다시 열면 예전 기기 목록이 살아나는 이유입니다.

다만 이 방식에는 대가가 있습니다. A 계정의 클라우드 원본은 지우지 않지만, 아직 업로드하지 못한 A의 로컬 전용 레시피는 기기에서 잃을 수 있습니다. 그런 자료까지 보존해야 한다면 계정별 저장 공간에 따로 보관하고, A가 다시 로그인했을 때만 전송하는 설계를 검토해야 합니다. 현재 수정이 모든 저장 방식의 해답인 것처럼 쓰지 않는 이유입니다.

고친 뒤에도 남은 두 가지 경계

첫째, 이전 버전이 저장한 목록에는 소유자 정보가 없을 수 있습니다. 현재 구현은 그런 목록을 자동으로 비우지 않고 통과시킵니다. 따라서 옛 캐시까지 계정별로 안전하게 구분된다고 보장할 수 없습니다. 서버에서 다시 받을 수 있는 단순 캐시인지, 미전송 자료가 섞였는지에 따라 이행 방식을 따로 정해야 합니다. 소유자를 모르는 자료에 현재 UID를 붙여 새 계정의 것이라고 확정해서는 안 됩니다.

둘째, 업로드를 막는 것과 화면에 이전 목록이 보이지 않게 하는 것은 별도 문제입니다. 계정 전환 직후 로컬 목록을 먼저 표시한다면 소유자 검사와 동기화가 끝나기 전 잠깐 A의 레시피가 보일 수 있습니다. 화면에 넣기 전에도 소유자를 확인하고, 이전 UID로 시작한 늦은 조회 결과를 새 계정 화면에 적용하지 않아야 합니다.

목록·소유자 UID·익명 여부를 SharedPreferences의 별도 키에 저장하는 방식에도 한계가 있습니다. 저장 도중 일부 값만 바뀌거나 앱이 강제 종료될 가능성까지 해결한 구현은 아닙니다. SharedPreferences 문서 역시 쓰기가 반환된 뒤 디스크에 남는다고 보장하지 않으며 중요한 자료의 저장소로 사용하지 말라고 안내합니다. shared_preferences 공식 문서

어디까지 확인했나

수정 당시 기록에는 실기기에서 문제가 발생한 관찰이 있습니다. 수정 후에는 모의 인증·저장소를 사용한 테스트로, 다른 정식 계정의 레시피를 새 익명 계정에 옮기지 않는지, 익명 자료의 이관을 유지하는지, 클라우드 조회 실패 후 앱을 다시 열어도 제외한 목록이 되살아나지 않는지를 확인했습니다. 이것은 수정 후 실기기에서 계정 전환을 다시 검증했다는 뜻은 아닙니다. 강제 종료나 저장 장치 오류도 검증 범위에 포함되지 않았습니다.

같은 문제가 생겼다면 먼저 기기 목록에 소유자 정보가 있는지, 그 목록을 어느 UID의 클라우드 자료와 합치는지 확인해 보세요. 계정 인증이 바뀌었다는 사실만으로 앱이 직접 저장한 목록의 주인까지 바뀌지는 않습니다.

로그인 뒤 함수 호출 자체가 거절되는 문제라면, Firebase 로그인 후에도 검색이 안 될 때 잘못된 안내를 고친 사례처럼 요청 검증과 오류 안내를 따로 살펴볼 수 있습니다.

위로 스크롤