결정-0059: 프로필 변경은 재증명을 요구하되 현재 세션을 남긴다
상태
승인됨. 2026-08-17에 M5 프로필 착수 전 결정했습니다.
맥락
M5 프로필의 네 요구사항 중 셋이 인증 수단을 건드립니다. 비밀번호 변경(PROF-03), 소셜 연동과 해제(PROF-04), 이메일 변경(PROF-05)이 그것이며, 닉네임 변경(PROF-01)만 표시용 값에 그칩니다.
요구사항 문서가 PROF-03의 세션 처리를 "정책은 설계에서 결정"으로 비워 둔 채 M4까지 왔습니다. 착수하려면 그 자리를 채워야 하고, 채우는 김에 성격이 같은 두 자리도 함께 정합니다. 셋은 모두 프로필을 바꾸는 행위가 계정 접근에 무엇을 하는가라는 한 질문의 갈래입니다.
당시 전제는 다음과 같습니다. 세션은 DB의 불투명 토큰이며 무효화가 행 삭제로 즉시 반영됩니다
(결정-0014). 이메일 소유 증명은 자체 OTP로 하며 제공자의 이메일을
신뢰하지 않습니다(결정-0015). 인증 수단은 accounts 테이블의
한 행이 하나이며 credential과 소셜 제공자가 같은 표를 씁니다.
확인한 사실
결정-0014가 이미 절반을 정해 두었습니다. 그 문서는 JWT 액세스 레이어를 유보한 근거로 "즉시 무효화가 필요한 요구사항이 이미 셋"을 들었고 그 첫 번째가 "비밀번호 변경 시 다른 세션 정리 (PROF-03)"입니다. 즉 다른 기기를 끊는다는 것은 결정되어 있었고, 남은 것은 변경을 수행한 세션 자신의 처리였습니다.
비밀번호 재설정(AUTH-07)은 요청한 세션까지 끊습니다. 코드는 이미 그렇게 동작하며 주석에 근거가 적혀 있습니다. 재설정은 로그인하지 못하는 상태에서 메일함만으로 수행되므로, 그 시점에 계정에 붙어 있던 세션 중 어느 것도 정당하다고 볼 근거가 없습니다.
변경(PROF-03)은 전제가 다릅니다. 현재 비밀번호를 제시한 사람이 수행하므로 그 세션의 정당성은 요청 자체가 증명합니다. 같은 처리를 할 이유가 재설정에서 왔을 뿐 변경에는 성립하지 않습니다.
검토한 대안
세션 처리 — 요청한 세션까지 끊는다
재설정과 코드가 같아집니다. deleteAllByUserId 하나로 끝나므로 저장소에 새 연산이 필요 없고,
두 흐름의 동작을 설명할 때 예외를 두지 않아도 됩니다.
기각 사유는 방금 본인임을 증명한 사람을 로그인 화면으로 보낸다는 점입니다. 이탈 지점이 하나 늘어나고, 그 대가로 얻는 보안 이득이 없습니다 — 끊으려는 대상인 "탈취된 접근"은 다른 기기의 세션이며 그것은 어느 안에서도 끊깁니다.
세션 처리 — 현재 세션을 유지하되 토큰을 회전한다
변경 전에 유출된 현재 세션 토큰까지 무효화됩니다. 세션 고정 공격을 다루는 정석적인 구성입니다.
기각 사유는 계약이 늘어난다는 점입니다. 응답에서 쿠키를 다시 내리는 경로가 필요하고, Bearer 클라이언트는 새 토큰을 받아 저장해야 합니다. 그런데 막으려는 것은 현재 세션 토큰이 이미 유출된 상태이며, 그 경우 공격자는 비밀번호 변경 자체를 수행할 수 있으므로 회전이 방어선이 되지 못합니다. 세션 토큰 유출은 기기 목록과 개별 로그아웃(AUTH-06)이 다루는 문제입니다.
세션 처리 — 다른 세션만 끊고 현재 세션을 유지한다 (채택)
저장소에 "이 세션만 빼고 삭제" 연산이 하나 늘어납니다. 그것이 감수하는 전부입니다.
마지막 인증 수단 판정 — 응용 계층의 분기
해제 서비스에서 목록을 세어 1이면 거절합니다. 구현이 짧습니다.
기각 사유는 검사가 호출자마다 반복된다는 점입니다. 해제 경로는 지금 하나지만 계정 삭제와 관리자 도구가 생기면 늘어나고, 한 곳이라도 빠뜨리면 로그인할 수 없는 계정이 만들어집니다. 그 상태는 사용자가 스스로 되돌릴 수 없으며 계정 복구(AUTH-08)마저 메일함 접근을 전제합니다.
소셜 연동 추가 — 별도의 연동 전용 진입점
/oauth2/link/{provider} 같은 경로를 새로 열고 그 경로로 들어온 인증만 연동으로 처리합니다.
의도가 URL에 드러납니다.
기각 사유는 Spring Security의 oauth2Login이 진입점을 하나로 전제한다는 점입니다. 두 번째
인가 요청 경로를 만들려면 필터 체인을 하나 더 두거나 리졸버를 갈라야 하고, 그 복잡도가 얻는
것보다 큽니다. 판정에 필요한 정보는 이미 요청에 있습니다.
결정
비밀번호 변경은 다른 세션을 모두 끊고 요청한 세션만 남깁니다. 비밀번호 재설정(AUTH-07)의 전 세션 삭제는 그대로 둡니다. 두 흐름의 처리가 다른 것은 전제가 다르기 때문이며, 재설정은 요청자의 정당성을 증명할 수단이 없고 변경은 있습니다.
인증 수단을 바꾸는 흐름은 그 시점의 소유를 다시 증명시킵니다. 비밀번호 변경은 현재 비밀번호를, 이메일 변경은 새 주소의 OTP를 요구합니다. 세션이 살아 있다는 사실만으로는 충분하지 않습니다. 닉네임 변경은 인증 수단이 아니므로 재증명이 없습니다.
마지막 인증 수단 판정은 도메인 불변식으로 세웁니다. 한 사용자의 인증 수단 전체를 감싸는 타입을 두고 해제를 그 타입의 연산으로 만듭니다. 마지막 하나를 지우려는 시도는 그 타입이 거절하므로, 응용 계층이 검사를 빠뜨려도 잠긴 계정이 만들어지지 않습니다.
소셜 연동 추가는 진입점을 나누지 않고 인증 완료 시점의 로그인 상태로 판정합니다. 제공자 인증을 마쳤을 때 요청에 유효한 세션이 실려 있으면 연동이고, 없으면 로그인 또는 가입입니다. 그 신원이 다른 계정에 이미 붙어 있으면 연결하지 않고 오류로 돌려보냅니다.
이메일 변경은 세션에 영향을 주지 않습니다. 주소를 옮기는 일이며 그 시점에 살아 있던 세션이 의심스러워지는 사건이 아닙니다.
주의: 소셜 연동은 제공자 신원을 옮기지 않습니다
이미 다른 계정에 붙은 제공자 신원을 연결 요청받으면 기존 연결을 끊고 옮기는 처리를 금지합니다. 옮기면 공격자가 자기 소셜 계정을 피해자 계정에 연결한 뒤 그것으로 로그인하는 경로가 열립니다. 옮기려는 사용자는 원래 계정에서 먼저 해제해야 하며, 그 해제는 마지막 수단 불변식을 통과해야 합니다.
결과
- 비밀번호를 바꾼 사용자가 다시 로그인하지 않습니다. 다른 기기는 끊깁니다.
- 계정 잠김이 구조로 막힙니다. 해제 경로가 늘어나도 같은 검사를 다시 쓰지 않습니다.
- 소셜 연동에 새 진입점과 필터 체인이 필요 없습니다. 대신 인증 성공 처리기가 세션 쿠키를 직접 읽어야 하며, 그 지점은 인증 필터가 아직 돌지 않은 자리입니다.
- 감수하는 것은 세션 저장소의 연산 하나(
deleteAllByUserIdExcept)와, 재설정과 변경의 세션 처리가 달라 두 흐름을 각각 설명해야 한다는 점입니다.
재검토 조건
- 계정 삭제나 관리자 도구가 인증 수단을 지우게 되면 그 경로도 같은 도메인 타입을 지나는지 확인합니다. 지나지 않는 경로가 생기면 불변식이 아니라 관습이 됩니다.
- 세션 토큰 회전이 다른 이유로 필요해지면 비밀번호 변경도 그 경로에 태웁니다. 위에서 기각한 것은 회전 자체가 아니라 이 요구사항만을 위해 회전을 도입하는 것입니다.