-
Notifications
You must be signed in to change notification settings - Fork 1
refactor: oauth2 resource server 도입 #36
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
Conversation
📝 Walkthrough""" WalkthroughJWT 인증 및 예외처리 구조가 대폭 변경되었습니다. 기존의 JwtAuthenticationFilter와 관련 예외 클래스, 에러 코드가 제거되고, Spring Security의 OAuth2 Resource Server 기반 JWT 인증으로 전환되었습니다. 인증 실패 및 인가 실패 시 커스텀 핸들러를 통해 JSON 형태의 에러 응답을 반환하도록 개선되었습니다. Role 기반 권한 관리와 User/Entity 구조에도 역할 정보가 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Gateway
participant JwtDecoder
participant AuthEntryPoint
participant AccessDeniedHandler
Client->>Gateway: API 요청 (JWT 포함)
Gateway->>JwtDecoder: JWT 디코딩 및 검증
alt 인증 성공
JwtDecoder-->>Gateway: 인증 객체 반환
Gateway->>Gateway: 권한 체크
alt 인가 성공
Gateway-->>Client: 정상 응답
else 인가 실패
Gateway->>AccessDeniedHandler: AccessDeniedException 발생
AccessDeniedHandler-->>Client: 401/403 JSON 에러 응답
end
else 인증 실패
Gateway->>AuthEntryPoint: AuthenticationException 발생
AuthEntryPoint-->>Client: 401 JSON 에러 응답
end
Assessment against linked issues
Assessment against linked issues: Out-of-scope changes해당 변경 내역에서는 모든 코드 변경이 연결된 이슈(#35)의 목적(필터 예외 메시지 개선 및 구조 개편)에 부합하며, 범위를 벗어난 기능적 변경은 발견되지 않았습니다. 📜 Recent review detailsConfiguration used: .coderabbit.yaml 📒 Files selected for processing (1)
🧰 Additional context used🪛 detekt (1.23.8)gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt[warning] 51-51: In most cases using a spread operator causes a full copy of the array to be created before calling a method. This may result in a performance penalty. (detekt.performance.SpreadOperator) ⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
🔇 Additional comments (3)
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
move-hoon
left a comment
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.
보수해주셔서 정말 감사합니다!
사실 저도 시큐리티 수정 측면에서 CustomAccessDeniedHandler와 CustomJwtAuthenticationEntryPoint 도입이 필요하다고 생각했었는데요, 혹시 이 부분도 함께 반영 부탁드려도 괜찮을까요?
|
https://yoo-dev.tistory.com/28 참고 링크 드립니다! |
|
저도 동의합니다! 말씀해주신 방향대로 함께 반영해보는 게 좋을 것 같습니다. 감사합니다! 🙇♂️ |
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.
Actionable comments posted: 3
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (5)
gateway/src/main/kotlin/org/yapp/gateway/config/CustomAccessDeniedHandler.kt(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/config/CustomAuthenticationEntryPoint.kt(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/config/SecurityConfig.kt(3 hunks)gateway/src/main/kotlin/org/yapp/gateway/filter/JwtAuthenticationFilter.kt(3 hunks)global-utils/src/main/kotlin/org/yapp/globalutils/exception/CommonErrorCode.kt(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-validation
🔇 Additional comments (5)
global-utils/src/main/kotlin/org/yapp/globalutils/exception/CommonErrorCode.kt (1)
20-24: 적절한 에러 코드 추가입니다.인증 및 인가 관련 에러 처리를 위한 표준 HTTP 상태 코드들이 적절하게 추가되었습니다. 기존 패턴을 잘 따르고 있으며, 에러 코드 번호도 순차적으로 배정되었습니다.
gateway/src/main/kotlin/org/yapp/gateway/config/CustomAuthenticationEntryPoint.kt (1)
14-57: 깔끔한 인증 예외 처리 구현입니다.
AuthenticationEntryPoint구현이 잘 되어 있습니다. JSON 직렬화 실패와 응답 작성 실패를 각각 처리하는 것이 좋은 접근입니다. 로깅도 적절하게 구성되어 있습니다.gateway/src/main/kotlin/org/yapp/gateway/config/SecurityConfig.kt (2)
30-60: 보안 설정이 잘 개선되었습니다.커스텀 예외 핸들러 추가와 세밀한 엔드포인트별 권한 설정이 좋습니다. 특히
/api/v1/auth/refresh,/api/v1/auth/signin,/api/v1/books/search,/api/v1/books/detail를 명시적으로 허용하고 나머지는 인증을 요구하는 구조가 명확합니다.
25-27:JwtAuthenticationFilter생성자 파라미터를 확인하세요.
JwtAuthenticationFilter에objectMapper를 전달하고 있지만, 실제 필터 구현에서는 사용되지 않고 있습니다. 필터에서ObjectMapper가 필요 없다면 제거하는 것이 좋습니다.gateway/src/main/kotlin/org/yapp/gateway/config/CustomAccessDeniedHandler.kt (1)
15-54: 일관된 접근 거부 예외 처리 구현입니다.
CustomAuthenticationEntryPoint와 동일한 패턴으로 구현되어 일관성이 있고, 예외 처리가 적절합니다.
| val authentication = jwtTokenProvider.getAuthentication(token) | ||
| SecurityContextHolder.getContext().authentication = authentication | ||
|
|
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.
JWT 검증 예외 처리가 누락되었습니다.
PR 목표가 JWT 예외 처리 개선인데, 오히려 모든 예외 처리가 제거되었습니다. jwtTokenProvider.getAuthentication()이 만료되거나 잘못된 토큰에 대해 예외를 던질 경우 처리가 필요합니다.
- val authentication = jwtTokenProvider.getAuthentication(token)
- SecurityContextHolder.getContext().authentication = authentication
+ try {
+ val authentication = jwtTokenProvider.getAuthentication(token)
+ SecurityContextHolder.getContext().authentication = authentication
+ } catch (e: JwtException) {
+ // JWT 예외는 CustomAuthenticationEntryPoint에서 처리되도록 필터 체인 계속 진행
+ filterChain.doFilter(request, response)
+ return
+ }🤖 Prompt for AI Agents
In gateway/src/main/kotlin/org/yapp/gateway/filter/JwtAuthenticationFilter.kt
around lines 38 to 40, the code sets the authentication without handling
exceptions from jwtTokenProvider.getAuthentication(token). To fix this, wrap the
call to getAuthentication in a try-catch block to catch exceptions related to
expired or invalid JWT tokens, and handle them appropriately, such as logging
the error and preventing the authentication from being set.
gateway/src/main/kotlin/org/yapp/gateway/filter/JwtAuthenticationFilter.kt
Outdated
Show resolved
Hide resolved
|
|
||
| private val log = LoggerFactory.getLogger(CustomAccessDeniedHandler::class.java) | ||
|
|
||
| @Throws(IOException::class, ServletException::class) |
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.
🧹 Nitpick (assertive)
@Throws 어노테이션이 불필요합니다.
메서드 내부에서 모든 IOException을 catch하고 있으므로 @Throws 어노테이션은 실제 동작과 맞지 않습니다.
- @Throws(IOException::class, ServletException::class)
override fun handle(📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Throws(IOException::class, ServletException::class) | |
| override fun handle( |
🤖 Prompt for AI Agents
In gateway/src/main/kotlin/org/yapp/gateway/config/CustomAccessDeniedHandler.kt
at line 21, remove the @Throws annotation from the method because all
IOException instances are caught inside the method, making the annotation
unnecessary and inconsistent with the actual behavior.
oauth2-resource-server
주요 변경 사항1. JwtAuthenticationFilter 삭제
2. SecurityConfig 수정: oauth2ResourceServer 도입
3. JwtConfig 신규 추가: JwtDecoder 및 JwtAuthenticationConverter Bean 등록
4. JwtTokenProvider 역할 축소
시나리오별 요청 흐름 예시1. 성공적인 인증 요청 (200 OK)기존 방식:
새로운 방식:
2. 만료되거나 유효하지 않은 토큰 요청 (401 Unauthorized)기존 방식:
새로운 방식:
3. 권한이 없는 리소스 요청 (403 Forbidden)기존 방식:
새로운 방식:
|
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.
Actionable comments posted: 5
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (19)
apis/src/main/kotlin/org/yapp/apis/auth/controller/AuthController.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/auth/helper/AuthTokenHelper.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/auth/usecase/AuthUseCase.kt(1 hunks)buildSrc/src/main/kotlin/Dependencies.kt(1 hunks)domain/src/main/kotlin/org/yapp/domain/user/User.kt(7 hunks)domain/src/main/kotlin/org/yapp/domain/user/UserDomainService.kt(2 hunks)gateway/build.gradle.kts(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/config/JwtConfig.kt(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/filter/JwtAuthenticationFilter.kt(0 hunks)gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenProvider.kt(2 hunks)gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenService.kt(2 hunks)gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenServiceImpl.kt(3 hunks)gateway/src/main/kotlin/org/yapp/gateway/jwt/exception/JwtErrorCode.kt(0 hunks)gateway/src/main/kotlin/org/yapp/gateway/jwt/exception/JwtException.kt(0 hunks)gateway/src/main/kotlin/org/yapp/gateway/security/CustomAccessDeniedHandler.kt(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/security/CustomAuthenticationEntryPoint.kt(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt(1 hunks)global-utils/src/main/kotlin/org/yapp/globalutils/auth/Role.kt(1 hunks)infra/src/main/kotlin/org/yapp/infra/user/entity/UserEntity.kt(3 hunks)
💤 Files with no reviewable changes (3)
- gateway/src/main/kotlin/org/yapp/gateway/jwt/exception/JwtException.kt
- gateway/src/main/kotlin/org/yapp/gateway/jwt/exception/JwtErrorCode.kt
- gateway/src/main/kotlin/org/yapp/gateway/filter/JwtAuthenticationFilter.kt
🧰 Additional context used
🧬 Code Graph Analysis (2)
gateway/src/main/kotlin/org/yapp/gateway/security/CustomAccessDeniedHandler.kt (1)
gateway/src/main/kotlin/org/yapp/gateway/security/CustomAuthenticationEntryPoint.kt (1)
sendErrorResponse(29-46)
gateway/src/main/kotlin/org/yapp/gateway/security/CustomAuthenticationEntryPoint.kt (1)
gateway/src/main/kotlin/org/yapp/gateway/security/CustomAccessDeniedHandler.kt (1)
sendErrorResponse(29-46)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-validation
🔇 Additional comments (27)
gateway/build.gradle.kts (1)
10-10: LGTM!OAuth2 Resource Server 의존성 추가가 적절합니다. 이는 JWT 기반 인증을 위한 표준 Spring Security 구성요소입니다.
apis/src/main/kotlin/org/yapp/apis/auth/controller/AuthController.kt (1)
32-32: 메서드명 변경으로 인한 코드 일관성 개선을 확인했습니다.
refreshToken()에서reissueTokenPair()로 변경된 것은 메서드가 토큰 쌍을 재발급한다는 의미를 더 명확하게 전달합니다.buildSrc/src/main/kotlin/Dependencies.kt (1)
11-11: LGTM!OAuth2 Resource Server 의존성 상수가 올바르게 추가되었습니다. 네이밍 패턴도 일관됩니다.
domain/src/main/kotlin/org/yapp/domain/user/UserDomainService.kt (1)
5-5: 기본 사용자 역할 할당 검증 완료–
global-utils/src/main/kotlin/org/yapp/globalutils/auth/Role.kt에enum class Role가 정의되어 있습니다.
–domain/src/main/kotlin/org/yapp/domain/user/UserDomainService.kt에서만role = Role.USER할당을 확인했습니다.위치한 코드가 의도한 대로 최소 권한 원칙에 준수하고 있으므로 변경 사항을 승인합니다.
global-utils/src/main/kotlin/org/yapp/globalutils/auth/Role.kt (1)
3-6: Role 열거형 구현이 잘 되어 있습니다.Spring Security 표준 형식(
ROLE_접두사)을 따르고 있으며, 시스템 전반에서 일관된 역할 관리를 위한 간단하고 명확한 구현입니다.apis/src/main/kotlin/org/yapp/apis/auth/helper/AuthTokenHelper.kt (1)
15-17: 토큰 생성 시 역할 정보 포함이 적절하게 구현되었습니다.
generateTokenPair메서드가Role매개변수를 받도록 업데이트되어 액세스 토큰에 사용자 역할 정보가 포함되도록 개선되었습니다. 이는 역할 기반 인증을 위한 필수적인 변경사항입니다.apis/src/main/kotlin/org/yapp/apis/auth/usecase/AuthUseCase.kt (2)
27-27: 로그인 시 사용자 역할 정보 전달이 적절합니다.
signIn메서드에서user.role을generateTokenPair에 전달하여 토큰에 역할 정보가 포함되도록 구현되었습니다.
31-36: 토큰 재발급 로직이 개선되었습니다.메서드명이
reissueTokenPair로 변경되어 의도가 더 명확해졌고, 전체 사용자 객체를 조회하여 역할 정보를 포함한 토큰을 재발급하도록 개선되었습니다.gateway/src/main/kotlin/org/yapp/gateway/security/CustomAccessDeniedHandler.kt (1)
20-27: 접근 거부 처리 로직이 적절합니다.요청 URI와 예외 정보를 로그로 기록하고 표준화된 에러 응답을 반환하는 구현이 적절합니다.
domain/src/main/kotlin/org/yapp/domain/user/User.kt (4)
3-3: Role 임포트가 적절하게 추가되었습니다.새로운 역할 기반 인증을 위한
Role열거형이 올바르게 임포트되었습니다.
16-16: User 도메인 모델에 역할 속성이 적절히 추가되었습니다.KDoc 문서와 데이터 클래스에
role속성이 일관되게 추가되어 역할 기반 인증을 지원하도록 개선되었습니다.Also applies to: 28-28
49-49: 팩토리 메서드들이 일관되게 업데이트되었습니다.
create와reconstruct팩토리 메서드 모두에서role매개변수가 추가되어 일관된 도메인 객체 생성을 지원합니다.Also applies to: 61-61, 75-75, 87-87
95-95: encapsulation 개선이 적절합니다.
isDeleted()메서드를 private으로 변경하여 도메인 객체의 캡슐화를 개선했습니다.gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenServiceImpl.kt (2)
4-4: 역할 기반 JWT 토큰 생성 로직이 적절히 구현되었습니다.
Role타입의 매개변수 추가와 단일 역할을 리스트로 변환하여 전달하는 방식이 적절합니다. 현재 구현에서는 단일 역할만 지원하지만, 향후 다중 역할 지원을 위한 확장 가능성을 고려한 설계입니다.Also applies to: 19-19, 22-23
42-42: 불필요한 세미콜론 제거로 코드 정리가 개선되었습니다.코드 스타일 개선이 적절히 이루어졌습니다.
infra/src/main/kotlin/org/yapp/infra/user/entity/UserEntity.kt (2)
8-8: Role 속성이 적절히 추가되었습니다.
@Enumerated(EnumType.STRING)어노테이션을 사용하여 enum 값을 문자열로 저장하는 것이 적절합니다. 데이터베이스 호환성과 가독성 측면에서 좋은 선택입니다.Also applies to: 36-36, 47-50
59-59: 마이그레이션 스크립트 확인 필요
fd -e sql -e py -e kt migration명령 결과 마이그레이션 파일이 검색되지 않았습니다.
스키마 변경을 위한 SQL/파이썬/코틀린 기반 마이그레이션 스크립트가 준비(추가)되었는지 다시 한번 확인해주세요.gateway/src/main/kotlin/org/yapp/gateway/config/JwtConfig.kt (3)
12-21: 설정 클래스 구조가 잘 설계되었습니다.상수 정의와 생성자 주입 방식이 적절하며, 한국어 주석이 코드의 이해도를 높입니다.
38-50: JWT 인증 변환기가 역할 기반 인증을 올바르게 처리합니다.
roles클레임에서 역할을 추출하여SimpleGrantedAuthority로 변환하는 로직이 적절합니다.sub클레임을 principal로 사용하는 것도 JWT 표준에 맞습니다.
28-32: 시크릿 키 설정 위치 확인 필요코드베이스에서
jwt.secret-key설정이*.yml,*.yaml,*.properties파일에 존재하지 않습니다.
환경 변수나 외부 설정(Vault, Kubernetes Secret 등)으로 시크릿 키를 주입하는지 수동으로 확인해주세요.gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenService.kt (1)
3-3: 인터페이스가 역할 기반 토큰 생성을 적절히 지원합니다.
Role매개변수 추가와 메서드 시그니처 변경이 적절하며, 토큰 검증 메서드 제거로 단일 책임 원칙을 잘 준수합니다.Also applies to: 15-15, 18-18
gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt (1)
22-32: OAuth2 resource server 설정이 적절히 구성되었습니다.CSRF 비활성화, 세션 상태 비저장, 커스텀 예외 핸들러 설정이 모두 적절하며, JWT 인증 변환기 통합이 올바르게 수행되었습니다.
gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenProvider.kt (5)
3-8: 깔끔한 리팩토링입니다!토큰 생성에만 필요한 import만 남기고 검증 관련 import를 제거한 것은 단일 책임 원칙에 잘 부합합니다.
30-33: 역할 기반 토큰 생성 로직이 잘 구현되었습니다.
Role객체를 문자열 키로 변환하여 JWT에 포함시키는 방식이 명확합니다.다만,
roles파라미터가 null이거나 비어있을 때의 동작을 확인해주세요. 현재 코드는 빈 리스트를 처리할 수 있지만, null safety를 보장하는지 확인이 필요합니다.
43-59: 역할 정보의 조건부 포함 로직이 우수합니다!역할이 있을 때만 JWT에 포함시키는 조건부 로직이 잘 구현되었습니다. 특히 refresh token에는 역할 정보를 포함시키지 않아 보안상 더 안전한 설계입니다.
39-41: Refresh token 만료 시간 노출 메서드 사용처 확인 완료rg 결과
getRefreshTokenExpiration()메서드가 다음 위치에서 사용되고 있음을 확인했습니다.
외부(Refresh Token 저장 시 TTL 설정)에서 만료 시간이 필요한 로직에 활용되고 있어 의도한 대로 동작합니다. 문제없으니 승인합니다.
- gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenService.kt
- gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenServiceImpl.kt
- apis/src/main/kotlin/org/yapp/apis/auth/helper/AuthTokenHelper.kt
1-61: PR 설명 및 JwtTokenProvider 테스트 보강 요청
- PR 설명에 “JwtAuthenticationFilter 예외 처리 개선” 외에도
JwtTokenProvider 리팩토링 및 전체 JWT 처리 아키텍처 변경사항을 명확히 반영해주세요.gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenProvider.kt관련 단위 테스트가 존재하지 않거나 업데이트되지 않은 것으로 확인됩니다.
테스트 구현 여부를 수동으로 검증하고, 누락된 경우 추가 부탁드립니다.
gateway/src/main/kotlin/org/yapp/gateway/security/CustomAuthenticationEntryPoint.kt
Show resolved
Hide resolved
| private fun sendErrorResponse(response: HttpServletResponse) { | ||
| val errorCode = CommonErrorCode.UNAUTHORIZED | ||
|
|
||
| val errorResponse = ErrorResponse.builder() | ||
| .status(errorCode.getHttpStatus().value()) | ||
| .code(errorCode.getCode()) | ||
| .message(errorCode.getMessage()) | ||
| .build() | ||
|
|
||
| try { | ||
| response.status = errorCode.getHttpStatus().value() | ||
| response.contentType = MediaType.APPLICATION_JSON_VALUE | ||
| response.characterEncoding = "UTF-8" | ||
| response.writer.write(objectMapper.writeValueAsString(errorResponse)) | ||
| } catch (e: IOException) { | ||
| log.error("Failed to write error response", e) | ||
| } | ||
| } |
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.
🛠️ Refactor suggestion
코드 중복 제거 및 공통 유틸리티 클래스 도입을 고려하세요.
sendErrorResponse 메서드가 CustomAccessDeniedHandler의 동일한 메서드와 완전히 중복됩니다. 이는 DRY 원칙에 위배됩니다.
공통 유틸리티 클래스를 생성하여 중복을 제거하는 것을 제안합니다:
+class SecurityErrorResponseUtil(
+ private val objectMapper: ObjectMapper
+) {
+ private val log = LoggerFactory.getLogger(this::class.java)
+
+ fun sendErrorResponse(response: HttpServletResponse, errorCode: CommonErrorCode) {
+ val errorResponse = ErrorResponse.builder()
+ .status(errorCode.getHttpStatus().value())
+ .code(errorCode.getCode())
+ .message(errorCode.getMessage())
+ .build()
+
+ try {
+ response.status = errorCode.getHttpStatus().value()
+ response.contentType = MediaType.APPLICATION_JSON_VALUE
+ response.characterEncoding = "UTF-8"
+ response.writer.write(objectMapper.writeValueAsString(errorResponse))
+ } catch (e: IOException) {
+ log.error("Failed to write error response", e)
+ }
+ }
+}그리고 두 핸들러에서 이 유틸리티를 사용하도록 수정:
- private fun sendErrorResponse(response: HttpServletResponse) {
- val errorCode = CommonErrorCode.UNAUTHORIZED
- // ... 기존 코드
- }
+ private fun sendErrorResponse(response: HttpServletResponse) {
+ securityErrorResponseUtil.sendErrorResponse(response, CommonErrorCode.UNAUTHORIZED)
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
gateway/src/main/kotlin/org/yapp/gateway/security/CustomAuthenticationEntryPoint.kt
lines 29 to 46, the sendErrorResponse method duplicates identical code found in
CustomAccessDeniedHandler. To fix this, create a common utility class that
contains the sendErrorResponse logic and refactor both handlers to call this
shared utility method instead of having their own copies. This will remove code
duplication and adhere to the DRY principle.
| } | ||
|
|
||
| private fun sendErrorResponse(response: HttpServletResponse) { | ||
| val errorCode = CommonErrorCode.UNAUTHORIZED |
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.
🧹 Nitpick (assertive)
인증 실패 유형별 세분화된 에러 코드 사용을 고려하세요.
모든 인증 실패에 대해 UNAUTHORIZED 에러 코드만 사용하는 것은 클라이언트가 실패 원인을 정확히 파악하기 어렵게 만듭니다.
인증 예외 유형에 따라 더 구체적인 에러 코드를 사용하는 것을 고려하세요:
- private fun sendErrorResponse(response: HttpServletResponse) {
- val errorCode = CommonErrorCode.UNAUTHORIZED
+ private fun sendErrorResponse(response: HttpServletResponse, authException: AuthenticationException) {
+ val errorCode = when (authException) {
+ is BadCredentialsException -> CommonErrorCode.INVALID_CREDENTIALS
+ is AccountExpiredException -> CommonErrorCode.ACCOUNT_EXPIRED
+ is DisabledException -> CommonErrorCode.ACCOUNT_DISABLED
+ else -> CommonErrorCode.UNAUTHORIZED
+ }🤖 Prompt for AI Agents
In
gateway/src/main/kotlin/org/yapp/gateway/security/CustomAuthenticationEntryPoint.kt
at line 30, the code uses a single generic UNAUTHORIZED error code for all
authentication failures. To improve client-side error handling, modify the code
to distinguish between different authentication exception types and assign more
specific error codes accordingly. Implement logic to check the exception type
and set the errorCode variable to a corresponding detailed error code instead of
always using UNAUTHORIZED.
| private fun sendErrorResponse(response: HttpServletResponse) { | ||
| val errorCode = CommonErrorCode.UNAUTHORIZED | ||
|
|
||
| val errorResponse = ErrorResponse.builder() | ||
| .status(errorCode.getHttpStatus().value()) | ||
| .code(errorCode.getCode()) | ||
| .message(errorCode.getMessage()) | ||
| .build() | ||
|
|
||
| try { | ||
| response.status = errorCode.getHttpStatus().value() | ||
| response.contentType = MediaType.APPLICATION_JSON_VALUE | ||
| response.characterEncoding = "UTF-8" | ||
| response.writer.write(objectMapper.writeValueAsString(errorResponse)) | ||
| } catch (e: IOException) { | ||
| log.error("Failed to write error response", e) | ||
| } | ||
| } |
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.
🛠️ Refactor suggestion
코드 중복 문제를 해결해주세요.
sendErrorResponse 메서드가 CustomAuthenticationEntryPoint의 동일한 메서드와 거의 동일한 구조입니다. 이는 DRY 원칙을 위반하며 유지보수 시 문제가 될 수 있습니다.
공통 에러 응답 로직을 별도의 유틸리티 클래스로 추출하는 것을 고려해보세요:
@Component
class SecurityErrorResponseWriter(
private val objectMapper: ObjectMapper
) {
fun writeErrorResponse(response: HttpServletResponse, errorCode: CommonErrorCode) {
val errorResponse = ErrorResponse.builder()
.status(errorCode.getHttpStatus().value())
.code(errorCode.getCode())
.message(errorCode.getMessage())
.build()
try {
response.status = errorCode.getHttpStatus().value()
response.contentType = MediaType.APPLICATION_JSON_VALUE
response.characterEncoding = "UTF-8"
response.writer.write(objectMapper.writeValueAsString(errorResponse))
} catch (e: IOException) {
log.error("Failed to write error response", e)
}
}
}그리고 두 핸들러 클래스에서 이 유틸리티를 사용하도록 리팩토링하세요.
🤖 Prompt for AI Agents
In
gateway/src/main/kotlin/org/yapp/gateway/security/CustomAccessDeniedHandler.kt
around lines 29 to 46, the sendErrorResponse method duplicates code found in
CustomAuthenticationEntryPoint, violating the DRY principle. To fix this,
extract the common error response writing logic into a new utility class, e.g.,
SecurityErrorResponseWriter, which takes an HttpServletResponse and
CommonErrorCode to build and write the error response. Then refactor both
CustomAccessDeniedHandler and CustomAuthenticationEntryPoint to use this utility
class instead of having their own sendErrorResponse implementations.
| .authorizeHttpRequests { | ||
| it.requestMatchers( | ||
| "/api/v1/auth/refresh", | ||
| "/api/v1/auth/signin", | ||
| "/api/v1/health", | ||
| "/actuator/**", | ||
| "/swagger-ui/**", | ||
| "/v3/api-docs/**", | ||
| "/kakao-login.html/**", | ||
| "/api/v1/books/search", | ||
| "/api/v1/books/detail" | ||
| ).permitAll() | ||
| it.requestMatchers("/api/v1/admin/**").hasRole("ADMIN") | ||
| it.requestMatchers("/api/v1/user/**").hasAnyRole("USER", "ADMIN") | ||
| it.requestMatchers("/api/v1/auth/**").authenticated() | ||
| it.requestMatchers("/api/v1/books/**").authenticated() | ||
| it.anyRequest().authenticated() | ||
| } |
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.
🧹 Nitpick (assertive)
권한 부여 규칙이 논리적으로 잘 구성되었습니다.
퍼블릭 엔드포인트, 역할 기반 엔드포인트, 인증 필요 엔드포인트가 적절히 분류되어 있습니다.
유지보수성 향상을 위해 엔드포인트 경로를 상수로 분리하는 것을 고려해보세요:
companion object {
private val PUBLIC_ENDPOINTS = arrayOf(
"/api/v1/auth/refresh",
"/api/v1/auth/signin",
"/api/v1/health",
"/actuator/**",
"/swagger-ui/**",
"/v3/api-docs/**",
"/kakao-login.html/**",
"/api/v1/books/search",
"/api/v1/books/detail"
)
private const val ADMIN_ENDPOINTS = "/api/v1/admin/**"
private const val USER_ENDPOINTS = "/api/v1/user/**"
}🤖 Prompt for AI Agents
In gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt around
lines 33 to 50, the endpoint paths used in the authorization rules should be
extracted into constants for better maintainability. Define companion object
constants for public endpoints as an array and for admin and user endpoints as
string constants. Then replace the hardcoded strings in the requestMatchers
calls with these constants to centralize endpoint path management.
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.
@hoonyworld
오 저는 이 방식 되게 괜찮다고 생각했어요!
companion object 안에 PUBLIC_ENDPOINTS, ADMIN_ENDPOINTS, USER_ENDPOINTS처럼
URL 권한 구분을 상수로 깔끔하게 모아두니까
전체 보안 흐름을 한눈에 파악할 수 있고,
나중에 엔드포인트가 추가되거나 바뀌더라도 한 곳에서만 수정하면 되는 점이 마음에 들더라고요.
보통 이런 패턴들이 SecurityConfig 쪽에서 막 섞여 있으면
"어디가 공개지?", "이건 왜 인증 필요하지?" 하면서
일일이 찾아봐야 되잖아요. 근데 이 방식은 보기에도 직관적이고, 유지보수하기도 좋다는 생각이 들었어요.
동훈님은 어떻게 생각하시나요!?
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.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
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.
@minwoo1999
저도 동의하는 바입니다 ㅎㅎ
사실 전 프로젝트에서는 whitelist endpoint를 String[] 배열에 넣어서 관리를 해서 넘겼었는데 관리하기 편하더라고요!!
적용 바로 해놓겠습니다~~
minwoo1999
left a comment
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.
리팩토링 잘 해주셔서 감사합니다.
몇가지 코멘트 남겼는데, 동훈님의 생각과 의견 남겨주세요!
고생하셨습니다.
| @Value("\${jwt.refresh-token-expiration}") | ||
| private val refreshTokenExpiration: Long | ||
| ) { | ||
|
|
||
| companion object { | ||
| private const val TOKEN_TYPE_CLAIM = "type" | ||
| private const val ROLES_CLAIM = "roles" | ||
| private const val ACCESS_TOKEN_TYPE = "access" | ||
| private const val REFRESH_TOKEN_TYPE = "refresh" | ||
| private const val DEFAULT_ROLE = "ROLE_USER" | ||
| private const val MILLISECONDS_PER_SECOND = 1000L | ||
| } | ||
|
|
||
| private val key = Keys.hmacShaKeyFor(secretKey.toByteArray()) | ||
|
|
||
| fun generateAccessToken(userId: UUID): String { | ||
| return generateToken(userId, accessTokenExpiration, ACCESS_TOKEN_TYPE) | ||
| fun generateAccessToken(userId: UUID, roles: List<Role>): String { | ||
| val roleStrings = roles.map { it.key } | ||
| return generateToken(userId, accessTokenExpiration, ACCESS_TOKEN_TYPE, roleStrings) | ||
| } | ||
|
|
||
| fun generateRefreshToken(userId: UUID): String { | ||
| return generateToken(userId, refreshTokenExpiration, REFRESH_TOKEN_TYPE) | ||
| } | ||
|
|
||
| fun validateToken(token: String): Boolean { | ||
| return try { | ||
| parseToken(token) | ||
| true | ||
| } catch (e: JwtException) { | ||
| false | ||
| } | ||
| return generateToken(userId, refreshTokenExpiration, REFRESH_TOKEN_TYPE, emptyList()) | ||
| } | ||
|
|
||
| fun validateTokenAndThrow(token: String) { | ||
| parseToken(token) // 예외는 내부에서 throw됨 | ||
| } | ||
|
|
||
| fun getUserIdFromToken(token: String): UUID { | ||
| val claims = parseToken(token) | ||
| return try { | ||
| claims.subject.let { UUID.fromString(it) } | ||
| } catch (e: NumberFormatException) { | ||
| throw CustomJwtException( | ||
| JwtErrorCode.INVALID_JWT_TOKEN, | ||
| "Invalid user ID in token" | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| fun getTokenType(token: String): String { | ||
| val claims = parseToken(token) | ||
| return when (val type = claims[TOKEN_TYPE_CLAIM]) { | ||
| is String -> type | ||
| null -> throw CustomJwtException( | ||
| JwtErrorCode.INVALID_JWT_TOKEN, | ||
| "Token type claim is missing" | ||
| ) | ||
| else -> throw CustomJwtException( | ||
| JwtErrorCode.INVALID_JWT_TOKEN, | ||
| "Token type claim has invalid type: ${type::class.simpleName}" | ||
| ) | ||
| } | ||
| fun getRefreshTokenExpiration(): Long { | ||
| return refreshTokenExpiration |
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.
@hoonyworld
지금 저희 프로젝트에서 jjwt (jjwt-api, jjwt-impl, jjwt-jackson) 관련 의존성을 따로 쓰고 있는데요,
이미 Spring Security의 OAuth2 Authorization Server를 사용 중이라면,
굳이 별도의 JWT 생성 라이브러리를 쓰기보다는 OAuth2Server에서 제공하는 JwtEncoder, JwtDecoder를 활용하는 방식으로 통일하는 게 좋을 것 같아요.
이유는 크게 세 가지예요:
- 의존성 최소화 / 충돌 방지
jjwt를 따로 두면 jackson, bouncycastle 같은 내부 의존성까지 딸려오기 때문에,
종속성 충돌이나 이슈가 생길 여지가 생깁니다.
2.JWT 발급/검증 일관성 확보
JwtEncoder, JwtDecoder를 통해 Spring Security Filter에서 쓰는 검증 로직과 완전히 동일한 방식으로 토큰 발급이 가능하다는 장점이 있어요.
별도 라이브러리를 쓰면 Header/Claim 구조나 서명 방식 차이로 인해 인증 필터에서 문제 생기는 경우가 종종 있어서, 일관되게 가져가는 게 훨씬 안정적입니다.
3.코드 간결화
jjwt로 JWT 만들 때는 builder 패턴으로 header, claim, signer 등 직접 다 구성해야 하지만,
JwtEncoder는 아래처럼 깔끔하게 처리 가능하니까 유지보수도 쉬워집니다.
예시코드
`@Service
public class JwtService {
private final JwtEncoder jwtEncoder;
private final String keyId;
private final Long accessTokenExpiration;
private final Long refreshTokenExpiration;
public JwtService(
JwtEncoder jwtEncoder,
@Value("${jwt.key-id}") String keyId,
@Value("${jwt.access-token}") Long accessTokenExpiration,
@Value("${jwt.refresh-token.expiration}") Long refreshTokenExpiration
) {
this.jwtEncoder = jwtEncoder;
this.keyId = keyId;
this.accessTokenExpiration = accessTokenExpiration;
this.refreshTokenExpiration = refreshTokenExpiration;
}
public String generateAccessToken(User user) {
Map<String, Object> claims = createAccessTokenClaims(user);
return encodeToken(claims, user.getUsername(), accessTokenExpiration);
}
public String generateRefreshToken(User user) {
Map<String, Object> claims = Map.of("tokenType", "refresh");
return encodeToken(claims, user.getUsername(), refreshTokenExpiration);
}
private Map<String, Object> createAccessTokenClaims(User user) {
return Map.of("roles", List.of(user.getRole().name()));
}
private String encodeToken(Map<String, Object> claims, String subject, long expirationMs) {
Instant now = Instant.now();
JwtClaimsSet claimsSet = JwtClaimsSet.builder()
.issuer("self")
.issuedAt(now)
.expiresAt(now.plusMillis(expirationMs))
.subject(subject)
.claims(existingClaims -> existingClaims.putAll(claims))
.build();
JwsHeader header = JwsHeader.with(() -> "HS512")
.keyId(keyId)
.build();
return jwtEncoder.encode(JwtEncoderParameters.from(header, claimsSet)).getTokenValue();
}
}
`
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.
저도 동의합니다!
현재 저희 프로젝트에서는 jjwt(jjwt-api, jjwt-impl, jjwt-jackson)를 별도로 사용 중이지만, 이미 Spring Security OAuth2 Authorization Server를 도입하도록 리팩토링을 한 상황에서 JWT 관련 처리도 Spring Security에서 제공하는 JwtEncoder, JwtDecoder를 사용하는 방식으로 통일하는 것이 더 적절하다고 생각합니다.
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.
Actionable comments posted: 2
♻️ Duplicate comments (1)
gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt (1)
47-54: 권한 부여 규칙이 논리적으로 잘 구성되었습니다.퍼블릭 엔드포인트, 역할 기반 엔드포인트, 인증 필요 엔드포인트가 적절히 분류되어 있습니다.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt(1 hunks)
🧰 Additional context used
🪛 detekt (1.23.8)
gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt
[warning] 48-48: In most cases using a spread operator causes a full copy of the array to be created before calling a method. This may result in a performance penalty.
(detekt.performance.SpreadOperator)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-validation
🔇 Additional comments (3)
gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt (3)
1-11: 임포트 구성이 적절합니다.Spring Security OAuth2 리소스 서버를 사용한 JWT 인증 구성에 필요한 임포트가 모두 포함되어 있습니다.
12-20: 생성자 주입을 통한 의존성 관리가 잘 구성되었습니다.
JwtAuthenticationConverter와ObjectMapper를 생성자 주입으로 받아 사용하는 것이 Spring의 권장사항에 부합합니다.
35-46: OAuth2 리소스 서버 구성이 올바릅니다.Spring Security의 OAuth2 리소스 서버 지원을 활용하여 JWT 인증을 구성한 것이 좋습니다. 커스텀
JwtAuthenticationConverter를 사용하여 JWT 토큰을 인증 객체로 변환하는 방식이 적절합니다.CSRF, 폼 로그인, HTTP Basic 인증을 비활성화하고 세션을 stateless로 설정한 것도 API 서버에 적합합니다.
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.
Actionable comments posted: 3
♻️ Duplicate comments (3)
gateway/src/main/kotlin/org/yapp/gateway/security/CustomAccessDeniedHandler.kt (1)
11-26: 이전 코드 중복 문제가 해결되었습니다.
SecurityErrorResponseWriter를 활용하여 이전 리뷰에서 지적된 코드 중복 문제가 잘 해결되었습니다. 구현의 장점:
- Spring Security의
AccessDeniedHandler인터페이스 적절한 구현- 공통 에러 응답 로직 재사용으로 DRY 원칙 준수
- 적절한 로깅 레벨(warn)과 요청 URI 정보 포함
- 명확하고 집중된 책임 분담
gateway/src/main/kotlin/org/yapp/gateway/security/CustomAuthenticationEntryPoint.kt (1)
23-23: 보안 로깅 개선이 필요합니다.인증 예외 정보 전체를 로깅하는 것은 보안상 민감한 정보가 노출될 수 있습니다.
예외 메시지만 로깅하거나, 더 안전한 로깅 방식을 사용하세요:
- log.warn("Not Authenticated Request - uri: ${request.requestURI}", authException) + log.warn("Not Authenticated Request - uri: ${request.requestURI}, reason: ${authException.message}")gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt (1)
49-49: 스프레드 연산자 사용 시 성능 최적화를 고려하세요.스프레드 연산자 사용 시 배열이 복사되어 성능에 영향을 줄 수 있습니다.
List를 사용하여 성능을 개선할 수 있습니다:
companion object { private val WHITELIST_URLS = listOf( "/api/v1/auth/refresh", "/api/v1/auth/signin", "/api/v1/health", "/actuator/**", "/swagger-ui/**", "/v3/api-docs/**", "/kakao-login.html/**", "/api/v1/books/search", "/api/v1/books/detail" ) } // 사용 시 it.requestMatchers(*WHITELIST_URLS.toTypedArray()).permitAll()
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (10)
apis/build.gradle.kts(0 hunks)buildSrc/src/main/kotlin/Dependencies.kt(1 hunks)gateway/build.gradle.kts(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/config/JwtConfig.kt(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/constants/JwtConstants.kt(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenProvider.kt(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/security/CustomAccessDeniedHandler.kt(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/security/CustomAuthenticationEntryPoint.kt(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/security/SecurityErrorResponseWriter.kt(1 hunks)
💤 Files with no reviewable changes (1)
- apis/build.gradle.kts
🧰 Additional context used
🪛 detekt (1.23.8)
gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt
[warning] 49-49: In most cases using a spread operator causes a full copy of the array to be created before calling a method. This may result in a performance penalty.
(detekt.performance.SpreadOperator)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-validation
🔇 Additional comments (8)
gateway/build.gradle.kts (1)
7-7: 의존성 변경이 적절합니다.기존의 수동 JWT 라이브러리 의존성을 Spring Boot OAuth2 resource server starter로 교체한 것은 올바른 접근입니다. 이는 Spring Security의 내장 JWT 지원을 활용하여 더 안전하고 유지보수가 용이한 구조를 제공합니다.
gateway/src/main/kotlin/org/yapp/gateway/constants/JwtConstants.kt (1)
3-6: JWT 상수 정의가 적절합니다.JWT 처리에 필요한 상수들이 명확하게 정의되어 있습니다. 특히
ROLES_CLAIM과ISSUER상수는 JWT 토큰의 일관된 처리를 위해 중요한 역할을 합니다.buildSrc/src/main/kotlin/Dependencies.kt (1)
11-11: 의존성 상수 추가가 적절합니다.OAuth2 resource server starter 의존성 상수가 기존 패턴과 일관성 있게 추가되었습니다. 이는 프로젝트 전반의 의존성 관리를 중앙화하는 좋은 접근 방식입니다.
gateway/src/main/kotlin/org/yapp/gateway/security/SecurityErrorResponseWriter.kt (1)
12-34: 코드 중복 문제가 잘 해결되었습니다.이전 리뷰에서 제안했던 공통 에러 응답 로직 추출이 훌륭하게 구현되었습니다. 주요 장점:
- Jackson ObjectMapper를 활용한 일관된 JSON 응답 생성
- 적절한 HTTP 상태 코드와 Content-Type 설정
- IOException에 대한 적절한 예외 처리 및 로깅
- Spring Component로 관리되어 의존성 주입 활용
gateway/src/main/kotlin/org/yapp/gateway/config/JwtConfig.kt (2)
47-50: JWT 인코더 설정이 적절합니다.Spring Security의 표준 패턴을 잘 따르고 있습니다.
73-82: JWT 인증 변환기가 잘 구현되었습니다.역할 클레임 처리와 null 안전성이 적절히 고려되었습니다.
gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenProvider.kt (2)
29-36: 액세스 토큰 생성 로직이 잘 구현되었습니다.Spring Security의 JwtEncoder를 활용하여 깔끔하게 구현되었고, 역할 정보도 적절히 포함됩니다.
47-62: 토큰 생성 구현이 우수합니다.Spring Security의 JWT 표준을 잘 따르고 있으며, 클레임 설정과 헤더 구성이 적절합니다.
| */ | ||
| @Bean | ||
| fun jwtDecoder(): JwtDecoder { | ||
| val secretKeySpec = SecretKeySpec(secretKey.toByteArray(), SIGNATURE_ALGORITHM.name) |
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.
일관된 문자 인코딩을 사용하세요.
JWT 인코딩과 디코딩에서 동일한 인코딩을 사용해야 합니다.
UTF-8 인코딩을 명시적으로 지정하세요:
- val secretKeySpec = SecretKeySpec(secretKey.toByteArray(), SIGNATURE_ALGORITHM.name)
+ val secretKeySpec = SecretKeySpec(secretKey.toByteArray(Charsets.UTF_8), SIGNATURE_ALGORITHM.name)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| val secretKeySpec = SecretKeySpec(secretKey.toByteArray(), SIGNATURE_ALGORITHM.name) | |
| val secretKeySpec = SecretKeySpec(secretKey.toByteArray(Charsets.UTF_8), SIGNATURE_ALGORITHM.name) |
🤖 Prompt for AI Agents
In gateway/src/main/kotlin/org/yapp/gateway/config/JwtConfig.kt at line 60, the
secretKey is converted to a byte array without specifying the character
encoding, which can cause inconsistencies. Fix this by explicitly specifying
UTF-8 encoding when converting the secretKey string to a byte array to ensure
consistent JWT encoding and decoding.
| */ | ||
| @Bean | ||
| fun jwkSource(): JWKSource<SecurityContext> { | ||
| val jwk: OctetSequenceKey = OctetSequenceKey.Builder(secretKey.toByteArray()).build() |
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.
명시적 문자 인코딩을 사용하세요.
toByteArray()는 플랫폼 기본 인코딩을 사용하므로 환경에 따라 다른 결과를 생성할 수 있습니다.
명시적으로 UTF-8 인코딩을 지정하세요:
- val jwk: OctetSequenceKey = OctetSequenceKey.Builder(secretKey.toByteArray()).build()
+ val jwk: OctetSequenceKey = OctetSequenceKey.Builder(secretKey.toByteArray(Charsets.UTF_8)).build()🤖 Prompt for AI Agents
In gateway/src/main/kotlin/org/yapp/gateway/config/JwtConfig.kt at line 36, the
call to toByteArray() uses the platform's default character encoding, which can
lead to inconsistent behavior across environments. Modify the code to explicitly
specify UTF-8 encoding when converting the secretKey string to a byte array to
ensure consistent and predictable results.
minwoo1999
left a comment
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.
GOOD 고생하셨습니다
| "/api/v1/books/search", | ||
| "/api/v1/books/detail" |
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.
기획 정책 상 search,detail은 로그인해야지만 이용할 수 있어서 화이트리스트에서 제외하면 좋을 것 같습니다.
| private val WHITELIST_URLS = arrayOf( | ||
| "/api/v1/auth/refresh", | ||
| "/api/v1/auth/signin", | ||
| "/api/v1/health", |
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.
회의결과 - 이부분도 actuator에서 사용하는 health check로 변경하는 방향으로
|
move-hoon
left a comment
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.
LGTM~


🔗 관련 이슈
Close #35
📘 작업 유형
📙 작업 내역
🧪 테스트 내역
브라우저/기기에서 동작 확인
유효한 토큰으로 API 요청 시 정상적으로 인증 처리되는 것을 확인했습니다.
엣지 케이스 테스트 완료
만료된 토큰, 잘못된 서명을 가진 토큰 등 비정상적인 토큰으로 요청 시, JwtErrorCode에 정의된 내용과 일치하는 오류 응답이 반환되는 것을 확인했습니다.
기존 기능 영향 없음
🎨 스크린샷 또는 시연 영상 (선택)

✅ PR 체크리스트
💬 추가 설명 or 리뷰 포인트 (선택)
Summary by CodeRabbit
신규 기능
버그 수정
리팩터링
기타