3 분 소요

이메일 대신 휴대폰 번호를?

한국에서 서비스할 예정이며 1인 1계정을 정책으로 한다면 휴대폰 인증은 매우 좋은 선택지가 된다. 물론 한 사람이 여러 개의 휴대폰 번호 (세컨폰 등)를 가질 수 있다는 엣지 케이스도 있지만, 이메일보다는 효과적으로 1인 1계정을 강요할 수 있는 수단이기 때문이다.

NICE API (PASS)등을 활용하면 보다 편리하고 더욱 효과적으로 1인 1계정을 강요할 수 있지만, 나는 다음과 같은 이유로 휴대폰 인증 과정을 직접 개발하기로 했다.

첫째, PASS를 통한 인증은 귀찮다.
이동통신사를 먼저 골라야 하고(알뜰폰이면 한번 더 눌러야 한다), 각종 이용약관에 동의함을 눌러야 하며, 이름(실명), 휴대폰 번호, 보안문자까지 입력한 다음에 인증문자를 받아서 인증문자를 입력해야 인증 프로세스가 완료된다. PASS 앱이 휴대폰에 설치되어 있지 않다면 생년월일, 국적(내국인, 외국인), 성별 등의 정보를 추가로 입력해야 한다.
이러한 과정은 대다수의 앱 사용자가 귀찮아하며, 앱을 설치하고 가입을 포기하게 되는 요소로도 작용할 수 있다.

둘째, 휴대폰 인증 과정 기획을 한번 해 보고 싶었다.
Cursor나 Github Copilot 등 코드 컨텍스트를 읽어서 자동으로 작성해주는 AI 툴들이 많이 발전함에 따라 개발자는 이제 귀찮고 반복적인 일들을 하지 않아도 되는 시대가 왔다. 이러한 AI 툴들이 더욱 발전한다면, 개발자의 역할은 계속 윗 레벨으로 올라가게 될 것이라고 생각한다. 나중에는 개발자가 기획까지 하면서 혼자서 서비스를 만들어내는 시대가 올 것이고, 그렇다면 기획하는 능력이 개발자의 스펙으로 작용할 수도 있을 것이다.

인증 시나리오

01 우선 회원가입과 인증 과정을 대략적으로 설명하겠다.

  • 전화번호 및 비밀번호
    • 회원가입 : 전화번호 입력 -> 인증문자 발송 -> 인증코드 입력 -> 이름, 비밀번호, 비밀번호 확인 입력 -> 회원가입 완료
    • 로그인 : 전화번호, 비밀번호 입력 -> 로그인
  • 카카오 OAuth
    • 카카오 SDK를 사용하여 카카오 AccessToken을 내 백엔드 서버로 전달 -> 백엔드 서버에서 카카오 AccessToken을 가지고 카카오 서버에 요청해서 해당 회원의 카카오 id, 카카오 이름 등을 가져옴(카카오 developers에서 가져올 정보를 고를 수 있다)
      • 이미 가입된 사용자라면 로그인
      • 신규 사용자라면 전화번호 입력 -> 인증문자 발송 -> 인증코드 입력 -> 이름 입력 -> 회원가입 완료

휴대폰 번호가 사용자를 식별하는 주요 컬럼이고, 여기서 카카오를 사용하면 카카오계정으로, 비밀번호를 사용하면 비밀번호로 인증한다.

개발 중 문제 발생

처음에는 사용자(Member) 테이블의 DDL을 설계할 때 휴대폰 번호에 UNIQUE 제약조건을 단순하게 걸어버렸다. 한 사람당 하나의 전화번호를 강제하도록 했기 때문이였다.

CREATE TABLE member (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(30) NOT NULL,
    phone VARCHAR(40) UNIQUE,
    password VARCHAR(255),
    ...

그런데, 휴대폰 인증 과정을 생각하면서 기획하다 보니 한 가지 시나리오가 앞으로의 휴대폰 인증 개발 과정을 매우 험난하게 만들어 버렸다.

휴대폰 번호가 바뀌면 어떡하지?

생각해 보니 휴대폰 번호를 바꿀 수 있었던 것이였다. 여기까지도 괜찮았다. 휴대폰번호를 재인증하면 되니까.
그런데 바뀐 휴대폰번호를 다른 사용자가 사용한다면 어떻게 해야 할까? 상황 이해를 돕기 위해 아래와 같은 시나리오를 상상해 보자.

  • 사용자 A는 010-1111-1111이라는 번호로 서비스를 가입해서 사용중임.
  • 사용자 A가 번호를 010-2222-2222로 바꿈. 그러나 같은 폰에 유심만 갈아끼웠을 때는 설치된 앱을 그대로 사용하므로, 사용자 A의 계정의 전화번호는 010-1111-1111으로 여전히 사용중임.
  • 사용자 B가 번호를 010-1111-1111으로 바꿈(예전에 A가 사용하던 번호).
  • 사용자 B가 010-1111-1111이라는 번호로 서비스를 가입하려는데 휴대폰 번호는 UNIQUE이므로, 백엔드의 로직상 가입을 거부당한다.

이렇게 된다면 사용자 B는 영문도 모른 채 자신의 번호로 서비스를 가입할 수 없게 된다.
그렇다면 휴대폰 번호의 UNIQUE를 없애면 어떨까?

  • 사용자 B가 010-1111-1111이라는 번호로 서비스 가입에 성공한다.
  • 그러나 사용자 A도 010-1111-1111이라는 번호로 서비스를 이용한다.

당연하게도 한 전화번호로 두 명의 사용자가 서비스를 이용하게 된다.
그래서 재인증 절차를 도입하게 되었다. 우선 사용자의 상태 컬럼에 PHONE_V_REQUIRED를 추가하였다.

public enum MemberStatus {
        CREATED,    // 계정 생성 직후
        ACTIVE,     // 활동 중
        BANNED,     // 활동 정지
        WITHDRAWN,  // 탈퇴됨
        PHONE_V_REQUIRED // 전화번호 재인증 필요 (추가)
    }

재인증을 도입하면, 다음과 같이 작동한다.

  • 사용자 B가 010-1111-1111이라는 번호로 서비스 가입을 하면서,
  • 010-1111-1111 번호를 가진 계정을 모두 검색한다(탈퇴한 계정 제외).
    • SELECT *
        FROM member
        WHERE phone = #{phone} AND status != 'WITHDRAWN'
      
  • 방금 가입한 B의 계정을 제외한 검색된 나머지 다른 계정들의 상태를 PHONE_V_REQUIRED로 바꿔버린다.
    •   private void changeMemberStatusToPhoneVerificationRequiredWhoHaveSavePhone(String newPhone, Long memberId) {
            List<Member> findMembers = memberMapper.findByPhone(newPhone);
            for (Member m : findMembers) {
                if (!m.getId().equals(memberId)) {
                    memberMapper.updateStatus(m.getId(), MemberStatus.PHONE_V_REQUIRED);
                }
            }
        }
      
  • A가 서비스에 로그인 시, A의 계정의 상태가 PHONE_V_REQUIRED이다.

이렇게 된다면, PHONE_V_REQUIRED인 계정은 휴대폰 번호 재인증을 거쳐야(활성화) 서비스를 사용하게 하면 된다.
이러면 문제가 모두 해결된 것 같았으나 ……

사용자는 어떤 행동을 할지 모른다

문제는 위와 같이 개발한다면 한 사용자가 하나의 번호를 가지고 여러개의 계정을 만들어서 돌려 쓸 수 있다는 치명적인 결함이 발생하게 된다.
이 문제를 일으키는 시나리오는 다음과 같다.

  • 사용자 C가 010-1111-1111으로 계정 1을 만든다.
  • 사용자 C가 계정 1을 로그아웃 후, 010-1111-1111으로 계정 2를 만든다. 이때의 계정 상태는 다음과 같다.
    • 계정 1: PHONE_V_REQUIRED
    • 계정 2: ACTIVE
  • 사용자 C가 계정 2를 로그아웃 후, 계정 1에 로그인해 010-1111-1111으로 재인증을 받고 계정 1을 활성화시킨다. 이때의 계정 상태는 다음과 같다.
    • 계정 1: ACTIVE
    • 계정 2: PHONE_V_REQUIRED

두번째와 세번째를 반복한다면, 사용자 C는 하나의 전화번호로 두 개의 계정을 돌려 쓸 수 있게 된다. 조금 더 머리가 좋은 사용자라면 계정 3개, 4개도 충분히 가능할 것이다.

따라서 재인증 시 해당 계정에 원래 등록된 전화번호로 재인증이 불가하도록 설정해 주었다.

if (currentPhone.equals(newPhone)) {
    throw new CustomException(ErrorEnum.NEED_ANOTHER_PHONE);
}

이로써 휴대폰 인증 과정을 성공적으로 기획하고 개발할 수 있었다.

댓글남기기