-
Notifications
You must be signed in to change notification settings - Fork 1
feat: DisableSwaggerSecurity 어노테이션으로 Swagger 내 로그인과 토큰 갱신 API 보안 설정을 비활성화하는 기능 추가 #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0f70fbf
36067d8
8fc6fa6
6aace86
9f365f9
2bfddf5
40c0bbf
90716b2
cb2340b
76851b7
ce96fb3
558d48b
7c3977e
dc46056
a6516db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,7 @@ import org.yapp.apis.auth.dto.response.RefreshTokenResponse | |
| ) | ||
| data class DeleteTokenRequest private constructor( | ||
| @field:NotBlank(message = "Refresh token must not be blank.") | ||
| @Schema(description = "Refresh token to be deleted", example = "eyJhbGciOiJIUz...") | ||
| @field:Schema(description = "Refresh token to be deleted", example = "eyJhbGciOiJIUz...") | ||
| val refreshToken: String? = null | ||
|
Comment on lines
+13
to
14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive)
🤖 Prompt for AI Agents |
||
| ) { | ||
| fun validRefreshToken() = refreshToken!! | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,14 +12,14 @@ import java.util.UUID | |
| description = "Request DTO to generate a new pair of access and refresh tokens" | ||
| ) | ||
| data class GenerateTokenPairRequest private constructor( | ||
| @Schema( | ||
| @field:Schema( | ||
| description = "User ID", | ||
| example = "a1b2c3d4-e5f6-7890-1234-56789abcdef0" | ||
| ) | ||
| @field:NotNull(message = "userId must not be null") | ||
| val userId: UUID? = null, | ||
|
|
||
|
Comment on lines
+15
to
21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 검증과 문서의 required 불일치
다음 중 하나를 권장합니다:
간단안 적용 diff 예시: @Schema(
name = "GenerateTokenPairRequest",
description = "Request DTO to generate a new pair of access and refresh tokens"
)
data class GenerateTokenPairRequest private constructor(
- @field:Schema(
+ @field:Schema(
+ required = true,
description = "User ID",
example = "a1b2c3d4-e5f6-7890-1234-56789abcdef0"
)
@field:NotNull(message = "userId must not be null")
val userId: UUID? = null,
- @field:Schema(
+ @field:Schema(
+ required = true,
description = "User role",
example = "USER"
)
@field:NotNull(message = "role must not be null")
val role: Role? = null
)Also applies to: 22-27 🤖 Prompt for AI Agents |
||
| @Schema( | ||
| @field:Schema( | ||
| description = "User role", | ||
| example = "USER" | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,23 +14,23 @@ import org.yapp.domain.user.ProviderType | |
| description = "DTO for social login requests" | ||
| ) | ||
| data class SocialLoginRequest private constructor( | ||
| @Schema( | ||
| @field:Schema( | ||
| description = "Type of social login provider", | ||
| example = "KAKAO", | ||
| required = true | ||
| ) | ||
| @field:NotBlank(message = "Provider type is required") | ||
| val providerType: String? = null, | ||
|
Comment on lines
+17
to
23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) providerType 값 제약 강화(선택) — 잘못된 값 조기 차단 현재 String 수신 후 enum 변환 실패 시 예외 처리하고 있습니다. 요청 단계에서 허용 값(예: KAKAO|APPLE)만 통과하도록 정규식 검증을 추가하면 불필요한 예외 흐름을 줄일 수 있습니다. 정규식 검증 추가 예시: @field:Schema(
description = "Type of social login provider",
example = "KAKAO",
required = true
)
-@field:NotBlank(message = "Provider type is required")
+@field:NotBlank(message = "Provider type is required")
+@field:jakarta.validation.constraints.Pattern(
+ regexp = "KAKAO|APPLE",
+ message = "Provider type must be one of [KAKAO, APPLE]"
+)
val providerType: String? = null,파일 상단 import 추가: import jakarta.validation.constraints.Pattern🤖 Prompt for AI Agents |
||
|
|
||
| @Schema( | ||
| @field:Schema( | ||
| description = "OAuth token issued by the social provider", | ||
| example = "eyJ...", | ||
| required = true | ||
| ) | ||
| @field:NotBlank(message = "OAuth token is required") | ||
| val oauthToken: String? = null, | ||
|
Comment on lines
+25
to
31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 토큰/코드 필드 Swagger 마스킹 및 WRITE_ONLY 권장 요청 DTO에 포함된 해당 라인 범위 내 적용 예시: @field:Schema(
description = "OAuth token issued by the social provider",
example = "eyJ...",
- required = true
+ required = true,
+ format = "password",
+ accessMode = io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY
)
@field:NotBlank(message = "OAuth token is required")
val oauthToken: String? = null,
@field:Schema(
description = "Authorization code used to issue Apple access/refresh tokens (required only for Apple login)",
example = "c322a426...",
- required = false
+ required = false,
+ format = "password",
+ accessMode = io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY
)
val authorizationCode: String? = nullAlso applies to: 33-38 🤖 Prompt for AI Agents |
||
|
|
||
| @Schema( | ||
| @field:Schema( | ||
| description = "Authorization code used to issue Apple access/refresh tokens (required only for Apple login)", | ||
| example = "c322a426...", | ||
| required = false | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,15 +11,15 @@ import java.util.* | |
| ) | ||
| data class TokenGenerateRequest private constructor( | ||
| @field:NotNull(message = "userId must not be null") | ||
| @Schema(description = "User ID", example = "f6b7d490-1b1a-4b9f-8e8e-27f8e3a5dafa") | ||
| @field:Schema(description = "User ID", example = "f6b7d490-1b1a-4b9f-8e8e-27f8e3a5dafa") | ||
| val userId: UUID? = null, | ||
|
|
||
| @field:NotBlank(message = "refreshToken must not be blank") | ||
| @Schema(description = "Generated refresh token", example = "eyJhbGciOiJIUzI1NiIsInR...") | ||
| @field:Schema(description = "Generated refresh token", example = "eyJhbGciOiJIUzI1NiIsInR...") | ||
| val refreshToken: String? = null, | ||
|
|
||
| @field:NotNull(message = "expiration must not be null") | ||
| @Schema(description = "Refresh token expiration time (in seconds)", example = "2592000") | ||
| @field:Schema(description = "Refresh token expiration time (in seconds)", example = "2592000") | ||
| val expiration: Long? = null | ||
|
Comment on lines
13
to
23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Nullable 선언과 Bean Validation 제약 조건의 불일치 필드가 🤖 Prompt for AI Agents |
||
| ) { | ||
| fun validUserId() = userId!! | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,32 +8,32 @@ import org.yapp.domain.user.ProviderType | |
| description = "Response DTO containing user information for newly registered users via social login" | ||
| ) | ||
| data class UserCreateInfoResponse private constructor( | ||
| @Schema( | ||
| @field:Schema( | ||
| description = "사용자 이메일", | ||
| example = "[email protected]", | ||
| nullable = true | ||
| ) | ||
| val email: String?, | ||
|
|
||
| @Schema( | ||
| @field:Schema( | ||
| description = "사용자 닉네임", | ||
| example = "코딩하는곰", nullable = true | ||
| ) | ||
| val nickname: String?, | ||
|
|
||
| @Schema( | ||
| @field:Schema( | ||
| description = "사용자 프로필 이미지 URL", | ||
| example = "https://example.com/image.jpg", nullable = true | ||
| ) | ||
| val profileImageUrl: String? = null, | ||
|
|
||
| @Schema( | ||
| @field:Schema( | ||
| description = "소셜 로그인 제공자", | ||
| example = "KAKAO") | ||
|
|
||
| val providerType: ProviderType, | ||
|
|
||
| @Schema( | ||
| @field:Schema( | ||
| description = "소셜 제공자에서 발급한 식별자", | ||
| example = "12345678901234567890" | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,28 +1,31 @@ | ||
| package org.yapp.apis.auth.service | ||
|
|
||
| import jakarta.validation.Valid | ||
| import org.springframework.stereotype.Service | ||
| import org.springframework.transaction.annotation.Propagation | ||
| import org.springframework.transaction.annotation.Transactional | ||
| import org.yapp.apis.user.dto.request.FindOrCreateUserRequest | ||
| import org.yapp.apis.user.dto.request.SaveAppleRefreshTokenRequest | ||
| import org.yapp.apis.user.dto.response.CreateUserResponse | ||
| import org.yapp.apis.user.service.UserAccountService | ||
| import org.yapp.globalutils.annotation.ApplicationService | ||
|
|
||
| @Service | ||
| @ApplicationService | ||
| class UserSignInService( | ||
| private val userAccountService: UserAccountService, | ||
| ) { | ||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| fun processSignIn( | ||
| @Valid request: FindOrCreateUserRequest, | ||
| appleRefreshToken: String? | ||
| @Valid request: FindOrCreateUserRequest, appleRefreshToken: String? | ||
| ): CreateUserResponse { | ||
| val createUserResponse = userAccountService.findOrCreateUser(request) | ||
| val initialUserResponse = userAccountService.findOrCreateUser(request) | ||
|
|
||
| appleRefreshToken?.let { | ||
| userAccountService.updateAppleRefreshToken(createUserResponse.id, it) | ||
| } | ||
|
|
||
| return createUserResponse | ||
| return appleRefreshToken.takeIf { !it.isNullOrBlank() } | ||
| ?.let { token -> | ||
| userAccountService.updateAppleRefreshToken( | ||
| SaveAppleRefreshTokenRequest.of( | ||
| initialUserResponse, token | ||
| ) | ||
| ) | ||
| } ?: initialUserResponse | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
인터페이스에
@DisableSwaggerSecurity부착은 의도에 부합하지만, 현 Customizer 로직으로는 미감지 위험SwaggerConfig.securityOperationCustomizer()가handlerMethod.getMethodAnnotation(...)로만 검사하면, 구현 클래스의 메서드에 어노테이션이 없을 때(=인터페이스에만 있을 때) 감지가 누락될 수 있습니다.AnnotatedElementUtils.findMergedAnnotation기반 + 인터페이스 메서드 탐색으로 보강하면 안정적으로 동작합니다. 관련 수정 제안은SwaggerConfig.kt코멘트를 참고하세요.빠른 점검:
Also applies to: 25-25, 52-52
🏁 Script executed:
Length of output: 240
다음 스크립트를 실행해
SwaggerConfig.kt내securityOperationCustomizer구현이handlerMethod.getMethodAnnotation만 사용하고 있는지 확인해 주세요:🏁 Script executed:
Length of output: 784
securityOperationCustomizer 로직 보강 필요: 인터페이스 메서드 어노테이션 감지 누락
현재
SwaggerConfig.kt의securityOperationCustomizer는 구현 클래스의 메서드에만 붙은 어노테이션을handlerMethod.getMethodAnnotation(...)로 조회하기 때문에, 인터페이스에만 선언된@DisableSwaggerSecurity를 놓칠 수 있습니다. 아래 위치를 수정해 주세요.apis/src/main/kotlin/org/yapp/apis/config/SwaggerConfig.kt
fun securityOperationCustomizer (라인 57~63)
현 구현 예시:
제안된 수정 방법 (인터페이스 메서드까지 탐색):
위와 같이 변경하면 인터페이스에 선언된
@DisableSwaggerSecurity도 안정적으로 감지됩니다.🤖 Prompt for AI Agents