사용자 인증/인가 및 관리를 담당하는 Spring Boot 기반 백엔드 서비스입니다. Hexagonal Architecture(육각형 아키텍처)를 적용하여 유지보수성과 확장성을 확보한 멀티 모듈 프로젝트입니다.
- Kotlin 2.2.10 (JVM 21)
- Spring Boot 3.5.6
- Spring Security 6.x
- Spring Data JPA
- PostgreSQL 17 (Bitnami image)
- Master-Slave Replication (Read/Write 분리)
- Master:
localhost:5432 - Standby:
localhost:5433
- Flyway (Database Migration)
- Redis 7.0.11 (Refresh Token 저장소)
- JWT (JSON Web Token)
- BCrypt (Password Hashing)
- JUnit 5
- Mockito-Kotlin
- Spring Boot Test
- MockMvc (Integration Test)
- Swagger/OpenAPI 3.0
- Gradle 8.x (Kotlin DSL)
- Docker Compose (로컬 개발 환경)
이 프로젝트는 **Hexagonal Architecture (Ports and Adapters)**를 기반으로 멀티 모듈 구조를 채택했습니다.
user-service/
├── bootstrap/ # 애플리케이션 진입점 및 실행 모듈
│ ├── src/main/kotlin/ # 메인 애플리케이션 클래스
│ ├── src/main/resources/ # 전역 설정 파일
│ │ └── db/migration/ # Flyway 마이그레이션 스크립트
│ ├── src/test/kotlin/ # 통합 테스트
│ └── compose.yaml # Docker Compose 설정
│
├── user-api/ # API 계층 (Adapters - Driving)
│ ├── user-api-auth/ # 인증 API (회원가입, 로그인)
│ ├── user-api-member/ # 회원 API (사용자 정보 관리)
│ └── user-api-admin/ # 관리자 API (전체 사용자 조회)
│
├── user-application/ # 애플리케이션 계층 (Use Cases)
│ ├── port/ # Application Port 인터페이스
│ ├── adapter/ # Use Case 구현
│ ├── dto/ # 요청/응답 DTO
│ └── event/ # 도메인 이벤트 리스너
│
├── user-domain/ # 도메인 계층 (Core Business Logic)
│ ├── entity/ # 엔티티 (User, Role, Permission)
│ ├── vo/ # Value Object (Email, Password)
│ ├── repository/ # Repository Port 인터페이스
│ └── exception/ # 도메인 예외
│
├── user-infrastructure/ # 인프라스트럭처 계층 (Adapters - Driven)
│ ├── user-infrastructure-auth/ # JWT 인증 구현체
│ ├── user-infrastructure-persistence/ # JPA Repository 구현체
│ ├── user-infrastructure-redis/ # Redis 구현체
│ └── user-infrastructure-mq/ # 메시지 큐 (placeholder)
│
└── common/ # 공통 모듈
├── config/ # 공통 설정
├── dto/ # 공통 응답 형식
├── exception/ # 전역 예외 처리
└── auth/ # 인증/인가 유틸리티
Hexagonal Architecture의 핵심 원칙을 준수합니다:
- Domain Layer → 외부 의존성 없음 (순수 비즈니스 로직)
- Application Layer → Domain Layer 의존
- Infrastructure Layer → Domain/Application의 Port 인터페이스 구현
- API Layer → Application Layer의 Service Port 호출
- Bootstrap → 모든 모듈 조합 및 실행
┌─────────────────────────────────────────────────────┐
│ bootstrap │
│ (Application Runner) │
└─────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ user-api-* │───────▶│ user-application │
│ (Controllers) │ │ (Use Cases/Port) │
└──────────────────┘ └──────────────────────┘
│
▼
┌──────────────────────┐
│ user-domain │
│ (Entities/VOs/Port) │
└──────────────────────┘
▲さんかく
│ (implements)
┌──────────────────────┐
│ user-infrastructure │
│ (JPA/JWT/Redis) │
└──────────────────────┘
- JDK 21 설치
- Docker 설치 (PostgreSQL, Redis 실행용)
- Git 설치
git clone <repository-url> cd user-service
Spring Boot의 Docker Compose 지원 기능을 사용하므로, 별도로 Docker Compose를 실행할 필요가 없습니다. 애플리케이션 실행 시 자동으로 PostgreSQL(Master/Slave)와 Redis 컨테이너가 시작됩니다.
만약 수동으로 실행하려면:
cd bootstrap
docker-compose -f compose.yaml up -d컨테이너 확인:
docker ps
다음 컨테이너들이 실행되어야 합니다:
postgres-main(포트 5432) - Master DBpostgres-standby(포트 5433) - Slave DB (Read Replica)redis(포트 6379) - Refresh Token 저장소
./gradlew clean build
./gradlew :bootstrap:bootRun
또는 빌드된 JAR 파일 실행:
java -jar bootstrap/build/libs/bootstrap-0.0.1.jar
- 애플리케이션:
http://localhost:8080 - Swagger UI:
http://localhost:8080/swagger-ui/index.html - API 문서:
http://localhost:8080/v3/api-docs
전체 테스트:
./gradlew test특정 모듈 테스트:
./gradlew :user-domain:test ./gradlew :user-application:test ./gradlew :bootstrap:test
특정 테스트 클래스:
./gradlew :bootstrap:test --tests "com.example.integration.AuthControllerIntegrationTest"Swagger UI를 통해 대화형 API 문서를 제공합니다: http://localhost:8080/swagger-ui/index.html
| Method | Endpoint | Description | Authentication |
|---|---|---|---|
| POST | /signup | 회원가입 | 불필요 |
| POST | /signin | 로그인 (JWT 발급) | 불필요 |
| Method | Endpoint | Description | Authentication |
|---|---|---|---|
| GET | /users/{userId} | 사용자 정보 조회 | 필요 (본인/관리자) |
| PUT | /users/{userId} | 사용자 정보 수정 | 필요 (본인/관리자) |
| DELETE | /users/{userId} | 사용자 탈퇴 | 필요 (본인/관리자) |
| Method | Endpoint | Description | Authentication |
|---|---|---|---|
| GET | /admin/users | 전체 사용자 목록 조회 (페이징) | 필요 (ADMIN만) |
POST /signup - 회원가입
Request Body:
{
"email": "user@example.com",
"password": "Password123!",
"role": "MEMBER"
}Response (201 Created):
{
"status": true,
"message": "회원가입이 완료되었습니다",
"data": {
"id": 1,
"email": "user@example.com",
"role": "MEMBER",
"createdAt": "2025年10月13日T12:00:00"
}
}POST /signin - 로그인
Request Body:
{
"email": "user@example.com",
"password": "Password123!",
"role": "MEMBER"
}Response (200 OK):
{
"status": true,
"message": "로그인이 완료되었습니다",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"accessTokenExpiresIn": 3600000,
"refreshTokenExpiresIn": 604800000
}
}GET /users/{userId} - 사용자 조회
Headers:
Authorization: Bearer {accessToken}
Response (200 OK):
{
"status": true,
"message": "사용자 정보 조회 완료",
"data": {
"id": 1,
"email": "user@example.com",
"role": "MEMBER",
"createdAt": "2025年10月13日T12:00:00",
"updatedAt": "2025年10月13日T12:00:00"
}
}GET /admin/users - 전체 사용자 조회 (관리자)
Headers:
Authorization: Bearer {accessToken}
Query Parameters:
page: 페이지 번호 (0부터 시작, 기본값: 0)size: 페이지 크기 (1-100, 기본값: 20)sort: 정렬 옵션 (예:createdAt,desc또는email,asc)
Example Request:
GET /admin/users?page=0&size=20&sort=createdAt,desc
Response (200 OK):
{
"status": true,
"message": "전체 사용자 조회 완료",
"data": {
"content": [
{
"id": 1,
"email": "user@example.com",
"role": "MEMBER",
"createdAt": "2025年10月13日T12:00:00"
}
],
"totalElements": 100,
"totalPages": 5,
"currentPage": 0,
"pageSize": 20,
"hasNext": true,
"hasPrevious": false
}
}선택 이유:
- 비즈니스 로직의 독립성: 도메인 계층이 프레임워크(Spring)나 외부 라이브러리(JPA, Redis)에 의존하지 않아 비즈니스 로직 변경이 용이
- 테스트 용이성: 인프라 구현체를 Mock으로 대체하여 단위 테스트 작성이 간편
- 기술 스택 변경 유연성: 예를 들어, JPA를 MyBatis로 변경하거나 Redis를 Memcached로 교체해도 도메인 계층 수정 불필요
- 팀 협업 효율: 도메인 전문가는
user-domain에, 인프라 전문가는user-infrastructure에 집중 가능
구현 방식:
- Port 인터페이스: 도메인/애플리케이션 계층에서 정의 (
UserRepository,TokenProvider,RefreshTokenStore) - Adapter 구현체: 인프라 계층에서 실제 구현 (
UserRepositoryImpl,JwtTokenProvider,RefreshTokenRedisRepository)
선택 이유:
- 명확한 계층 분리: 각 모듈의 책임이 명확하여 코드 이해도 향상
- 의존성 제어: Gradle 설정으로 잘못된 계층 간 참조 방지 (예: domain → infrastructure 참조 불가)
- 독립적인 배포: 향후 마이크로서비스 전환 시 모듈별로 독립 배포 가능
- 빌드 최적화: 변경된 모듈만 재빌드하여 빌드 시간 단축
선택 이유:
- 읽기 성능 향상: 읽기 쿼리를 Slave DB로 분산하여 Master DB 부하 감소
- 고가용성: Master 장애 시 Slave를 Master로 승격 가능 (Failover)
- 실무 반영: 실제 프로덕션 환경에서 자주 사용되는 아키텍처 학습
구현 방식:
ReplicationRoutingDataSource: Spring의AbstractRoutingDataSource를 상속하여 트랜잭션 읽기/쓰기 속성에 따라 DataSource 라우팅@Transactional(readOnly = true): Slave DB로 라우팅@Transactional: Master DB로 라우팅
선택 이유:
- Stateless: 서버에 세션 저장 불필요, 수평 확장(Scale-out) 용이
- MSA 친화적: API Gateway와 결합하여 인증 정보 전달 간편
- 모바일 앱 지원: 쿠키 기반 세션보다 모바일 환경에 적합
구현 방식:
- Access Token: 짧은 유효기간 (1시간), 모든 API 요청에 포함
- Refresh Token: 긴 유효기간 (7일), Redis에 저장하여 토큰 갱신 시 사용
- JwtAuthenticationFilter: 모든 요청에서 JWT 검증 및 SecurityContext 설정
보안 고려사항:
- Access Token은 stateless하지만, Refresh Token은 Redis에 저장하여 강제 로그아웃 구현 가능
- 사용자 탈퇴 시 Redis에서 Refresh Token 삭제하여 즉시 인증 무효화
선택 이유:
- 도메인 규칙 캡슐화: 이메일 유효성 검증, 비밀번호 정책 검증을 Value Object 내부에 위치
- 불변성(Immutability): 한 번 생성된 Email, Password는 변경 불가능하여 부수효과 방지
- 재사용성: 여러 엔티티에서 동일한 검증 로직 공유
선택 이유:
- 역할별 보안 강도 차별화: ADMIN은 더 강력한 비밀번호 요구 (12자 이상, 4종류 문자 모두 포함)
- 확장성: 새로운 역할(예: SUPER_ADMIN) 추가 시 새로운 Policy 클래스만 추가
- 단일 책임 원칙: 각 Policy 클래스는 하나의 역할에 대한 검증만 담당
구현 방식:
MemberPasswordPolicy: 8자 이상, 영문/숫자/특수문자 중 2가지 이상AdminPasswordPolicy: 12자 이상, 영문(대소)/숫자/특수문자 모두 포함
선택 이유:
- 데이터 보존: 법적 요구사항(예: 개인정보보호법)을 위해 탈퇴 로그 유지
- 복구 가능성: 사용자가 실수로 탈퇴한 경우 데이터 복구 가능
- 참조 무결성 유지: 외래키 관계가 있는 다른 테이블의 데이터 보존
구현 방식:
User.delete(): 이메일과 비밀번호를 익명화하고deletedAt타임스탬프 설정- 익명화 규칙: 이메일은
deleted_{timestamp}@deleted.com, 비밀번호는 랜덤 해시
GDPR 준수:
- 탈퇴 시 즉시 개인정보(이메일) 익명화하여 복구 불가능한 형태로 변환
deletedAt인덱스를 활용하여 N일 후 물리 삭제하는 배치 작업 구현 가능
선택 이유:
- 유연한 권한 관리: 한 명의 사용자가 MEMBER와 ADMIN 역할을 동시에 가질 수 있음
- 실무 요구사항 반영: 쇼핑몰 직원이 일반 회원으로도 쇼핑하고, 관리자로도 시스템 관리하는 경우
구현 방식:
- 데이터베이스 스키마:
(email, role_id)복합 유니크 제약 - JPA Repository:
findByEmailAndRoleId()메서드로 조회
주의사항:
- 로그인 시 반드시
email과role을 모두 입력받아야 함 - 같은 이메일이라도 Role별로 다른 비밀번호 설정 가능
문제: 초기 구현 시 읽기 전용 트랜잭션에서도 Master DB로 연결되는 문제 발생.
원인:
@Transactional(readOnly = true)가 선언되어도, Spring의 DataSourceTransactionManager가 트랜잭션 시작 시점에 DataSource를 결정하는데, 이때 TransactionSynchronizationManager.isCurrentTransactionReadOnly()가 false를 반환.
해결:
ReplicationRoutingDataSource에서 determineCurrentLookupKey() 메서드를 오버라이드하여 트랜잭션 속성을 직접 확인:
override fun determineCurrentLookupKey(): Any { return if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { DatabaseType.SLAVE } else { DatabaseType.MASTER } }
검증:
통합 테스트에서 로그를 확인하여 읽기 쿼리가 postgres-standby:5433으로, 쓰기 쿼리가 postgres-main:5432로 가는지 확인.
문제:
Password Value Object를 생성할 때마다 BCrypt 해싱을 수행하면, 테스트에서 동일한 비밀번호를 여러 번 생성 시 해시값이 달라져 비교가 어려움.
해결 방안:
- Plain Text로 생성:
Password.of(plainText, policy)- 새로운 비밀번호 입력 시 사용 - Hash로 생성:
Password.fromHash(hash, changedAt)- DB에서 조회 시 사용
companion object { fun of(plainText: String, policy: PasswordPolicy): Password { policy.validate(plainText) val hash = BCrypt.hashpw(plainText, BCrypt.gensalt()) return Password(hash, LocalDateTime.now()) } fun fromHash(hash: String, changedAt: LocalDateTime): Password { return Password(hash, changedAt) } }
문제:
Kotlin의 data class는 equals(), hashCode(), toString()을 자동 생성하지만, JPA Entity에 사용 시 문제 발생:
- 기본 생성자 필요
- Lazy Loading 프록시 객체와
equals()비교 시 무한 재귀 호출
해결:
- Entity는 일반
class로 선언 equals()와hashCode()를 ID 기반으로 직접 구현allOpen플러그인으로@Entity클래스를 자동으로open처리 (Hibernate 프록시 지원)
@Entity class User(...) { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is User) return false return id == other.id } override fun hashCode(): Int = id?.hashCode() ?: 0 }
고민:
- DB 저장: 영속성 보장, 복잡한 쿼리 가능
- Redis 저장: 빠른 조회, TTL 자동 만료
선택: Redis
이유:
- Refresh Token은 임시 데이터이므로 영속성 필요 없음
- TTL 기능으로 만료된 토큰 자동 삭제 (Garbage Collection 불필요)
- 로그인 요청 시 빠른 조회 성능
트레이드오프:
- Redis 장애 시 모든 사용자 재로그인 필요 (해결: 추후 Redis Sentinel/Cluster로 고가용성 확보)
문제: 여러 컨트롤러에서 "본인 또는 관리자만 접근 가능" 로직이 반복됨.
해결: AuthorizationChecker 유틸리티 클래스 생성
@Component class AuthorizationChecker { fun checkResourceAccess(targetUserId: Long) { val currentUser = getCurrentUser() if (!currentUser.hasRole("ADMIN") && currentUser.id != targetUserId) { throw AccessDeniedException("접근 권한이 없습니다") } } }
문제:
@SpringBootTest를 사용한 통합 테스트에서 테스트 간 데이터 충돌 발생.
해결:
@Transactional적용: 각 테스트 메서드 실행 후 자동 롤백- 랜덤 이메일 생성:
randomEmail()메서드로 충돌 방지
@SpringBootTest @Transactional class AuthControllerIntegrationTest { fun randomEmail(): String { return "test-${System.currentTimeMillis()}@example.com" } }
문제:
User 조회 시 연관된 Role 엔티티를 Lazy Loading으로 조회하면 N+1 쿼리 발생.
해결:
@ManyToOne(fetch = FetchType.EAGER)적용- 또는 JPQL로
JOIN FETCH사용
향후 개선 방안:
- 대량 조회 시 JPQL
JOIN FETCH또는 EntityGraph 사용 - 캐싱 전략 적용 (Spring Cache + Redis)
사용자 탈퇴 시 발생하는 부가 작업(이메일 발송, 파일 삭제 등)을 비동기로 처리하여 API 응답 시간을 단축합니다.
- Spring @Async: 비동기 메서드 실행
- @TransactionalEventListener: 트랜잭션 커밋 후 이벤트 발행
- ThreadPoolTaskExecutor: 커스텀 스레드 풀
1. 비동기 설정
@Configuration @EnableAsync class AsyncConfig { @Bean fun asyncExecutor(): Executor { return ThreadPoolTaskExecutor().apply { corePoolSize = 2 maxPoolSize = 5 queueCapacity = 100 setThreadNamePrefix("async-") initialize() } } }
2. 이벤트 리스너
@Component class UserEventListener { @Async @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) fun handleUserDeletedEvent(event: UserDeletedEvent) { val data = event.source as UserDeletedEventData // 1. 탈퇴 확인 이메일 발송 sendWithdrawalConfirmationEmail(data) // 2. 사용자 업로드 파일 삭제 deleteUserFiles(data) // 3. 관련 데이터 정리 cleanupRelatedData(data) } }
3. 이벤트 발행
@Transactional override fun deleteUser(userId: Long) { val user = userRepository.findById(userId) val eventData = UserDeletedEventData(user.id, user.email.getValue()) // 1. 사용자 정보 익명화 user.delete() // 2. Refresh Token 삭제 refreshTokenStore.deleteByUserId(userId) // 3. DB에서 삭제 userRepository.delete(user) // 4. 비동기 이벤트 발행 (트랜잭션 커밋 후 실행) eventPublisher.publishEvent(UserDeletedEvent(eventData)) }
장점:
- 응답 시간 단축: 사용자는 탈퇴 요청 후 즉시 응답 받음 (부가 작업 대기 불필요)
- 트랜잭션 분리: 이메일 발송 실패 시에도 사용자 탈퇴는 완료됨 (부분 실패 허용)
- 확장성: 향후 외부 API 호출(예: 결제 취소)이 추가되어도 성능 저하 없음
트레이드오프:
- 비동기 작업 실패 시 재시도 로직 필요 (현재는 로그만 기록)
Spring @Async vs 메시지 큐 (RabbitMQ/Kafka):
| 기준 | Spring @Async | 메시지 큐 |
|---|---|---|
| 구현 복잡도 | 낮음 (애너테이션 한 줄) | 높음 (별도 인프라) |
| 메시지 영속성 | 없음 (재시작 시 손실) | 있음 (Disk 저장) |
| 재시도 로직 | 직접 구현 필요 | Built-in 지원 |
| 확장성 | 단일 서버 제한 | 다중 서버 분산 |
선택: Spring @Async
이유:
- 현재 요구사항(이메일 발송, 파일 삭제)은 중요도가 낮아 영속성 불필요
- 초기 개발 속도 및 인프라 비용 고려
- 향후 요구사항 변경 시 메시지 큐로 마이그레이션 가능 (인터페이스는 동일)
┌─────────────┐
│ Integration │ ← 적음 (End-to-End)
│ Tests │
└─────────────┘
┌───────────────────┐
│ Service Tests │ ← 중간 (Business Logic)
└───────────────────┘
┌────────────────────────┐
│ Unit Tests │ ← 많음 (Domain Objects)
└────────────────────────┘
목적: 도메인 객체(Entity, Value Object)와 서비스 로직의 정확성 검증
범위:
user-domain: Entity, Value Object, 도메인 로직user-application: Service 메서드 (Mock 사용)
특징:
- 외부 의존성(DB, Redis) 없음
- Mockito-Kotlin으로 의존성 Mocking
- 빠른 실행 속도 (<100ms per test)
예시 테스트:
EmailTest: 이메일 유효성 검증PasswordTest: 비밀번호 정책 검증 (MEMBER/ADMIN)UserTest: User 엔티티의 비즈니스 로직 검증 (탈퇴 처리 등)UserServiceImplTest: 회원가입/로그인 로직 검증
목적: API 엔드포인트와 전체 플로우 검증 (Controller → Service → Repository → DB)
범위:
bootstrap: API 엔드포인트 테스트- 실제 DB 연결 (Docker Compose 자동 실행)
- Spring Security 인증/인가 검증
특징:
@SpringBootTest: 전체 ApplicationContext 로드MockMvc: HTTP 요청/응답 시뮬레이션@Transactional: 각 테스트 후 자동 롤백
주요 테스트 시나리오:
- 회원가입 성공/실패 (MEMBER/ADMIN, 중복 이메일, 비밀번호 정책)
- 로그인 성공/실패 (JWT 토큰 발급, 잘못된 비밀번호)
- 동일 이메일로 다른 역할 가입 가능
- 사용자 정보 조회/수정/삭제 (본인/관리자 권한)
- 권한 없는 접근 거부 (403 Forbidden)
- Soft Delete 검증
- 전체 사용자 조회 (페이징, 정렬)
- ADMIN 권한 검증 (MEMBER는 접근 불가)
목적: 외부 시스템(Redis, JWT) 연동 검증
예시:
RefreshTokenRedisRepositoryTest: Redis CRUD 및 TTL 검증JwtTokenProviderTest: JWT 생성/검증 로직 테스트
목적: 의존성 규칙 및 레이어 아키텍처 준수 검증
예시:
@Test fun `도메인 계층은 인프라 계층에 의존하지 않아야 함`() { noClasses() .that().resideInAPackage("..userdomain..") .should().dependOnClassesThat().resideInAPackage("..userinfrastructure..") .check(importedClasses) }
| 계층 | 목표 커버리지 |
|---|---|
| Domain (Entity/VO) | 90%+ |
| Application (Service) | 80%+ |
| Infrastructure | 70%+ |
| API (Controller) | 80%+ |
# 전체 테스트 실행 ./gradlew test # 특정 모듈 테스트 ./gradlew :user-domain:test ./gradlew :user-application:test # 커버리지 리포트 생성 ./gradlew test jacocoTestReport
과제 수행 중 AI(Claude Code)를 활용한 부분을 투명하게 공개합니다.
위치: bootstrap/src/test/kotlin/com/example/integration/AuthControllerIntegrationTest.kt
사용한 프롬프트:
통합 테스트 코드를 작성해줘.
생성된 코드:
AuthControllerIntegrationTest.kt: 회원가입/로그인 API 테스트 (라인 3-4에 AI 사용 주석)MemberControllerIntegrationTest.kt: 회원 관리 API 테스트AdminControllerIntegrationTest.kt: 관리자 API 테스트
수정 사항:
- AI가 생성한 기본 구조를 유지하되, 엣지 케이스(비밀번호 정책 위반, 역할 불일치 등) 테스트 추가
@BeforeEach에서 공통 사용자 생성 로직을createUserAndGetToken()메서드로 리팩토링- HTTP 상태 코드를
400 Bad Request에서409 Conflict로 수정 (중복 이메일 오류)
위치: user-api/*/src/main/kotlin/**/*Controller.kt
사용한 프롬프트:
컨트롤러에 Swagger 애너테이션을 추가해줘.
생성된 코드:
@Tag(name = "인증 API", description = "회원가입, 로그인 등 인증 관련 API") @Operation(summary = "회원가입", description = "새로운 사용자를 등록합니다")
수정 사항: 없음 (그대로 사용)
위치: user-infrastructure-auth/src/main/kotlin/com/example/infrastructure/auth/AuthorizationChecker.kt
사용한 프롬프트:
프롬프트: 인증 / 인가 사용했을때 JWT / 세션 / OAuth2 등 다양한 인증 방식을 추상화가 필요할거 같아
생성된 코드:
AuthorizationChecker.kt: 현재 사용자 정보 조회 및 권한 체크 유틸리티 클래스AuthorizationException.kt: 권한 예외 클래스AuthenticatedUser.kt: 현재 인증된 사용자 정보 DTOAuthenticationContext.kt: SecurityContext에서 인증 정보 추출 유틸리티