387 lines
13 KiB
Markdown
387 lines
13 KiB
Markdown
# User Service 내부 시퀀스 설계서
|
|
|
|
## 문서 정보
|
|
- **작성일**: 2025-10-22
|
|
- **작성자**: System Architect
|
|
- **버전**: 1.0
|
|
- **관련 문서**:
|
|
- [유저스토리](../../../userstory.md)
|
|
- [외부 시퀀스](../outer/사용자인증플로우.puml)
|
|
- [논리 아키텍처](../../logical/logical-architecture.md)
|
|
|
|
---
|
|
|
|
## 개요
|
|
|
|
User Service의 4가지 주요 시나리오에 대한 내부 처리 흐름을 상세히 정의합니다.
|
|
|
|
### 시나리오 목록
|
|
|
|
| 번호 | 파일명 | 유저스토리 | 주요 처리 내용 |
|
|
|------|--------|-----------|---------------|
|
|
| 1 | user-회원가입.puml | UFR-USER-010 | 기본 정보 검증, 사업자번호 검증(국세청 API), 트랜잭션 처리, JWT 발급 |
|
|
| 2 | user-로그인.puml | UFR-USER-020 | 비밀번호 검증(bcrypt), JWT 발급, 세션 저장, 최종 로그인 시각 업데이트 |
|
|
| 3 | user-프로필수정.puml | UFR-USER-030 | 기본 정보 수정, 매장 정보 수정, 비밀번호 변경, 트랜잭션 처리 |
|
|
| 4 | user-로그아웃.puml | UFR-USER-040 | JWT 검증, 세션 삭제, Blacklist 추가 |
|
|
|
|
---
|
|
|
|
## 아키텍처 구조
|
|
|
|
### Layered Architecture
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ API Layer (Controller) │ - HTTP 요청/응답 처리
|
|
│ │ - DTO 변환 및 검증
|
|
└─────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────┐
|
|
│ Business Layer (Service) │ - 비즈니스 로직 처리
|
|
│ - UserService │ - 트랜잭션 관리
|
|
│ - AuthenticationService │ - 외부 API 연동
|
|
│ - BusinessValidator │
|
|
└─────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────┐
|
|
│ Data Layer (Repository) │ - 데이터베이스 접근
|
|
│ - UserRepository │ - JPA/MyBatis
|
|
│ - StoreRepository │
|
|
└─────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────┐
|
|
│ External Systems │ - 국세청 API
|
|
│ │ - Redis Cache
|
|
│ │ - PostgreSQL DB
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
### 주요 컴포넌트
|
|
|
|
#### API Layer
|
|
- **UserController**: 사용자 관련 REST API 엔드포인트
|
|
- `POST /api/users/register`: 회원가입
|
|
- `POST /api/users/login`: 로그인
|
|
- `PUT /api/users/profile`: 프로필 수정
|
|
- `POST /api/users/logout`: 로그아웃
|
|
|
|
#### Business Layer
|
|
- **UserService**: 사용자 정보 관리 비즈니스 로직
|
|
- **AuthenticationService**: 인증 및 세션 관리 로직
|
|
- **BusinessValidator**: 사업자번호 검증 로직 (Circuit Breaker 적용)
|
|
|
|
#### Data Layer
|
|
- **UserRepository**: users 테이블 CRUD
|
|
- **StoreRepository**: stores 테이블 CRUD
|
|
|
|
#### Utility
|
|
- **PasswordEncoder**: bcrypt 해싱 (Cost Factor 10)
|
|
- **JwtTokenProvider**: JWT 토큰 생성/검증 (만료 7일)
|
|
|
|
---
|
|
|
|
## 시나리오별 상세 설명
|
|
|
|
### 1. 회원가입 (user-회원가입.puml)
|
|
|
|
#### 처리 단계
|
|
1. **입력 검증**: `@Valid` 어노테이션으로 DTO 검증
|
|
2. **중복 사용자 확인**: 전화번호 기반 중복 체크
|
|
3. **사업자번호 검증**:
|
|
- Redis 캐시 확인 (TTL 7일)
|
|
- 캐시 MISS: 국세청 API 호출 (Circuit Breaker 적용)
|
|
- 캐시 HIT: 0.1초, MISS: 5초
|
|
4. **비밀번호 해싱**: bcrypt (Cost Factor 10)
|
|
5. **사업자번호 암호화**: AES-256
|
|
6. **데이터베이스 트랜잭션**:
|
|
- User INSERT
|
|
- Store INSERT
|
|
- COMMIT (실패 시 자동 Rollback)
|
|
7. **JWT 토큰 생성**: Claims(userId, role=OWNER, exp=7일)
|
|
8. **세션 저장**: Redis (TTL 7일)
|
|
|
|
#### Resilience 패턴
|
|
- **Circuit Breaker**: 국세청 API (실패율 50% 초과 시 Open)
|
|
- **Retry**: 최대 3회 (지수 백오프: 1초, 2초, 4초)
|
|
- **Timeout**: 5초
|
|
- **Fallback**: 사업자번호 검증 스킵 (수동 확인 안내)
|
|
|
|
#### 응답 시간
|
|
- 캐시 HIT: 1초 이내
|
|
- 캐시 MISS: 5초 이내
|
|
|
|
---
|
|
|
|
### 2. 로그인 (user-로그인.puml)
|
|
|
|
#### 처리 단계
|
|
1. **입력 검증**: 필수 필드 확인
|
|
2. **사용자 조회**: 전화번호로 사용자 검색
|
|
3. **비밀번호 검증**: bcrypt compare
|
|
4. **JWT 토큰 생성**: Claims(userId, role=OWNER, exp=7일)
|
|
5. **세션 저장**: Redis (TTL 7일)
|
|
6. **최종 로그인 시각 업데이트**: 비동기 처리 (`@Async`)
|
|
|
|
#### 보안 처리
|
|
- 에러 메시지: 전화번호/비밀번호 구분 없이 동일 메시지 반환
|
|
- 비밀번호: bcrypt compare (원본 노출 안 됨)
|
|
|
|
#### 성능 최적화
|
|
- 최종 로그인 시각 업데이트: 비동기 처리로 응답 시간 단축
|
|
- 응답 시간: 0.5초 목표
|
|
|
|
---
|
|
|
|
### 3. 프로필 수정 (user-프로필수정.puml)
|
|
|
|
#### 처리 단계
|
|
1. **JWT 인증**: `@AuthenticationPrincipal`로 userId 추출
|
|
2. **사용자 조회**: userId로 기존 정보 조회
|
|
3. **비밀번호 변경 처리** (선택적):
|
|
- 현재 비밀번호 검증 (bcrypt compare)
|
|
- 새 비밀번호 해싱 (bcrypt)
|
|
4. **기본 정보 업데이트**: 이름, 전화번호, 이메일
|
|
5. **매장 정보 업데이트**: 매장명, 업종, 주소, 영업시간
|
|
6. **데이터베이스 트랜잭션**:
|
|
- User UPDATE
|
|
- Store UPDATE
|
|
- COMMIT (실패 시 자동 Rollback)
|
|
7. **캐시 무효화**: 프로필 캐시 삭제 (선택적)
|
|
|
|
#### 보안 처리
|
|
- 비밀번호 변경: 현재 비밀번호 확인 필수
|
|
- 권한 검증: 본인만 수정 가능
|
|
|
|
#### 향후 개선사항
|
|
- 전화번호 변경: SMS/이메일 재인증 구현
|
|
- 이메일 변경: 이메일 인증 구현
|
|
|
|
---
|
|
|
|
### 4. 로그아웃 (user-로그아웃.puml)
|
|
|
|
#### 처리 단계
|
|
1. **JWT 인증**: `@AuthenticationPrincipal`로 userId 추출
|
|
2. **JWT 토큰 검증**: 서명 및 만료 시간 확인
|
|
3. **Redis 세션 삭제**: `DEL user:session:{token}`
|
|
4. **JWT Blacklist 추가** (선택적):
|
|
- 만료되지 않은 토큰 강제 무효화
|
|
- Redis에 Blacklist 추가 (TTL: 남은 만료 시간)
|
|
5. **로그아웃 로그 기록**: userId, timestamp
|
|
|
|
#### 보안 처리
|
|
- JWT Blacklist: 만료 전 토큰 강제 무효화
|
|
- 멱등성 보장: 중복 로그아웃 요청에 안전
|
|
|
|
#### 클라이언트 측 처리
|
|
- LocalStorage 또는 Cookie에서 JWT 토큰 삭제
|
|
- 로그인 화면으로 리다이렉트
|
|
|
|
#### 성능 최적화
|
|
- Redis 삭제 연산: O(1) 시간 복잡도
|
|
- 응답 시간: 0.1초 이내
|
|
|
|
---
|
|
|
|
## 데이터 모델
|
|
|
|
### User Entity
|
|
```java
|
|
@Entity
|
|
@Table(name = "users")
|
|
public class User {
|
|
@Id
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
private Long userId;
|
|
|
|
@Column(nullable = false, length = 100)
|
|
private String name;
|
|
|
|
@Column(nullable = false, unique = true, length = 20)
|
|
private String phoneNumber;
|
|
|
|
@Column(nullable = false, unique = true, length = 255)
|
|
private String email;
|
|
|
|
@Column(nullable = false, length = 60)
|
|
private String passwordHash; // bcrypt hash
|
|
|
|
@Enumerated(EnumType.STRING)
|
|
@Column(nullable = false, length = 20)
|
|
private UserRole role; // OWNER, CUSTOMER
|
|
|
|
@Column(name = "last_login_at")
|
|
private LocalDateTime lastLoginAt;
|
|
|
|
@Column(name = "created_at", nullable = false)
|
|
private LocalDateTime createdAt;
|
|
|
|
@Column(name = "updated_at")
|
|
private LocalDateTime updatedAt;
|
|
}
|
|
```
|
|
|
|
### Store Entity
|
|
```java
|
|
@Entity
|
|
@Table(name = "stores")
|
|
public class Store {
|
|
@Id
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
private Long storeId;
|
|
|
|
@Column(name = "user_id", nullable = false)
|
|
private Long userId;
|
|
|
|
@Column(nullable = false, length = 200)
|
|
private String storeName;
|
|
|
|
@Column(length = 100)
|
|
private String industry;
|
|
|
|
@Column(columnDefinition = "TEXT")
|
|
private String address;
|
|
|
|
@Column(name = "business_number_encrypted", length = 255)
|
|
private String businessNumberEncrypted; // AES-256 encrypted
|
|
|
|
@Column(name = "business_hours", length = 500)
|
|
private String businessHours;
|
|
|
|
@Column(name = "created_at", nullable = false)
|
|
private LocalDateTime createdAt;
|
|
|
|
@Column(name = "updated_at")
|
|
private LocalDateTime updatedAt;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 캐싱 전략
|
|
|
|
### Redis 캐시 키 구조
|
|
|
|
| 캐시 키 패턴 | 데이터 타입 | TTL | 용도 |
|
|
|------------|-----------|-----|------|
|
|
| `user:session:{token}` | String (JSON) | 7일 | JWT 세션 정보 (userId, role) |
|
|
| `user:business:{사업자번호}` | String (JSON) | 7일 | 사업자번호 검증 결과 (valid, status) |
|
|
| `jwt:blacklist:{token}` | String | 남은 만료 시간 | 로그아웃된 JWT 토큰 Blacklist |
|
|
| `user:profile:{userId}` | String (JSON) | 1시간 | 사용자 프로필 정보 (선택적) |
|
|
|
|
### Cache-Aside 패턴
|
|
1. Application → Redis 확인 (Cache HIT/MISS)
|
|
2. Cache MISS → Database/External API 조회
|
|
3. Database/External API → Redis 캐싱 (TTL 설정)
|
|
4. Redis → Application 반환
|
|
|
|
---
|
|
|
|
## 에러 처리
|
|
|
|
### 주요 예외 클래스
|
|
|
|
| 예외 클래스 | HTTP 상태 | 발생 시점 |
|
|
|-----------|---------|----------|
|
|
| `DuplicateUserException` | 400 | 이미 가입된 전화번호 |
|
|
| `BusinessNumberInvalidException` | 400 | 사업자번호 검증 실패 |
|
|
| `AuthenticationFailedException` | 401 | 로그인 실패 (전화번호/비밀번호 불일치) |
|
|
| `InvalidTokenException` | 401 | JWT 토큰 무효 |
|
|
| `UserNotFoundException` | 404 | 사용자 없음 |
|
|
| `InvalidPasswordException` | 400 | 현재 비밀번호 불일치 (프로필 수정) |
|
|
|
|
### 에러 응답 형식
|
|
```json
|
|
{
|
|
"error": "에러 메시지",
|
|
"code": "ERROR_CODE",
|
|
"timestamp": "2025-10-22T10:30:00Z"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 보안 고려사항
|
|
|
|
### 1. 비밀번호 보안
|
|
- **해싱 알고리즘**: bcrypt (Cost Factor 10)
|
|
- **원본 노출 방지**: 비밀번호는 해시로만 저장, 평문 로깅 금지
|
|
- **에러 메시지**: 전화번호/비밀번호 구분 없이 동일 메시지 반환 (보안 강화)
|
|
|
|
### 2. JWT 보안
|
|
- **만료 시간**: 7일 (Refresh Token 별도 구현 가능)
|
|
- **서명 알고리즘**: HS256 또는 RS256
|
|
- **Blacklist 관리**: 로그아웃 시 Redis Blacklist에 추가
|
|
|
|
### 3. 민감 정보 암호화
|
|
- **사업자번호**: AES-256 암호화 저장
|
|
- **전송 보안**: HTTPS 강제 적용
|
|
|
|
### 4. 세션 관리
|
|
- **세션 저장**: Redis (서버 재시작에도 유지)
|
|
- **세션 만료**: 7일 후 자동 삭제 (TTL)
|
|
- **동시 세션**: 동일 사용자 다중 세션 허용 (필요 시 제한 가능)
|
|
|
|
---
|
|
|
|
## 성능 최적화
|
|
|
|
### 1. 캐싱 효과
|
|
- **사업자번호 검증**: 5초 → 0.1초 (98% 개선)
|
|
- **세션 조회**: Redis 사용으로 DB 부하 감소
|
|
|
|
### 2. 비동기 처리
|
|
- **최종 로그인 시각 업데이트**: `@Async`로 비동기 처리
|
|
- **응답 시간 개선**: 0.5초 목표 달성
|
|
|
|
### 3. 데이터베이스 최적화
|
|
- **인덱스**: `phone_number`, `email` 컬럼에 Unique Index
|
|
- **Connection Pool**: HikariCP 사용 (최소 10개, 최대 50개)
|
|
|
|
---
|
|
|
|
## 테스트 전략
|
|
|
|
### Unit Test
|
|
- UserService, AuthenticationService 단위 테스트
|
|
- Mock 객체: UserRepository, Redis, 국세청 API
|
|
|
|
### Integration Test
|
|
- Controller → Service → Repository 통합 테스트
|
|
- 실제 Redis 및 PostgreSQL 사용 (Testcontainers)
|
|
|
|
### E2E Test
|
|
- Postman 또는 REST Assured로 전체 플로우 테스트
|
|
- 회원가입 → 로그인 → 프로필 수정 → 로그아웃
|
|
|
|
---
|
|
|
|
## 향후 개선사항
|
|
|
|
### Phase 2
|
|
1. **Refresh Token 구현**: Access Token 만료 시 갱신 메커니즘
|
|
2. **소셜 로그인**: 카카오, 네이버, 구글 OAuth 2.0 연동
|
|
3. **2FA (Two-Factor Authentication)**: SMS 또는 TOTP 기반 2단계 인증
|
|
4. **비밀번호 재설정**: 이메일/SMS를 통한 비밀번호 재설정 기능
|
|
|
|
### Phase 3
|
|
1. **계정 잠금 정책**: 로그인 5회 실패 시 계정 잠금
|
|
2. **세션 관리 고도화**: 동시 세션 수 제한, 세션 활성 기록
|
|
3. **감사 로그**: 민감 작업(로그인, 비밀번호 변경) 감사 로그 저장
|
|
4. **Rate Limiting**: 로그인 API에 Rate Limiting 적용 (사용자당 5회/분)
|
|
|
|
---
|
|
|
|
## 참고 문서
|
|
- [유저스토리](../../../userstory.md)
|
|
- [외부 시퀀스](../outer/사용자인증플로우.puml)
|
|
- [논리 아키텍처](../../logical/logical-architecture.md)
|
|
- [공통설계원칙](../../../common-principles.md)
|
|
- [내부시퀀스설계가이드](../../../../claude/sequence-inner-design.md)
|
|
|
|
---
|
|
|
|
**문서 버전**: 1.0
|
|
**최종 수정일**: 2025-10-22
|
|
**작성자**: System Architect
|
|
**변경 사항**: User Service 내부 시퀀스 4개 시나리오 초안 작성 완료
|