diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9178431
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,105 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+
+# Gradle
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+# IntelliJ IDEA
+.idea/
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+# Eclipse
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+# NetBeans
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+# VS Code
+.vscode/
+
+# Mac
+.DS_Store
+
+# Windows
+Thumbs.db
+ehthumbs.db
+Desktop.ini
+
+# Linux
+*~
+
+# Temporary files
+*.tmp
+*.temp
+
+# Spring Boot
+*.pid
+
+# Database
+*.db
+*.sqlite
+
+# Claude downloads
+claude/
+
+# Logs directory
+logs/
+
+# Debug images
+debug/
+
+# Environment files
+.env
+.env.local
+.env.dev
+.env.prod
+
+# Certificates
+*.pem
+*.key
+*.crt
+
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 23baf58..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# 디폴트 무시된 파일
-/shelf/
-/workspace.xml
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index e651a1d..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 65368a9..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/phonebill.iml b/.idea/phonebill.iml
deleted file mode 100644
index d6ebd48..0000000
--- a/.idea/phonebill.iml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.playwright-mcp/current-result-page.png b/.playwright-mcp/current-result-page.png
deleted file mode 100644
index 8173e7a..0000000
Binary files a/.playwright-mcp/current-result-page.png and /dev/null differ
diff --git a/CLAUDE.md b/CLAUDE.md
index 6e5c61e..13c956d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -492,3 +492,22 @@ QA Engineer
- "@develop-help": "개발실행프롬프트 내용을 터미널에 출력"
- "@deploy-help": "배포실행프롬프트 내용을 터미널에 출력"
```
+
+# Lessons Learned
+
+## 개발 워크플로우
+- **❗ 핵심 원칙**: 코드 수정 → 컴파일 → 사람에게 서버 시작 요청 → 테스트
+- **소스 수정**: Spring Boot는 코드 변경 후 반드시 컴파일 + 재시작 필요
+- **컴파일**: 최상위 루트에서 `./gradlew {service-name}:compileJava` 명령 사용
+- **서버 시작**: AI가 직접 서버를 시작하지 말고 반드시 사람에게 요청할것
+
+## 실행 프로파일 작성 경험
+- **Gradle 실행 프로파일**: Spring Boot가 아닌 Gradle 실행 프로파일 사용 필수
+- **환경변수 매핑**: `` 형태로 환경변수 설정
+- **컴포넌트 스캔 이슈**: common 모듈의 @Component가 인식되지 않는 경우 발생
+- **의존성 주입 오류**: JwtTokenProvider 빈을 찾을 수 없는 오류 확인됨
+
+## 백킹서비스 연결 정보
+- **LoadBalancer External IP**: kubectl 명령으로 실제 IP 확인 후 환경변수 설정
+- **DB 연결정보**: 각 서비스별 별도 DB 사용 (auth, bill_inquiry, product_change)
+- **Redis 공유**: 모든 서비스가 동일한 Redis 인스턴스 사용
diff --git a/api-gateway/.run/api-gateway.run.xml b/api-gateway/.run/api-gateway.run.xml
new file mode 100644
index 0000000..ba289bc
--- /dev/null
+++ b/api-gateway/.run/api-gateway.run.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+ false
+
+
+
diff --git a/api-gateway/README.md b/api-gateway/README.md
new file mode 100644
index 0000000..398ae1a
--- /dev/null
+++ b/api-gateway/README.md
@@ -0,0 +1,330 @@
+# PhoneBill API Gateway
+
+통신요금 관리 서비스의 API Gateway 모듈입니다.
+
+## 개요
+
+Spring Cloud Gateway를 사용하여 구현된 API Gateway로, 마이크로서비스들의 단일 진입점 역할을 담당합니다.
+
+### 주요 기능
+
+- **JWT 토큰 기반 인증/인가**: 모든 요청에 대한 통합 인증 처리
+- **서비스별 라우팅**: 각 마이크로서비스로의 지능형 라우팅
+- **Rate Limiting**: Redis 기반 요청 제한
+- **Circuit Breaker**: 외부 시스템 장애 격리
+- **CORS 설정**: 크로스 오리진 요청 처리
+- **API 문서화 통합**: 모든 서비스의 Swagger 문서 통합
+- **헬스체크**: 시스템 상태 모니터링
+- **Fallback 처리**: 서비스 장애 시 대체 응답
+
+## 기술 스택
+
+- **Java 17**
+- **Spring Boot 3.2.1**
+- **Spring Cloud Gateway**
+- **Spring Data Redis Reactive**
+- **JWT (JJWT 0.12.3)**
+- **Resilience4j** (Circuit Breaker)
+- **SpringDoc OpenAPI 3**
+
+## 아키텍처
+
+### 라우팅 구성
+
+```
+/auth/** -> auth-service (인증 서비스)
+/bills/** -> bill-service (요금조회 서비스)
+/products/** -> product-service (상품변경 서비스)
+/kos/** -> kos-mock-service (KOS 목업 서비스)
+```
+
+### 패키지 구조
+
+```
+com.unicorn.phonebill.gateway/
+├── config/ # 설정 클래스
+│ ├── GatewayConfig # Gateway 라우팅 설정
+│ ├── RedisConfig # Redis 및 Rate Limiting 설정
+│ ├── SwaggerConfig # API 문서화 설정
+│ └── WebConfig # Web 설정
+├── controller/ # 컨트롤러
+│ └── HealthController # 헬스체크 API
+├── dto/ # 데이터 전송 객체
+│ └── TokenValidationResult # JWT 검증 결과
+├── exception/ # 예외 클래스
+│ └── GatewayException # Gateway 예외
+├── filter/ # Gateway 필터
+│ └── JwtAuthenticationGatewayFilterFactory # JWT 인증 필터
+├── handler/ # 핸들러
+│ └── FallbackHandler # Circuit Breaker Fallback 핸들러
+├── service/ # 서비스
+│ └── JwtTokenService # JWT 토큰 검증 서비스
+└── util/ # 유틸리티
+ └── JwtUtil # JWT 유틸리티
+```
+
+## 빌드 및 실행
+
+### 개발 환경
+
+```bash
+# 의존성 설치 및 빌드
+./gradlew build
+
+# 개발 환경 실행
+./gradlew bootRun --args='--spring.profiles.active=dev'
+
+# 또는
+./gradlew bootRun -Pdev
+```
+
+### 운영 환경
+
+```bash
+# 운영용 JAR 빌드
+./gradlew bootJar
+
+# 운영 환경 실행
+java -jar api-gateway-1.0.0.jar --spring.profiles.active=prod
+```
+
+## 환경 설정
+
+### 개발 환경 (application-dev.yml)
+
+- JWT 토큰 유효시간: 1시간 (개발 편의성)
+- Redis: localhost:6379
+- Rate Limiting: 1000 requests/minute
+- Circuit Breaker: 관대한 설정
+- Swagger UI: 활성화
+
+### 운영 환경 (application-prod.yml)
+
+- JWT 토큰 유효시간: 30분 (보안 강화)
+- Redis: 클러스터 설정
+- Rate Limiting: 500 requests/minute
+- Circuit Breaker: 엄격한 설정
+- Swagger UI: 비활성화
+
+### 환경 변수
+
+운영 환경에서는 다음 환경 변수를 설정해야 합니다:
+
+```bash
+JWT_SECRET=your-256-bit-secret-key
+REDIS_HOST=redis-cluster.domain.com
+REDIS_PASSWORD=your-redis-password
+AUTH_SERVICE_URL=https://auth-service.internal.domain.com
+BILL_SERVICE_URL=https://bill-service.internal.domain.com
+PRODUCT_SERVICE_URL=https://product-service.internal.domain.com
+KOS_MOCK_SERVICE_URL=https://kos-mock.internal.domain.com
+```
+
+## API 문서
+
+### 개발 환경
+
+Swagger UI는 개발 환경에서만 활성화됩니다:
+
+- **Swagger UI**: http://localhost:8080/swagger-ui.html
+- **API Docs**: http://localhost:8080/v3/api-docs
+
+### 헬스체크
+
+- **기본 헬스체크**: `GET /health`
+- **상세 헬스체크**: `GET /health/detailed`
+- **Actuator 헬스체크**: `GET /actuator/health`
+
+## JWT 인증
+
+### 토큰 형식
+
+```
+Authorization: Bearer
+```
+
+### 토큰 페이로드 예시
+
+```json
+{
+ "sub": "user123",
+ "role": "USER",
+ "iat": 1704700800,
+ "exp": 1704704400,
+ "jti": "token-unique-id"
+}
+```
+
+### 인증 제외 경로
+
+- `/auth/login` (로그인)
+- `/auth/refresh` (토큰 갱신)
+- `/health` (헬스체크)
+- `/actuator/health` (Actuator 헬스체크)
+
+## Rate Limiting
+
+### 제한 정책
+
+- **일반 사용자**: 100 requests/minute
+- **VIP 사용자**: 500 requests/minute
+- **IP 기반 제한**: Fallback으로 사용
+
+### Key Resolver
+
+1. **userKeyResolver**: JWT에서 사용자 ID 추출 (기본)
+2. **ipKeyResolver**: 클라이언트 IP 기반
+3. **pathKeyResolver**: API 경로 기반
+
+## Circuit Breaker
+
+### 설정
+
+- **실패율 임계값**: 50% (auth), 60% (bill, product), 70% (kos)
+- **최소 호출 수**: 5-20회
+- **Open 상태 대기시간**: 10-60초
+- **Half-Open 상태 허용 호출**: 3-10회
+
+### Fallback
+
+Circuit Breaker가 Open 상태일 때 Fallback 응답을 제공:
+
+- **인증 서비스**: 503 Service Unavailable
+- **요금조회 서비스**: 캐시된 메뉴 데이터 제공 가능
+- **상품변경 서비스**: 고객센터 안내 메시지
+- **KOS 서비스**: 외부 시스템 점검 안내
+
+## 모니터링
+
+### Actuator 엔드포인트
+
+```bash
+# 애플리케이션 상태
+GET /actuator/health
+
+# Gateway 라우트 정보
+GET /actuator/gateway/routes
+
+# 메트릭 정보
+GET /actuator/metrics
+
+# 환경 정보 (개발환경만)
+GET /actuator/env
+```
+
+### 로깅
+
+- **개발환경**: DEBUG 레벨, 상세한 요청/응답 로그
+- **운영환경**: INFO 레벨, 성능 고려한 최적화된 로그
+
+## 보안
+
+### HTTPS
+
+운영 환경에서는 반드시 HTTPS를 사용해야 합니다.
+
+### CORS
+
+- **개발환경**: 모든 localhost 오리진 허용
+- **운영환경**: 특정 도메인만 허용
+
+### 보안 헤더
+
+- X-Content-Type-Options: nosniff
+- X-Frame-Options: DENY
+- X-XSS-Protection: 1; mode=block
+
+## 트러블슈팅
+
+### 일반적인 문제
+
+1. **Redis 연결 실패**
+ ```bash
+ # Redis 서비스 상태 확인
+ systemctl status redis
+
+ # Redis 연결 테스트
+ redis-cli ping
+ ```
+
+2. **JWT 검증 실패**
+ ```bash
+ # JWT 시크릿 키 확인
+ echo $JWT_SECRET
+
+ # 토큰 유효성 확인 (개발용)
+ curl -H "Authorization: Bearer " http://localhost:8080/health
+ ```
+
+3. **Circuit Breaker Open**
+ ```bash
+ # Circuit Breaker 상태 확인
+ curl http://localhost:8080/actuator/circuitbreakers
+ ```
+
+### 로그 확인
+
+```bash
+# 개발환경 로그
+tail -f logs/api-gateway-dev.log
+
+# 운영환경 로그
+tail -f /var/log/api-gateway/api-gateway.log
+```
+
+## 성능 튜닝
+
+### JVM 옵션 (운영환경)
+
+```bash
+java -server \
+ -Xms512m -Xmx1024m \
+ -XX:+UseG1GC \
+ -XX:G1HeapRegionSize=16m \
+ -XX:+UseStringDeduplication \
+ -jar api-gateway-1.0.0.jar
+```
+
+### Redis 최적화
+
+- Connection Pool 설정 조정
+- Pipeline 사용 고려
+- 클러스터 모드 활용
+
+## 개발 가이드
+
+### 새로운 서비스 추가
+
+1. `GatewayConfig`에 라우팅 규칙 추가
+2. `SwaggerConfig`에 API 문서 URL 추가
+3. `FallbackHandler`에 Fallback 로직 추가
+4. Circuit Breaker 설정 추가
+
+### 커스텀 필터 추가
+
+```java
+@Component
+public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory {
+ // 필터 구현
+}
+```
+
+## 릴리스 노트
+
+### v1.0.0 (2025-01-08)
+
+- 초기 릴리스
+- JWT 인증 시스템 구현
+- 4개 마이크로서비스 라우팅 지원
+- Circuit Breaker 및 Rate Limiting 구현
+- Swagger 통합 문서화
+- 헬스체크 및 모니터링 기능
+
+## 라이선스
+
+이 프로젝트는 회사 내부 프로젝트입니다.
+
+## 기여
+
+- **개발팀**: 이개발(백엔더)
+- **검토**: 김기획(기획자), 박화면(프론트), 최운영(데옵스), 정테스트(QA매니저)
\ No newline at end of file
diff --git a/api-gateway/build.gradle b/api-gateway/build.gradle
new file mode 100644
index 0000000..41b6725
--- /dev/null
+++ b/api-gateway/build.gradle
@@ -0,0 +1,87 @@
+// API Gateway 모듈
+// 루트 build.gradle의 subprojects 블록에서 공통 설정 적용됨
+// API Gateway는 WebFlux를 사용하므로 일부 다른 설정 필요
+
+// Spring Cloud 버전 정의
+ext {
+ set('springCloudVersion', '2023.0.0')
+}
+
+dependencies {
+ // Common module dependency
+ implementation project(':common')
+
+ // Spring Cloud Gateway (api-gateway specific)
+ implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
+
+ // Circuit Breaker (api-gateway specific)
+ implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j'
+
+ // Monitoring (api-gateway specific)
+ implementation 'io.micrometer:micrometer-registry-prometheus'
+
+ // Logging (api-gateway specific)
+ implementation 'net.logstash.logback:logstash-logback-encoder:7.4'
+
+ // Netty macOS DNS resolver (api-gateway specific)
+ implementation 'io.netty:netty-resolver-dns-native-macos:4.1.100.Final:osx-aarch_64'
+
+ // Test Dependencies (api-gateway specific)
+ testImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock'
+}
+
+dependencyManagement {
+ imports {
+ mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
+ }
+}
+
+// 추가 테스트 설정 (루트에서 기본 설정됨)
+tasks.named('test') {
+ systemProperty 'spring.profiles.active', 'test'
+}
+
+// JAR 파일명 설정
+jar {
+ archiveBaseName = 'api-gateway'
+ enabled = false
+}
+
+bootJar {
+ archiveBaseName = 'api-gateway'
+
+ // 빌드 정보 추가
+ manifest {
+ attributes(
+ 'Implementation-Title': 'PhoneBill API Gateway',
+ 'Implementation-Version': "${version}",
+ 'Built-By': System.getProperty('user.name'),
+ 'Built-JDK': System.getProperty('java.version'),
+ 'Build-Time': new Date().format('yyyy-MM-dd HH:mm:ss')
+ )
+ }
+}
+
+// 개발 환경 실행 설정
+if (project.hasProperty('dev')) {
+ bootRun {
+ systemProperty 'spring.profiles.active', 'dev'
+ jvmArgs = ['-Dspring.devtools.restart.enabled=true']
+ }
+}
+
+// 프로덕션 환경 실행 설정
+if (project.hasProperty('prod')) {
+ bootRun {
+ systemProperty 'spring.profiles.active', 'prod'
+ jvmArgs = [
+ '-server',
+ '-Xms512m',
+ '-Xmx1024m',
+ '-XX:+UseG1GC',
+ '-XX:G1HeapRegionSize=16m',
+ '-XX:+UseStringDeduplication',
+ '-XX:+OptimizeStringConcat'
+ ]
+ }
+}
\ No newline at end of file
diff --git a/api-gateway/src/main/java/com/unicorn/phonebill/gateway/ApiGatewayApplication.java b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/ApiGatewayApplication.java
new file mode 100644
index 0000000..6373e04
--- /dev/null
+++ b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/ApiGatewayApplication.java
@@ -0,0 +1,39 @@
+package com.unicorn.phonebill.gateway;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+/**
+ * API Gateway 애플리케이션 메인 클래스
+ *
+ * Spring Cloud Gateway를 사용하여 마이크로서비스들의 단일 진입점 역할을 담당합니다.
+ *
+ * 주요 기능:
+ * - JWT 토큰 기반 인증/인가
+ * - 서비스별 라우팅 (user-service, bill-service, product-service, kos-mock)
+ * - CORS 설정
+ * - Circuit Breaker 패턴 적용
+ * - Rate Limiting
+ * - API 문서화 통합
+ * - 모니터링 및 헬스체크
+ *
+ * @author 이개발(백엔더)
+ * @version 1.0.0
+ * @since 2025-01-08
+ */
+@SpringBootApplication(exclude = {
+ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class
+})
+@EnableConfigurationProperties(GatewayLoadBalancerProperties.class)
+public class ApiGatewayApplication {
+
+ public static void main(String[] args) {
+ // 시스템 프로퍼티 설정 (성능 최적화)
+ System.setProperty("spring.main.lazy-initialization", "true");
+ System.setProperty("reactor.bufferSize.small", "256");
+
+ SpringApplication.run(ApiGatewayApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/GatewayConfig.java b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/GatewayConfig.java
new file mode 100644
index 0000000..5924d8d
--- /dev/null
+++ b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/GatewayConfig.java
@@ -0,0 +1,175 @@
+package com.unicorn.phonebill.gateway.config;
+
+import com.unicorn.phonebill.gateway.filter.JwtAuthenticationGatewayFilterFactory;
+import org.springframework.cloud.gateway.route.RouteLocator;
+import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.reactive.CorsWebFilter;
+import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
+
+import java.util.Arrays;
+
+/**
+ * Spring Cloud Gateway 라우팅 및 CORS 설정
+ *
+ * 마이크로서비스별 라우팅 규칙과 CORS 정책을 정의합니다.
+ *
+ * 라우팅 구성:
+ * - /auth/** -> auth-service (인증 서비스)
+ * - /bills/** -> bill-service (요금조회 서비스)
+ * - /products/** -> product-service (상품변경 서비스)
+ * - /kos/** -> kos-mock (KOS 목업 서비스)
+ *
+ * @author 이개발(백엔더)
+ * @version 1.0.0
+ * @since 2025-01-08
+ */
+@Configuration
+public class GatewayConfig {
+
+ private final JwtAuthenticationGatewayFilterFactory jwtAuthFilter;
+
+ public GatewayConfig(JwtAuthenticationGatewayFilterFactory jwtAuthFilter) {
+ this.jwtAuthFilter = jwtAuthFilter;
+ }
+
+ /**
+ * 라우팅 규칙 정의
+ *
+ * 각 마이크로서비스로의 라우팅 규칙과 필터를 설정합니다.
+ * JWT 인증이 필요한 경로와 불필요한 경로를 구분하여 처리합니다.
+ *
+ * @param builder RouteLocatorBuilder
+ * @return RouteLocator
+ */
+ @Bean
+ public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
+ return builder.routes()
+ // Auth Service 라우팅 (인증 불필요)
+ .route("auth-service", r -> r
+ .path("/auth/login", "/auth/refresh")
+ .and()
+ .method("POST")
+ .uri("lb://auth-service"))
+
+ // Auth Service 라우팅 (인증 필요)
+ .route("auth-service-authenticated", r -> r
+ .path("/auth/**")
+ .filters(f -> f
+ .filter(jwtAuthFilter.apply(new JwtAuthenticationGatewayFilterFactory.Config()))
+ .circuitBreaker(cb -> cb
+ .setName("auth-service-cb")
+ .setFallbackUri("forward:/fallback/auth"))
+ .retry(retry -> retry
+ .setRetries(3)
+ .setBackoff(java.time.Duration.ofSeconds(2), java.time.Duration.ofSeconds(10), 2, true)))
+ .uri("lb://auth-service"))
+
+ // Bill-Inquiry Service 라우팅 (인증 필요)
+ .route("bill-service", r -> r
+ .path("/bills/**")
+ .filters(f -> f
+ .filter(jwtAuthFilter.apply(new JwtAuthenticationGatewayFilterFactory.Config()))
+ .circuitBreaker(cb -> cb
+ .setName("bill-service-cb")
+ .setFallbackUri("forward:/fallback/bill"))
+ .retry(retry -> retry
+ .setRetries(3)
+ .setBackoff(java.time.Duration.ofSeconds(2), java.time.Duration.ofSeconds(10), 2, true))
+ )
+ .uri("lb://bill-service"))
+
+ // Product-Change Service 라우팅 (인증 필요)
+ .route("product-service", r -> r
+ .path("/products/**")
+ .filters(f -> f
+ .filter(jwtAuthFilter.apply(new JwtAuthenticationGatewayFilterFactory.Config()))
+ .circuitBreaker(cb -> cb
+ .setName("product-service-cb")
+ .setFallbackUri("forward:/fallback/product"))
+ .retry(retry -> retry
+ .setRetries(3)
+ .setBackoff(java.time.Duration.ofSeconds(2), java.time.Duration.ofSeconds(10), 2, true))
+ )
+ .uri("lb://product-service"))
+
+ // KOS Mock Service 라우팅 (내부 서비스용)
+ .route("kos-mock-service", r -> r
+ .path("/kos/**")
+ .filters(f -> f
+ .circuitBreaker(cb -> cb
+ .setName("kos-mock-cb")
+ .setFallbackUri("forward:/fallback/kos"))
+ .retry(retry -> retry
+ .setRetries(5)
+ .setBackoff(java.time.Duration.ofSeconds(1), java.time.Duration.ofSeconds(5), 2, true)))
+ .uri("lb://kos-mock-service"))
+
+ // Health Check 라우팅 (인증 불필요)
+ .route("health-check", r -> r
+ .path("/health", "/actuator/health")
+ .uri("http://localhost:8080"))
+
+ // Swagger UI 라우팅 (개발환경에서만 사용)
+ .route("swagger-ui", r -> r
+ .path("/swagger-ui/**", "/v3/api-docs/**")
+ .uri("http://localhost:8080"))
+
+ .build();
+ }
+
+ /**
+ * CORS 설정
+ *
+ * 프론트엔드에서 API Gateway로의 크로스 오리진 요청을 허용합니다.
+ * 개발/운영 환경에 따라 허용 오리진을 다르게 설정합니다.
+ *
+ * @return CorsWebFilter
+ */
+ @Bean
+ public CorsWebFilter corsWebFilter() {
+ CorsConfiguration corsConfig = new CorsConfiguration();
+
+ // 허용할 Origin 설정
+ corsConfig.setAllowedOriginPatterns(Arrays.asList(
+ "http://localhost:3000", // React 개발 서버
+ "http://localhost:3001", // Next.js 개발 서버
+ "https://*.unicorn.com", // 운영 도메인
+ "https://*.phonebill.com" // 운영 도메인
+ ));
+
+ // 허용할 HTTP 메서드
+ corsConfig.setAllowedMethods(Arrays.asList(
+ "GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"
+ ));
+
+ // 허용할 헤더
+ corsConfig.setAllowedHeaders(Arrays.asList(
+ "Authorization",
+ "Content-Type",
+ "X-Requested-With",
+ "X-Request-ID",
+ "X-User-Agent"
+ ));
+
+ // 노출할 헤더 (클라이언트가 접근 가능한 헤더)
+ corsConfig.setExposedHeaders(Arrays.asList(
+ "X-Request-ID",
+ "X-Response-Time",
+ "X-Rate-Limit-Remaining"
+ ));
+
+ // 자격 증명 허용 (쿠키, Authorization 헤더 등)
+ corsConfig.setAllowCredentials(true);
+
+ // Preflight 요청 캐시 시간 (초)
+ corsConfig.setMaxAge(3600L);
+
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", corsConfig);
+
+ return new CorsWebFilter(source);
+ }
+}
\ No newline at end of file
diff --git a/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/SwaggerConfig.java b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/SwaggerConfig.java
new file mode 100644
index 0000000..94b353a
--- /dev/null
+++ b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/SwaggerConfig.java
@@ -0,0 +1,185 @@
+package com.unicorn.phonebill.gateway.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springdoc.core.models.GroupedOpenApi;
+import org.springdoc.core.properties.SwaggerUiConfigParameters;
+import reactor.core.publisher.Mono;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Swagger 통합 문서화 설정
+ *
+ * API Gateway를 통해 모든 마이크로서비스의 OpenAPI 문서를 통합하여 제공합니다.
+ * 개발 환경에서만 활성화되며, 각 서비스별 API 문서를 중앙집중식으로 관리합니다.
+ *
+ * 주요 기능:
+ * - 마이크로서비스별 OpenAPI 문서 통합
+ * - Swagger UI 커스터마이징
+ * - JWT 인증 정보 포함
+ * - 환경별 설정 (개발환경에서만 활성화)
+ *
+ * @author 이개발(백엔더)
+ * @version 1.0.0
+ * @since 2025-01-08
+ */
+@Configuration
+@Profile("!prod") // 운영환경에서는 비활성화
+public class SwaggerConfig {
+
+ @Value("${services.auth-service.url:http://localhost:8081}")
+ private String authServiceUrl;
+
+ @Value("${services.bill-service.url:http://localhost:8082}")
+ private String billServiceUrl;
+
+ @Value("${services.product-service.url:http://localhost:8083}")
+ private String productServiceUrl;
+
+ @Value("${services.kos-mock-service.url:http://localhost:8084}")
+ private String kosMockServiceUrl;
+
+ /**
+ * Swagger UI 설정 파라미터
+ *
+ * @return SwaggerUiConfigParameters
+ */
+ @Bean
+ public SwaggerUiConfigParameters swaggerUiConfigParameters() {
+ // Spring Boot 3.x에서는 SwaggerUiConfigParameters 생성자가 변경됨
+ SwaggerUiConfigParameters parameters = new SwaggerUiConfigParameters(
+ new org.springdoc.core.properties.SwaggerUiConfigProperties()
+ );
+
+ // 각 마이크로서비스의 OpenAPI 문서 URL 설정
+ List urls = new ArrayList<>();
+ urls.add("Gateway::/v3/api-docs");
+ urls.add("Auth Service::" + authServiceUrl + "/v3/api-docs");
+ urls.add("Bill Service::" + billServiceUrl + "/v3/api-docs");
+ urls.add("Product Service::" + productServiceUrl + "/v3/api-docs");
+ urls.add("KOS Mock::" + kosMockServiceUrl + "/v3/api-docs");
+
+ // Spring Boot 3.x 호환성을 위한 설정
+ System.setProperty("springdoc.swagger-ui.urls", String.join(",", urls));
+
+ return parameters;
+ }
+
+ /**
+ * API Gateway OpenAPI 그룹 정의
+ *
+ * @return GroupedOpenApi
+ */
+ @Bean
+ public GroupedOpenApi gatewayApi() {
+ return GroupedOpenApi.builder()
+ .group("gateway")
+ .displayName("API Gateway")
+ .pathsToMatch("/health/**", "/actuator/**")
+ .addOpenApiCustomizer(openApi -> {
+ openApi.info(new io.swagger.v3.oas.models.info.Info()
+ .title("PhoneBill API Gateway")
+ .version("1.0.0")
+ .description("통신요금 관리 서비스 API Gateway\n\n" +
+ "이 문서는 API Gateway의 헬스체크 및 관리 기능을 설명합니다.")
+ );
+
+ // JWT 보안 스키마 추가
+ openApi.addSecurityItem(
+ new io.swagger.v3.oas.models.security.SecurityRequirement()
+ .addList("bearerAuth")
+ );
+
+ openApi.getComponents()
+ .addSecuritySchemes("bearerAuth",
+ new io.swagger.v3.oas.models.security.SecurityScheme()
+ .type(io.swagger.v3.oas.models.security.SecurityScheme.Type.HTTP)
+ .scheme("bearer")
+ .bearerFormat("JWT")
+ .description("JWT 토큰을 Authorization 헤더에 포함시켜 주세요.\n" +
+ "형식: Authorization: Bearer {token}")
+ );
+ })
+ .build();
+ }
+
+ /**
+ * Swagger UI 리다이렉트 라우터
+ *
+ * @return RouterFunction
+ */
+ @Bean
+ public RouterFunction swaggerRouterFunction() {
+ return RouterFunctions.route()
+ // 루트 경로에서 Swagger UI로 리다이렉트
+ .GET("/", request ->
+ ServerResponse.temporaryRedirect(URI.create("/swagger-ui.html")).build())
+
+ // docs 경로에서 Swagger UI로 리다이렉트
+ .GET("/docs", request ->
+ ServerResponse.temporaryRedirect(URI.create("/swagger-ui.html")).build())
+
+ // api-docs 경로에서 Swagger UI로 리다이렉트
+ .GET("/api-docs", request ->
+ ServerResponse.temporaryRedirect(URI.create("/swagger-ui.html")).build())
+
+ // 서비스별 API 문서 프록시
+ .GET("/v3/api-docs/auth", request ->
+ proxyApiDocs(authServiceUrl + "/v3/api-docs"))
+
+ .GET("/v3/api-docs/bills", request ->
+ proxyApiDocs(billServiceUrl + "/v3/api-docs"))
+
+ .GET("/v3/api-docs/products", request ->
+ proxyApiDocs(productServiceUrl + "/v3/api-docs"))
+
+ .GET("/v3/api-docs/kos", request ->
+ proxyApiDocs(kosMockServiceUrl + "/v3/api-docs"))
+
+ .build();
+ }
+
+ /**
+ * API 문서 프록시
+ *
+ * 각 마이크로서비스의 OpenAPI 문서를 프록시하여 제공합니다.
+ *
+ * @param apiDocsUrl API 문서 URL
+ * @return ServerResponse
+ */
+ private Mono proxyApiDocs(String apiDocsUrl) {
+ // 실제 구현에서는 WebClient를 사용하여 마이크로서비스의 API 문서를 가져와야 합니다.
+ // 현재는 임시로 빈 문서를 반환합니다.
+ return ServerResponse.ok()
+ .contentType(org.springframework.http.MediaType.APPLICATION_JSON)
+ .bodyValue("{\n" +
+ " \"openapi\": \"3.0.1\",\n" +
+ " \"info\": {\n" +
+ " \"title\": \"Service API\",\n" +
+ " \"version\": \"1.0.0\",\n" +
+ " \"description\": \"마이크로서비스 API 문서\\n\\n" +
+ "실제 서비스가 시작되면 상세한 API 문서가 표시됩니다.\"\n" +
+ " },\n" +
+ " \"paths\": {\n" +
+ " \"/status\": {\n" +
+ " \"get\": {\n" +
+ " \"summary\": \"서비스 상태 확인\",\n" +
+ " \"responses\": {\n" +
+ " \"200\": {\n" +
+ " \"description\": \"서비스 정상\"\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ "}");
+ }
+}
\ No newline at end of file
diff --git a/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/WebConfig.java b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/WebConfig.java
new file mode 100644
index 0000000..e823754
--- /dev/null
+++ b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/WebConfig.java
@@ -0,0 +1,66 @@
+package com.unicorn.phonebill.gateway.config;
+
+import com.unicorn.phonebill.gateway.handler.FallbackHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerResponse;
+
+/**
+ * Web 설정 및 라우터 함수 정의
+ *
+ * Spring WebFlux의 함수형 라우팅을 사용하여 Fallback 엔드포인트를 정의합니다.
+ * Circuit Breaker에서 호출할 Fallback 경로를 설정합니다.
+ *
+ * @author 이개발(백엔더)
+ * @version 1.0.0
+ * @since 2025-01-08
+ */
+@Configuration
+public class WebConfig {
+
+ private final FallbackHandler fallbackHandler;
+
+ public WebConfig(FallbackHandler fallbackHandler) {
+ this.fallbackHandler = fallbackHandler;
+ }
+
+ /**
+ * Fallback 라우터 함수
+ *
+ * Circuit Breaker에서 사용할 Fallback 엔드포인트를 정의합니다.
+ *
+ * @return RouterFunction
+ */
+ @Bean
+ public RouterFunction fallbackRouterFunction() {
+ return RouterFunctions.route()
+ // 인증 서비스 Fallback
+ .GET("/fallback/auth", fallbackHandler::authServiceFallback)
+ .POST("/fallback/auth", fallbackHandler::authServiceFallback)
+
+ // 요금조회 서비스 Fallback
+ .GET("/fallback/bill", fallbackHandler::billServiceFallback)
+ .POST("/fallback/bill", fallbackHandler::billServiceFallback)
+
+ // 상품변경 서비스 Fallback
+ .GET("/fallback/product", fallbackHandler::productServiceFallback)
+ .POST("/fallback/product", fallbackHandler::productServiceFallback)
+ .PUT("/fallback/product", fallbackHandler::productServiceFallback)
+
+ // KOS Mock 서비스 Fallback
+ .GET("/fallback/kos", fallbackHandler::kosServiceFallback)
+ .POST("/fallback/kos", fallbackHandler::kosServiceFallback)
+
+ // Rate Limit Fallback
+ .GET("/fallback/ratelimit", fallbackHandler::rateLimitFallback)
+ .POST("/fallback/ratelimit", fallbackHandler::rateLimitFallback)
+
+ // 일반 Fallback (기타 모든 경로)
+ .GET("/fallback/**", fallbackHandler::genericFallback)
+ .POST("/fallback/**", fallbackHandler::genericFallback)
+
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/WebFluxConfig.java b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/WebFluxConfig.java
new file mode 100644
index 0000000..99a56e4
--- /dev/null
+++ b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/WebFluxConfig.java
@@ -0,0 +1,49 @@
+package com.unicorn.phonebill.gateway.config;
+
+import org.springframework.boot.web.codec.CodecCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.codec.ServerCodecConfigurer;
+
+/**
+ * WebFlux 설정
+ *
+ * Spring Cloud Gateway에서 필요한 WebFlux 관련 빈들을 정의합니다.
+ *
+ * @author 이개발(백엔더)
+ * @version 1.0.0
+ * @since 2025-01-08
+ */
+@Configuration
+public class WebFluxConfig {
+
+ /**
+ * ServerCodecConfigurer 빈 정의
+ *
+ * Spring Cloud Gateway가 요구하는 ServerCodecConfigurer를 직접 정의합니다.
+ *
+ * @return ServerCodecConfigurer
+ */
+ @Bean
+ public ServerCodecConfigurer serverCodecConfigurer() {
+ return ServerCodecConfigurer.create();
+ }
+
+ /**
+ * CodecCustomizer 빈 정의 (선택적)
+ *
+ * 필요한 경우 코덱을 커스터마이징할 수 있습니다.
+ *
+ * @return CodecCustomizer
+ */
+ @Bean
+ public CodecCustomizer codecCustomizer() {
+ return configurer -> {
+ // 최대 메모리 크기 설정 (기본값: 256KB)
+ configurer.defaultCodecs().maxInMemorySize(1024 * 1024); // 1MB
+
+ // 기타 필요한 코덱 설정
+ configurer.defaultCodecs().enableLoggingRequestDetails(true);
+ };
+ }
+}
\ No newline at end of file
diff --git a/api-gateway/src/main/java/com/unicorn/phonebill/gateway/controller/HealthController.java b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/controller/HealthController.java
new file mode 100644
index 0000000..cdfcde4
--- /dev/null
+++ b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/controller/HealthController.java
@@ -0,0 +1,251 @@
+package com.unicorn.phonebill.gateway.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.ReactiveRedisTemplate;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Map;
+
+/**
+ * API Gateway 헬스체크 컨트롤러
+ *
+ * API Gateway와 연관된 시스템들의 상태를 점검합니다.
+ *
+ * 주요 기능:
+ * - Gateway 자체 상태 확인
+ * - Redis 연결 상태 확인
+ * - 각 마이크로서비스 연결 상태 확인
+ * - 전체 시스템 상태 요약
+ *
+ * @author 이개발(백엔더)
+ * @version 1.0.0
+ * @since 2025-01-08
+ */
+@RestController
+public class HealthController {
+
+ private final ReactiveRedisTemplate redisTemplate;
+
+ @Autowired
+ public HealthController(ReactiveRedisTemplate redisTemplate) {
+ this.redisTemplate = redisTemplate;
+ }
+
+ /**
+ * 기본 헬스체크 엔드포인트
+ *
+ * @return 상태 응답
+ */
+ @GetMapping("/health")
+ public Mono>> health() {
+ return checkSystemHealth()
+ .map(healthStatus -> {
+ HttpStatus status = healthStatus.get("status").equals("UP")
+ ? HttpStatus.OK
+ : HttpStatus.SERVICE_UNAVAILABLE;
+
+ return ResponseEntity.status(status).body(healthStatus);
+ })
+ .onErrorReturn(
+ ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(Map.of(
+ "status", "DOWN",
+ "error", "Health check failed",
+ "timestamp", Instant.now().toString()
+ ))
+ );
+ }
+
+ /**
+ * 상세 헬스체크 엔드포인트
+ *
+ * @return 상세 상태 정보
+ */
+ @GetMapping("/health/detailed")
+ public Mono>> detailedHealth() {
+ return Mono.zip(
+ checkGatewayHealth(),
+ checkRedisHealth(),
+ checkDownstreamServices()
+ ).map(tuple -> {
+ Map gatewayHealth = tuple.getT1();
+ Map redisHealth = tuple.getT2();
+ Map servicesHealth = tuple.getT3();
+
+ boolean allHealthy =
+ "UP".equals(gatewayHealth.get("status")) &&
+ "UP".equals(redisHealth.get("status")) &&
+ "UP".equals(servicesHealth.get("status"));
+
+ Map response = Map.of(
+ "status", allHealthy ? "UP" : "DOWN",
+ "timestamp", Instant.now().toString(),
+ "components", Map.of(
+ "gateway", gatewayHealth,
+ "redis", redisHealth,
+ "services", servicesHealth
+ )
+ );
+
+ HttpStatus status = allHealthy ? HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE;
+ return ResponseEntity.status(status).body(response);
+ }).onErrorReturn(
+ ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(Map.of(
+ "status", "DOWN",
+ "error", "Detailed health check failed",
+ "timestamp", Instant.now().toString()
+ ))
+ );
+ }
+
+ /**
+ * 간단한 상태 확인 엔드포인트
+ *
+ * @return 상태 응답
+ */
+ @GetMapping("/status")
+ public Mono>> status() {
+ return Mono.just(ResponseEntity.ok(Map.of(
+ "status", "UP",
+ "service", "API Gateway",
+ "timestamp", Instant.now().toString(),
+ "version", "1.0.0"
+ )));
+ }
+
+ /**
+ * 전체 시스템 상태 점검
+ *
+ * @return 시스템 상태
+ */
+ private Mono