회원 / 인증¶
휴대폰 번호를 단일 신원(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:
백엔드 구현¶
| 계층 | 클래스 / 파일 | 역할 |
|---|---|---|
| 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별 검색.
신규 회원가입 시 호출되는
verifyAndCreateAccount는AccountController(/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) |
알려진 이슈 / 개선 예정¶
- 카드사 심사 백도어 (
01099999999→999999): PG사 심사 통과 후 코드에서 제거 예정 (AuthService.kt의//TODO - 카드사 심사 통과시 제거주석) - 재시도 제한 미구현: 잘못된 코드를 무제한 시도 가능 (rate limit 부재) — 보안 개선 여지
- OAuth2 도메인 코드 존재 (
OAuth2Account,OAuth2Provider등) — 현재 미사용. 추후 카카오/네이버 로그인 도입 시 활용 /auth/test는 운영 환경 노출 위험: dev tooling이지만 profile gate 미적용 — 운영 노출 시 임의 번호로 SMS 발송 가능. (별도 PR로 profile-gate 적용 필요. #170은/accounts/test/**에만 적용됨)
관련 문서¶
- 멤버십·구독 — 가입 직후 FREE 멤버십 부여 흐름
- 관리자 콘솔 — 어드민이 계정 상태/권한 변경
- API 레퍼런스 / OpenAPI — 모든 endpoint의 request/response schema