콘텐츠로 이동

회원 / 인증

휴대폰 번호를 단일 신원(identity)으로 사용하는 SMS 인증 기반 회원가입·로그인. 별도 비밀번호 없이 폰 인증코드만으로 가입/로그인이 완결되며, JWT(access token + refresh token)로 세션을 관리합니다.

사용자 여정

신규 가입 / 재가입 흐름

sequenceDiagram
    autonumber
    participant U as 사용자
    participant FE as 프론트엔드
    participant API as Backend (AuthController)
    participant SVC as AuthService
    participant DB as MySQL
    participant SMS as 외부 SMS

    U->>FE: 폰번호 입력 + "인증코드 받기"
    FE->>API: POST /auth/phone-authentications/issue
    API->>SVC: issuePhoneAuthentication(phone, isChange=false)
    SVC->>DB: PhoneAuthentication 조회 (있으면 issue() 갱신, 없으면 create)
    SVC->>SMS: 인증 메시지 발송
    SVC-->>FE: { id, issueCount }

    U->>FE: 인증코드 6자리 입력
    FE->>API: POST /accounts (가입) 또는 로그인 흐름
※ 검증/계정 생성은 AccountController가 담당 Note over API,DB: PhoneAuthentication.verifyAndCreateAccount(code)
코드 일치 확인 → Account.normalUser(phone) 생성 API-->>FE: { accountId, accessToken, refreshToken }

폰 번호 변경 흐름 (로그인 상태)

sequenceDiagram
    autonumber
    participant U as 사용자
    participant FE as 프론트엔드
    participant API as Backend (AuthController)
    participant SVC as AuthService
    participant DB as MySQL

    U->>FE: 새 폰번호 입력 + "인증코드 받기"
    FE->>API: POST /auth/phone-authentications/issue
{ phone, isChange: true } API->>SVC: issuePhoneAuthentication(phone, isChange=true) Note over SVC: 새 폰번호가 이미 다른 계정에서 사용 중이면
AlreadyUsedPhoneException U->>FE: 인증코드 입력 FE->>API: POST /auth/phone-authentications/change
(Authorization: Bearer ) API->>SVC: changePhoneAuthentication(accountId, id, code) SVC->>DB: 새 PhoneAuthentication.verifyAndChangeAccount(code, account)
기존 PhoneAuthentication 삭제 API-->>FE: 200 OK

토큰 재발급 흐름

Access token 만료 시 refresh token으로 재발급. 한 번 사용된 refresh token은 blacklist 등록되어 재사용 불가.

sequenceDiagram
    participant FE as 프론트엔드
    participant API as Backend (AuthController)
    participant SVC as AuthService
    participant DB as MySQL

    FE->>API: POST /auth/reissue
(refresh-token: ) API->>SVC: reissueToken(RT) SVC->>DB: RefreshTokenBlacklist 조회 alt RT가 blacklist에 있음 SVC-->>FE: 401 AlreadyUsedRefreshTokenException else 정상 SVC->>DB: 새 token 생성 + Account.addToken SVC-->>FE: { accountId, accessToken, refreshToken } end

백엔드 구현

계층 클래스 / 파일 역할
Controller AuthController (apps/backend/.../controller/AuthController.kt) /auth/** HTTP endpoint
Service AuthService (apps/backend/.../application/service/auth/AuthService.kt) 인증코드 발급/검증/토큰 재발급 비즈니스 로직
Domain (Entity) PhoneAuthentication (apps/backend/.../application/domain/auth/PhoneAuthentication.kt) 인증 세션 상태 (코드, 만료, 시도 횟수)
Domain (Entity) Account (apps/backend/.../application/domain/auth/Account.kt) 사용자 계정 (status, role, phone)
Domain (Entity) RefreshTokenBlacklist 사용된 refresh token 차단 목록
Security TokenProvider / TokenParser JWT 발급/파싱
Outbound MessageSender (application/service/message/) SMS 발송 추상화 (구현체: SensMessageSender — Naver Cloud SENS. SnsMessageSender도 존재하나 //@Component 주석으로 비활성화)

도메인 규칙

규칙 위치 값 / 설명
인증코드 길이 PhoneAuthentication.makeCode() 6자리 숫자 (100000~999999)
인증코드 만료 PhoneAuthentication.verify() 305초 (5분 5초) — 초과 시 VerificationTimeExceedException
일일 발급 횟수 카운터 PhoneAuthentication.initializeAttemptCount() 날짜가 바뀌면 issueCount 자동 0으로 리셋
카드사 심사 백도어 AuthService.issuePhoneAuthentication() 폰번호 01099999999 → 코드 항상 999999, SMS 미발송 (PG사 심사용)
회원탈퇴 후 재가입 Account.isReJoinAvailable() 탈퇴 후 일정 시간 경과해야 재가입 가능 — 위반 시 RejoinFailException
폰 변경 시 중복 차단 AuthService.issuePhoneAuthentication(phone, isChange=true) 새 폰번호가 다른 계정에 이미 매핑돼 있으면 AlreadyUsedPhoneException
Refresh token 1회 사용 AuthService.reissueToken() 사용된 RT는 RefreshTokenBlacklist에 등록 → 재사용 시 AlreadyUsedRefreshTokenException

API 엔드포인트

Method Path 설명
POST /auth/phone-authentications/issue 인증코드 발급 (가입/재발급/변경 모두 사용)
POST /auth/phone-authentications/change 로그인 상태에서 폰번호 변경
POST /auth/reissue Access token 재발급 (refresh-token 헤더 필요)
GET /auth/test SMS 발송 확인용 (dev 도구)

스펙 상세(request/response schema)는 API 레퍼런스 / OpenAPI 페이지에서 endpoint별 검색.

신규 회원가입 시 호출되는 verifyAndCreateAccountAccountController(/accounts/**)가 담당. 멤버십·구독 페이지의 "최초 가입 시 FREE 멤버십 자동 부여" 절 참조.

데이터 모델

erDiagram
    PHONE_AUTHENTICATION {
        string phone_authentication_id PK "UUID"
        string phone "11자리 (예: 01099999999)"
        string code "6자리"
        int issue_count "일별 누적 발급 횟수"
        datetime last_issue_time
        boolean join_status
        string account_id FK "nullable"
    }
    ACCOUNT {
        string account_id PK
        string phone
        string name
        string role "GUEST / USER / ADMIN"
        string account_status "ACTIVE / WITHDRAWAL"
    }
    REFRESH_TOKEN_BLACKLIST {
        string refresh_token PK
        datetime expired_time
    }
    PHONE_AUTHENTICATION ||--o| ACCOUNT : "account_id (1:0..1)"

보안 설정

항목 위치 비고
JWT 서명 키 application-{prod,local}.yml jwt.secret prod/local 다른 값 (dev는 별도 yml 없이 local 프로파일 사용). 운영 시 환경변수로 외부화 권장
Access token 유효기간 TokenType.kt:7 ACCESS = 30분
Refresh token 유효기간 TokenType.kt:10 REFRESH = 7일
CORS allowed origins SecurityConfig.kt:200-207 localhost:* 와일드카드 + *.bootalk.co.kr 와일드카드 + semugpt.co.kr, www.semugpt.co.kr, new.semugpt.co.kr (#151 4.5f)

알려진 이슈 / 개선 예정

  • 카드사 심사 백도어 (01099999999999999): PG사 심사 통과 후 코드에서 제거 예정 (AuthService.kt//TODO - 카드사 심사 통과시 제거 주석)
  • 재시도 제한 미구현: 잘못된 코드를 무제한 시도 가능 (rate limit 부재) — 보안 개선 여지
  • OAuth2 도메인 코드 존재 (OAuth2Account, OAuth2Provider 등) — 현재 미사용. 추후 카카오/네이버 로그인 도입 시 활용
  • /auth/test는 운영 환경 노출 위험: dev tooling이지만 profile gate 미적용 — 운영 노출 시 임의 번호로 SMS 발송 가능. (별도 PR로 profile-gate 적용 필요. #170은 /accounts/test/**에만 적용됨)

관련 문서