Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
51a7ff4
feat: User Entity 생성 및 UserPrincipal 세팅
Darren4641 Dec 11, 2025
2d12b11
feat: ExceptionHandler 및 BaseResponse 세팅
Dec 12, 2025
44486c9
feat: Swagger 403/500 이슈 해결 및 auth 패키지 분리
Dec 17, 2025
9ba35f5
fix: spotlessApply
Dec 17, 2025
f6b4721
fix: @Repository 제거
Dec 18, 2025
2deb263
fix: security 관련 설정 user 패키지로 이동
Dec 18, 2025
d4c38eb
fix: User Entity 및 repository 관련 파일 user 패키지로 이동
Dec 18, 2025
ce812e4
fix: spotless 적용
Dec 18, 2025
41ae93d
feat: Jasypt 설정 추가
Darren4641 Dec 23, 2025
3667def
feat: kakao oauth idToken 발급 로직 추가 (테스트용)
Darren4641 Dec 23, 2025
f17b055
Merge branch 'staging' into feat/#7
Dec 26, 2025
aac77eb
fix: UseCase 어노테이션 추가 및 디렉토리 구조 변경
Dec 26, 2025
c427ed1
feat: kakao oauth oidc 추출
Darren4641 Dec 28, 2025
a426592
feat: k3s 배포를 위한 Dockerfile 생성
Dec 29, 2025
e683508
feat: 카카오 OIDC 회원가입 기능 구현
Dec 29, 2025
7670b92
Merge branch 'staging' into feat/#7
Dec 29, 2025
febdc20
feat: 예외 addMessage 메서드 제거
Dec 29, 2025
44ac306
refactor: signup -> register 네이밍 변경
Dec 29, 2025
b5f663d
fix: transactionRunner 추가
Dec 29, 2025
08211f7
fix: spotless 적용
Dec 29, 2025
1d101ad
fix: login 기능 구현 1차
Dec 30, 2025
7cb12fe
fix: refreshToken 발행 추가
Dec 31, 2025
5cc5655
fix: refreshToken을 통해 accessToken 갱신 API 추가
Jan 2, 2026
a732f84
fix: JasyptConfig profile 지정
Jan 2, 2026
85caa33
feat: CustomerUserDetailService 제거 (불필요)
Darren4641 Jan 3, 2026
74b7f72
Merge branch 'staging' into feat/#7
Darren4641 Jan 3, 2026
5c47ead
feat: spotless 적용
Darren4641 Jan 3, 2026
a0a498f
feat: converter 적용
Darren4641 Jan 4, 2026
36f57ad
feat: E2E TEST 코드 추가
Darren4641 Jan 4, 2026
4229c2a
feat: E2E TEST 코드 추가
Darren4641 Jan 4, 2026
50dfc34
fix: JasyptTest 테스트코드 yml 의존성 제거
Darren4641 Jan 4, 2026
2d7e099
feat: ci discord 알림 추가
Darren4641 Jan 4, 2026
b385dbb
ci: 빌드 최적화
Darren4641 Jan 4, 2026
c98470b
ci: k3s 강제 재시작 적용
Darren4641 Jan 4, 2026
41c0886
fix: kakao oauth nativeID로 변경
Darren4641 Jan 4, 2026
76224ac
ci: 배포후 downtime 0로 변경 (health check 추가)
Darren4641 Jan 5, 2026
552cf64
fix: app.version 공통 application.yaml로 이동
Darren4641 Jan 5, 2026
3547cb7
fix: JasyptUtil 파일 제거
Jan 6, 2026
5203d51
fix: RestClientConfig 파일 infra 디렉토리로 이동
Jan 6, 2026
3bf18d0
fix: OIDCPublicKeysResponse 변수 var -> val 변경
Jan 6, 2026
2fc6be2
fix: KakaoClientResponse 주석 제거
Jan 6, 2026
02d31cc
fix: Valid 어노테이션 @field로 변경
Jan 9, 2026
feb2d32
fix: contract 디렉토리 생성 후 response 이동
Jan 9, 2026
f077349
fix: ci yaml staging 브랜치로 변경
Jan 9, 2026
20935dd
Merge branch 'staging' into feat/#7
Jan 9, 2026
a6fbf29
fix: spotlessApply 적용
Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Git
.git
.gitignore
.gitattributes
.github

# Gradle
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!gradle/wrapper/gradle-wrapper.properties

# IDE
.idea
*.iml
*.iws
*.ipr
.vscode
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Logs
*.log

# Local data
postgres_data/
localstack_data/

# Docker
Dockerfile
.dockerignore
docker-compose.yaml

# Documentation
README.md
*.md

# Kotlin
.kotlin

# Test
src/test/

# Misc
Makefile
42 changes: 42 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Build stage
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DockerFile 내부에서 gradle build를 하는 것 같은데, multi stage build가 필요하지 않다면 외부에서 gradle build를 하고 빌드 결과물만 COPY해오는 방법이 어떨까요? 트레이드 오프가 있을 것 같은데, DockerFile 내부에서 gradle build를 하면 캐싱을 사실상 활용하기가 어려워 전체 빌드 시간이 증가하고 gradle build에만 필요한 파일들이 포함되어 이미지 크기가 증가할 것 같습니다.

reference:
https://www.linkedin.com/posts/sabaribalajip_should-i-build-my-jar-externally-and-copy-activity-7364497498779947009-GkIG

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀하신대로 GitAction 시점에 build한 jar를 이용하는 방식으로 진행했으며 Layerd Cache를 이용하여 이미지를 만들도록 변경했습니다!

FROM eclipse-temurin:21-jdk-alpine AS builder

WORKDIR /app

# Gradle wrapper와 설정 파일 복사 (캐싱 최적화를 위해 먼저 복사)
COPY gradle gradle
COPY gradlew .
COPY settings.gradle.kts .
COPY build.gradle.kts .

# 실행 권한 부여
RUN chmod +x ./gradlew

# 의존성 다운로드 (소스 코드 변경 시에도 이 레이어는 캐시됨)
RUN ./gradlew dependencies --no-daemon

# 소스 코드 복사
COPY src src

# 애플리케이션 빌드
RUN ./gradlew bootJar --no-daemon -x test

# Runtime stage
FROM eclipse-temurin:21-jre-alpine

WORKDIR /app

# 보안을 위해 non-root 사용자 생성
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring

# 빌드된 JAR 파일 복사
COPY --from=builder /app/build/libs/*.jar app.jar

# 환경변수 설정 (기본값, 런타임에 오버라이드 가능)
ENV SPRING_PROFILES_ACTIVE=staging
ENV JASYPT_PASSWORD=""

# 애플리케이션 실행
# Spring Boot가 SPRING_PROFILES_ACTIVE, JASYPT_PASSWORD 환경변수를 자동으로 읽음
ENTRYPOINT ["java", "-jar", "app.jar"]
66 changes: 60 additions & 6 deletions src/main/kotlin/com/yapp2app/auth/api/controller/AuthController.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package com.yapp2app.auth.api.controller

import com.yapp2app.auth.api.dto.KakaoOIDCLoginRequest
import com.yapp2app.auth.api.dto.TokenResponse
import com.yapp2app.auth.api.request.KakaoOIDCLoginRequest
import com.yapp2app.auth.api.response.GetKakaoRegisterResponse
import com.yapp2app.auth.api.response.GetKakaoTokenResponse
import com.yapp2app.auth.application.command.RegisterKakaoUserCommand
import com.yapp2app.auth.application.usecase.KakaoAuthUseCase
import com.yapp2app.common.api.dto.BaseResponse
import io.swagger.v3.oas.annotations.Hidden
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.validation.Valid
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

/**
Expand All @@ -20,15 +28,61 @@ import org.springframework.web.bind.annotation.RestController
@Tag(name = "AuthController", description = "인증/인가 API")
@RequestMapping("/api/auth")
@RestController
class AuthController {
class AuthController(private val kakaoAuthUseCase: KakaoAuthUseCase) {

/**
* OIDC 방식 로그인
* OIDC 방식 회원가입
*/
@Operation(
summary = "카카오 OIDC 회원가입",
description = """
## 카카오 OIDC 회원가입 API

앱에서 카카오 SDK로 획득한 idToken을 검증하고 회원가입을 처리합니다.

### 테스트용 idToken 발급 방법

#### 1단계: Authorization Code 획득
아래 URL을 브라우저에서 실행하여 카카오 로그인 후 idToken 얻습니다.

[local] https://kauth.kakao.com/oauth/authorize?client_id=a8777a62d28eee709e96cd6f803ec377&redirect_uri=http://localhost:8080/api/auth/test/kakao/redirect&response_type=code&scope=openid,profile_nickname

[staging] https://kauth.kakao.com/oauth/authorize?client_id=a8777a62d28eee709e96cd6f803ec377&redirect_uri=https://dev-yapp.suitestudy.com:4641/api/auth/test/kakao/redirect&response_type=code&scope=openid,profile_nickname

응답의 `id_token` 필드 값을 이 API의 `idToken`으로 사용하세요.
""",
)
@ApiResponses(
ApiResponse(responseCode = "200", description = "카카오 OIDC 엔드포인트가 정상적으로 작동합니다."),
)
@PostMapping("/kakao/oidc")
fun kakaoLoginWithOIDC(@RequestBody request: KakaoOIDCLoginRequest): BaseResponse<TokenResponse> =
BaseResponse(data = TokenResponse("OK", "OK"))
fun kakaoRegisterWithOIDC(
@RequestBody @Valid request: KakaoOIDCLoginRequest,
): BaseResponse<GetKakaoRegisterResponse> {
val result = kakaoAuthUseCase.execute(RegisterKakaoUserCommand(idToken = request.idToken))

return BaseResponse(data = GetKakaoRegisterResponse(oid = result.oid, providerType = result.providerType))
}

/**
* ****** Test용이므로 Swagger Hidden 처리 ******
* 테스트용 카카오 OAuth Redirect 엔드포인트
* Authorization Code를 받아서 idToken으로 교환
*/
@Hidden
@GetMapping("/test/kakao/redirect")
fun kakaoTestRedirect(@RequestParam code: String): BaseResponse<GetKakaoTokenResponse> {
val tokenResponse = kakaoAuthUseCase.getAccessTokenByCode(code)
return BaseResponse(
data = GetKakaoTokenResponse(
accessToken = tokenResponse.accessToken,
tokenType = tokenResponse.tokenType,
refreshToken = tokenResponse.refreshToken,
expiresIn = tokenResponse.expiresIn,
scope = tokenResponse.scope,
refreshTokenExpiresIn = tokenResponse.refreshTokenExpiresIn,
idToken = tokenResponse.idToken,
),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import com.yapp2app.auth.infra.security.token.AuthTokenProvider
import com.yapp2app.common.api.dto.BaseResponse
import com.yapp2app.common.api.dto.ResultCode
import com.yapp2app.common.exception.BusinessException
import com.yapp2app.user.application.repository.UserRepository
import com.yapp2app.user.domain.enums.ProviderType
import com.yapp2app.user.infra.persist.jpa.UserRepository
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
Expand All @@ -31,15 +31,11 @@ class TestAuthController(

@PostMapping("/login")
fun login(@RequestBody request: LoginRequest): BaseResponse<TokenResponse> {
val user = userRepository.findByEmailAndProviderType(
request.email,
ProviderType.LOCAL,
val user = userRepository.findByOidAndProviderType(
request.oid,
ProviderType.KAKAO,
) ?: throw BusinessException(ResultCode.NOT_FOUND_USER)

if (!passwordEncoder.matches(request.password, user.password)) {
throw BusinessException(ResultCode.SECURITY_ERROR)
}

// Access Token 생성
val accessToken = tokenProvider.createToken(
id = user.id.toString(),
Expand Down
7 changes: 1 addition & 6 deletions src/main/kotlin/com/yapp2app/auth/api/dto/AuthDto.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@ package com.yapp2app.auth.api.dto
// Request DTOs
// ====================================================================================================

/**
* 카카오 OIDC 로그인
*/
data class KakaoOIDCLoginRequest(val idToken: String)

data class LoginRequest(val email: String, val password: String)
data class LoginRequest(val oid: Long, val password: String)

// ====================================================================================================
// Response DTOs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.yapp2app.auth.api.request

import jakarta.validation.constraints.NotBlank

/**
* fileName : AuthRequest
* author : darren
* date : 2025. 12. 26. 18:05
* description : 인증/인가 관련 요청 body
*/
data class KakaoOIDCLoginRequest(@NotBlank(message = "ID 토큰은 필수입니다") val idToken: String)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

질문입니다.
@notblank로 사용해도 bean validation 정상적으로 적용되나요?
@field:NotBlank와 같이 명시적인 스코프 지정을 안하면 적용이 안된다는 내용이 있어 공유드립니다.

reference:
https://velog.io/@ldhbenecia/Spring-Boot-Kotlin-Bean-Validation%EC%9D%B4-%EC%9E%91%EB%8F%99%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영완료했습니다! String값의 경우만 NotBlank 처리했고, 나머지 Long값이나 enum타입의 경우는 ?인자가 붙은게 아니기떄문에 이미 null을 허용할 수 없어서 기본값이 0으로 들어가는 것으로 확인했습니다. 0으로 들어가기 때문에 가입된계정이 없습니다 이라는 BusinessException을 던지는데, 이부분을 그러면 Long? 으로 반환할지도 고민이되는데, 현상태도 괜찮을 것 같고 어떻게 하는 것이 좋을까요?

21 changes: 21 additions & 0 deletions src/main/kotlin/com/yapp2app/auth/api/response/AuthResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.yapp2app.auth.api.response

import com.yapp2app.user.domain.enums.ProviderType

/**
* fileName : AuthResponse
* author : darren
* date : 2025. 12. 26. 18:05
* description : Auth aggregate에 대한 응답
*/
data class GetKakaoRegisterResponse(val oid: Long, val providerType: ProviderType)

data class GetKakaoTokenResponse(
val accessToken: String,
val tokenType: String,
val refreshToken: String,
val expiresIn: Int,
val scope: String? = null,
val refreshTokenExpiresIn: Int? = null,
val idToken: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.yapp2app.auth.application.command

/**
* fileName : AuthCommand
* author : darren
* date : 2025. 12. 12. 13:18
* description : 인증/인가 관련 API
*/
data class RegisterKakaoUserCommand(val idToken: String)
Loading