-
Notifications
You must be signed in to change notification settings - Fork 0
[refactor]: 배너 API를 Admin에서 Banner 도메인으로 이동한다 #310
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배너 관리 기능을 Admin 도메인에서 Banner 도메인으로 재구조화합니다. AdminController와 AdminService에서 배너 관련 엔드포인트와 메서드를 제거하고, BannerController와 신규 BannerService로 이동합니다. 배너 요청/응답 DTO의 패키지도 재배치됩니다. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. 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.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/main/java/side/onetime/dto/banner/response/GetAllBarBannersResponse.java (1)
1-19: 패키지 이동이 적절하게 완료되었습니다.
side.onetime.dto.banner.response패키지로의 이동이 잘 수행되었습니다.
PageInfo가side.onetime.dto.admin.response패키지에 남아있어 cross-package 의존성이 발생합니다. 향후PageInfo를side.onetime.dto.common같은 공통 패키지로 이동하는 것을 고려해 볼 수 있습니다.src/main/java/side/onetime/controller/BannerController.java (1)
21-24: @RequestMapping 경로 구조 확인 필요.현재 클래스 레벨에서
/api/v1을 사용하고 개별 메서드에서/banners/**,/bar-banners/**를 정의하고 있습니다. 이는 기능적으로는 문제없지만, 다른 컨트롤러들(AdminController는/api/v1/admin,TokenController는/api/v1/tokens)과 일관성이 부족합니다.하나의 컨트롤러에서 두 가지 리소스 타입(banners, bar-banners)을 모두 관리하는 현재 구조가 의도된 설계라면 문제없습니다. 다만 향후 각각을 별도 컨트롤러로 분리하는 것도 고려해볼 수 있습니다.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
src/main/java/side/onetime/controller/AdminController.javasrc/main/java/side/onetime/controller/BannerController.javasrc/main/java/side/onetime/dto/banner/request/RegisterBannerRequest.javasrc/main/java/side/onetime/dto/banner/request/RegisterBarBannerRequest.javasrc/main/java/side/onetime/dto/banner/request/UpdateBannerRequest.javasrc/main/java/side/onetime/dto/banner/request/UpdateBarBannerRequest.javasrc/main/java/side/onetime/dto/banner/response/GetAllActivatedBannersResponse.javasrc/main/java/side/onetime/dto/banner/response/GetAllActivatedBarBannersResponse.javasrc/main/java/side/onetime/dto/banner/response/GetAllBannersResponse.javasrc/main/java/side/onetime/dto/banner/response/GetAllBarBannersResponse.javasrc/main/java/side/onetime/dto/banner/response/GetBannerResponse.javasrc/main/java/side/onetime/dto/banner/response/GetBarBannerResponse.javasrc/main/java/side/onetime/global/config/SecurityConfig.javasrc/main/java/side/onetime/global/filter/JwtFilter.javasrc/main/java/side/onetime/service/AdminService.javasrc/main/java/side/onetime/service/BannerService.javasrc/test/java/side/onetime/admin/AdminControllerTest.javasrc/test/java/side/onetime/banner/BannerControllerTest.java
🧰 Additional context used
📓 Path-based instructions (7)
src/main/java/**/dto/**/*.java
📄 CodeRabbit inference engine (CLAUDE.md)
src/main/java/**/dto/**/*.java: Create DTOs organized by feature in dto directory with request and response subdirectories
Use toEntity() methods and static factory of() methods for DTO to entity conversion
Files:
src/main/java/side/onetime/dto/banner/response/GetAllBannersResponse.javasrc/main/java/side/onetime/dto/banner/request/UpdateBarBannerRequest.javasrc/main/java/side/onetime/dto/banner/response/GetBannerResponse.javasrc/main/java/side/onetime/dto/banner/request/RegisterBarBannerRequest.javasrc/main/java/side/onetime/dto/banner/response/GetBarBannerResponse.javasrc/main/java/side/onetime/dto/banner/response/GetAllActivatedBarBannersResponse.javasrc/main/java/side/onetime/dto/banner/response/GetAllBarBannersResponse.javasrc/main/java/side/onetime/dto/banner/request/UpdateBannerRequest.javasrc/main/java/side/onetime/dto/banner/response/GetAllActivatedBannersResponse.javasrc/main/java/side/onetime/dto/banner/request/RegisterBannerRequest.java
src/main/java/**/dto/**/response/*Response.java
📄 CodeRabbit inference engine (CLAUDE.md)
Use *Response suffixes for DTO class naming
Files:
src/main/java/side/onetime/dto/banner/response/GetAllBannersResponse.javasrc/main/java/side/onetime/dto/banner/response/GetBannerResponse.javasrc/main/java/side/onetime/dto/banner/response/GetBarBannerResponse.javasrc/main/java/side/onetime/dto/banner/response/GetAllActivatedBarBannersResponse.javasrc/main/java/side/onetime/dto/banner/response/GetAllBarBannersResponse.javasrc/main/java/side/onetime/dto/banner/response/GetAllActivatedBannersResponse.java
src/test/java/**/*Test.java
📄 CodeRabbit inference engine (CLAUDE.md)
src/test/java/**/*Test.java: Use Spring REST Docs and SpringDoc OpenAPI for API documentation generation
Use JUnit 5 with Spring Boot Test for unit and integration testing
Files:
src/test/java/side/onetime/banner/BannerControllerTest.javasrc/test/java/side/onetime/admin/AdminControllerTest.java
src/main/java/**/dto/**/request/*Request.java
📄 CodeRabbit inference engine (CLAUDE.md)
Use *Request and *Response suffixes for DTO class naming
Files:
src/main/java/side/onetime/dto/banner/request/UpdateBarBannerRequest.javasrc/main/java/side/onetime/dto/banner/request/RegisterBarBannerRequest.javasrc/main/java/side/onetime/dto/banner/request/UpdateBannerRequest.javasrc/main/java/side/onetime/dto/banner/request/RegisterBannerRequest.java
src/main/java/**/service/*Service.java
📄 CodeRabbit inference engine (CLAUDE.md)
src/main/java/**/service/*Service.java: Use @service annotation for business logic layer classes
Use @distributedlock annotation for race condition prevention in service methods
Use @transactional annotation for transaction management
Files:
src/main/java/side/onetime/service/BannerService.javasrc/main/java/side/onetime/service/AdminService.java
src/main/java/**/{controller,service}/*.java
📄 CodeRabbit inference engine (CLAUDE.md)
Use constructor injection with @requiredargsconstructor for dependency injection
Files:
src/main/java/side/onetime/service/BannerService.javasrc/main/java/side/onetime/service/AdminService.javasrc/main/java/side/onetime/controller/BannerController.javasrc/main/java/side/onetime/controller/AdminController.java
src/main/java/**/controller/*Controller.java
📄 CodeRabbit inference engine (CLAUDE.md)
src/main/java/**/controller/*Controller.java: Use @RestController annotation for REST API endpoints
Use generic response wrapper ApiResponse with onSuccess() and onFailure() methods for all API responses
Use RESTful API endpoints with /api/v1/ prefix
Files:
src/main/java/side/onetime/controller/BannerController.javasrc/main/java/side/onetime/controller/AdminController.java
🧠 Learnings (12)
📓 Common learnings
Learnt from: anxi01
Repo: onetime-with-members/backend PR: 269
File: src/main/java/side/onetime/dto/admin/response/GetBannerResponse.java:12-16
Timestamp: 2025-08-30T09:03:01.457Z
Learning: BarBanner와 Banner는 완전히 별개의 엔티티입니다. Banner는 organization, title, subTitle, colorCode, imageUrl 필드를 사용하고, BarBanner는 contentKor, contentEng, backgroundColorCode, textColorCode 필드를 사용합니다. 각각 다른 목적과 스키마를 가진 독립적인 엔티티로 설계되었습니다.
Learnt from: bbbang105
Repo: onetime-with-members/backend PR: 269
File: src/main/java/side/onetime/util/S3Util.java:63-73
Timestamp: 2025-08-29T11:40:58.895Z
Learning: 배너 이미지 저장 시 /banner/{banner_id}/ 경로 구조를 사용하여 파일을 더 체계적으로 관리하는 것을 선호합니다.
📚 Learning: 2025-12-21T11:41:05.367Z
Learnt from: CR
Repo: onetime-with-members/backend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-21T11:41:05.367Z
Learning: Applies to src/test/java/**/controller/*ControllerTest.java : Use MockMvc for controller integration tests
Applied to files:
src/test/java/side/onetime/banner/BannerControllerTest.javasrc/test/java/side/onetime/admin/AdminControllerTest.java
📚 Learning: 2025-12-21T11:41:05.367Z
Learnt from: CR
Repo: onetime-with-members/backend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-21T11:41:05.367Z
Learning: Applies to src/test/java/**/*Test.java : Use Spring REST Docs and SpringDoc OpenAPI for API documentation generation
Applied to files:
src/test/java/side/onetime/banner/BannerControllerTest.javasrc/test/java/side/onetime/admin/AdminControllerTest.java
📚 Learning: 2025-08-29T11:40:00.748Z
Learnt from: bbbang105
Repo: onetime-with-members/backend PR: 269
File: src/main/java/side/onetime/domain/BarBanner.java:76-78
Timestamp: 2025-08-29T11:40:00.748Z
Learning: BarBanner와 Banner 엔티티에서 소프트 삭제(markAsDeleted) 시에는 isDeleted=true와 함께 isActivated=false도 설정해야 합니다. 이는 삭제된 레코드가 복구될 때 의도치 않게 활성 상태로 남는 것을 방지하기 위함입니다.
Applied to files:
src/test/java/side/onetime/banner/BannerControllerTest.javasrc/test/java/side/onetime/admin/AdminControllerTest.java
📚 Learning: 2025-12-21T11:41:05.367Z
Learnt from: CR
Repo: onetime-with-members/backend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-21T11:41:05.367Z
Learning: Applies to src/main/java/**/dto/**/request/*Request.java : Use *Request and *Response suffixes for DTO class naming
Applied to files:
src/main/java/side/onetime/dto/banner/request/UpdateBarBannerRequest.javasrc/main/java/side/onetime/dto/banner/response/GetBannerResponse.javasrc/main/java/side/onetime/dto/banner/request/RegisterBarBannerRequest.javasrc/main/java/side/onetime/dto/banner/response/GetBarBannerResponse.javasrc/main/java/side/onetime/dto/banner/response/GetAllActivatedBarBannersResponse.javasrc/main/java/side/onetime/dto/banner/response/GetAllActivatedBannersResponse.javasrc/main/java/side/onetime/dto/banner/request/RegisterBannerRequest.java
📚 Learning: 2025-12-21T11:41:05.367Z
Learnt from: CR
Repo: onetime-with-members/backend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-21T11:41:05.367Z
Learning: Applies to src/main/java/**/dto/**/response/*Response.java : Use *Response suffixes for DTO class naming
Applied to files:
src/main/java/side/onetime/dto/banner/response/GetBannerResponse.javasrc/main/java/side/onetime/dto/banner/response/GetBarBannerResponse.javasrc/main/java/side/onetime/dto/banner/response/GetAllActivatedBarBannersResponse.javasrc/main/java/side/onetime/dto/banner/response/GetAllActivatedBannersResponse.java
📚 Learning: 2025-08-29T11:40:58.895Z
Learnt from: bbbang105
Repo: onetime-with-members/backend PR: 269
File: src/main/java/side/onetime/util/S3Util.java:63-73
Timestamp: 2025-08-29T11:40:58.895Z
Learning: 배너 이미지 저장 시 /banner/{banner_id}/ 경로 구조를 사용하여 파일을 더 체계적으로 관리하는 것을 선호합니다.
Applied to files:
src/main/java/side/onetime/service/BannerService.javasrc/main/java/side/onetime/controller/BannerController.java
📚 Learning: 2025-08-29T11:40:12.488Z
Learnt from: bbbang105
Repo: onetime-with-members/backend PR: 269
File: src/main/java/side/onetime/service/AdminService.java:280-288
Timestamp: 2025-08-29T11:40:12.488Z
Learning: 배너 등록/수정 시 S3 업로드와 DB 저장 순서는 DB 저장 먼저 → S3 업로드 → 실패 시 exception으로 롤백하는 방식이 더 간단하고 효과적임. TransactionSynchronization보다는 Transactional의 자동 롤백 메커니즘을 활용하는 것이 권장됨.
Applied to files:
src/main/java/side/onetime/service/BannerService.javasrc/main/java/side/onetime/service/AdminService.java
📚 Learning: 2025-12-21T11:41:05.367Z
Learnt from: CR
Repo: onetime-with-members/backend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-21T11:41:05.367Z
Learning: Applies to src/main/java/**/auth/*.java : Use Spring Security, OAuth2 (Google, Kakao, Naver), and JWT (JJWT) for authentication and authorization
Applied to files:
src/main/java/side/onetime/global/filter/JwtFilter.java
📚 Learning: 2025-12-21T11:41:05.367Z
Learnt from: CR
Repo: onetime-with-members/backend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-21T11:41:05.367Z
Learning: Applies to src/main/java/**/controller/*Controller.java : Use RESTful API endpoints with /api/v1/ prefix
Applied to files:
src/main/java/side/onetime/controller/BannerController.javasrc/main/java/side/onetime/controller/AdminController.java
📚 Learning: 2025-12-21T11:41:05.367Z
Learnt from: CR
Repo: onetime-with-members/backend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-21T11:41:05.367Z
Learning: Applies to src/main/java/**/controller/*Controller.java : Use RestController annotation for REST API endpoints
Applied to files:
src/main/java/side/onetime/controller/BannerController.javasrc/main/java/side/onetime/controller/AdminController.java
📚 Learning: 2025-12-21T11:41:05.367Z
Learnt from: CR
Repo: onetime-with-members/backend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-21T11:41:05.367Z
Learning: Applies to src/test/java/**/*Test.java : Use JUnit 5 with Spring Boot Test for unit and integration testing
Applied to files:
src/test/java/side/onetime/admin/AdminControllerTest.java
🧬 Code graph analysis (4)
src/main/java/side/onetime/dto/banner/response/GetAllBannersResponse.java (2)
src/main/java/side/onetime/dto/admin/response/GetAllBannersResponse.java (2)
JsonNaming(8-19)of(13-18)src/main/java/side/onetime/dto/admin/response/GetAllBarBannersResponse.java (2)
JsonNaming(8-19)of(13-18)
src/test/java/side/onetime/banner/BannerControllerTest.java (1)
src/test/java/side/onetime/admin/AdminControllerTest.java (1)
WebMvcTest(38-484)
src/main/java/side/onetime/global/config/SecurityConfig.java (1)
src/main/java/side/onetime/domain/BarBanner.java (1)
updateLinkUrl(72-74)
src/main/java/side/onetime/controller/BannerController.java (2)
src/main/java/side/onetime/global/config/SecurityConfig.java (1)
RequiredArgsConstructor(22-122)src/main/java/side/onetime/controller/TokenController.java (1)
RestController(16-36)
⏰ 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 and Push to ECR
🔇 Additional comments (27)
src/main/java/side/onetime/dto/banner/request/RegisterBannerRequest.java (1)
1-1: 패키지 이동이 올바르게 수행되었습니다.Admin 도메인에서 Banner 도메인으로 DTO가 깔끔하게 이동되었습니다. 코딩 가이드라인을 준수하고 있습니다.
src/main/java/side/onetime/dto/banner/request/UpdateBannerRequest.java (1)
1-1: 패키지 이동이 올바르게 수행되었습니다.DTO가 Banner 도메인으로 적절히 재배치되었습니다.
src/main/java/side/onetime/dto/banner/request/UpdateBarBannerRequest.java (1)
1-1: 패키지 이동이 올바르게 수행되었습니다.BarBanner DTO가 Banner 도메인으로 적절히 이동되었습니다.
src/main/java/side/onetime/dto/banner/response/GetAllActivatedBarBannersResponse.java (1)
1-1: 패키지 이동이 올바르게 수행되었습니다.Response DTO가 Banner 도메인으로 적절히 재배치되었으며, 코딩 가이드라인을 준수하고 있습니다.
src/main/java/side/onetime/dto/banner/response/GetBannerResponse.java (1)
1-1: 패키지 이동이 올바르게 수행되었습니다.Response DTO가 Banner 도메인으로 적절히 이동되었으며, static factory 메서드를 포함하고 있습니다.
src/main/java/side/onetime/global/filter/JwtFilter.java (1)
104-104: BarBanner 경로의 JWT 필터 제외를 확인해주세요.
/api/v1/bar-banners경로가 JWT 필터에서 제외되어 인증 검증을 우회하게 되었습니다. 이는 SecurityConfig의 변경사항과 일관되지만, BarBanner 엔드포인트가 실제로 인증 없이 접근 가능해야 하는지 확인이 필요합니다.src/main/java/side/onetime/dto/banner/response/GetBarBannerResponse.java (1)
1-1: 패키지 이동이 올바르게 수행되었습니다.GetBarBannerResponse가 Banner 도메인으로 적절히 이동되었으며, static factory 메서드를 포함하고 있습니다.
src/main/java/side/onetime/dto/banner/response/GetAllActivatedBannersResponse.java (1)
1-15: 패키지 이동이 적절하게 완료되었습니다.
side.onetime.dto.admin.response에서side.onetime.dto.banner.response로의 패키지 이동이 PR 목표에 맞게 잘 수행되었습니다. 코딩 가이드라인에 따른*Response네이밍 규칙과 static factory method 패턴도 잘 유지되어 있습니다.src/main/java/side/onetime/dto/banner/request/RegisterBarBannerRequest.java (1)
1-41: 패키지 이동 및 DTO 구현이 적절합니다.
side.onetime.dto.banner.request패키지로의 이동이 잘 수행되었습니다. 코딩 가이드라인에 따른*Request네이밍 규칙,toEntity()메서드, 그리고 적절한 validation 어노테이션이 잘 적용되어 있습니다.src/test/java/side/onetime/banner/BannerControllerTest.java (5)
41-52: 테스트 클래스 구성이 적절합니다.
@WebMvcTest와@MockBean을 사용한 컨트롤러 테스트 구성이 코딩 가이드라인에 맞게 잘 설정되어 있습니다.BannerService,JwtUtil,CustomUserDetailsService에 대한 목 의존성이 적절하게 주입되었습니다.
53-96: 배너 등록 테스트가 적절하게 구현되었습니다.Multipart 요청을 사용한 배너 등록 테스트가 잘 작성되어 있습니다.
MockMultipartFile을 통한 JSON request와 이미지 파일 처리가 적절합니다.
98-148: 띠배너 등록 테스트가 적절하게 구현되었습니다.JSON 요청 처리, request/response 필드 문서화, 스키마 참조가 잘 작성되어 있습니다.
393-488: 활성화된 배너/띠배너 조회 테스트가 적절합니다.공개 엔드포인트에 대한 테스트로서 Authorization 헤더 없이 요청하는 것이 적절합니다. 이는 SecurityConfig와 JwtFilter의 공개 접근 설정과 일치합니다.
666-699: 배너 클릭 수 증가 테스트가 적절합니다.path parameter 문서화와 응답 필드 검증이 잘 작성되어 있습니다.
src/main/java/side/onetime/dto/banner/response/GetAllBannersResponse.java (1)
1-20: 패키지 이동이 적절하게 완료되었습니다.
side.onetime.dto.banner.response패키지로의 이동이 PR 목표에 맞게 잘 수행되었습니다.src/test/java/side/onetime/admin/AdminControllerTest.java (2)
21-24: import 정리가 적절하게 수행되었습니다.배너 관련 기능이 BannerController로 이동함에 따라 import가 특정 admin request DTO로 정리되었습니다. 관심사 분리가 잘 이루어졌습니다.
38-483: AdminControllerTest가 적절하게 리팩토링되었습니다.배너 관련 테스트가
BannerControllerTest로 이동되어, 이 테스트 클래스는 이제 관리자 계정 관리(회원가입, 로그인, 프로필 조회, 권한 수정, 탈퇴)와 대시보드 기능에만 집중합니다. 도메인 분리가 잘 반영되었습니다.src/main/java/side/onetime/service/BannerService.java (5)
26-34: 서비스 클래스 구성이 코딩 가이드라인에 맞게 잘 설정되었습니다.
@Service,@RequiredArgsConstructor를 통한 생성자 주입,@Slf4j로깅이 적절하게 적용되었습니다.
46-53: 배너 등록 로직이 적절하게 구현되었습니다.DB 저장 후 S3 업로드 순서가 권장 패턴에 맞게 구현되었습니다. 업로드 실패 시 트랜잭션 롤백을 활용할 수 있습니다. Based on learnings, DB 저장 먼저 → S3 업로드 → 실패 시 롤백 방식이 적용되었습니다.
173-195: 활성화된 배너 조회 메서드가 공개 엔드포인트로 적절하게 구현되었습니다.
getAllActivatedBanners()와getAllActivatedBarBanners()에서 인증 검증(jwtUtil.getAdminUserFromHeader)을 수행하지 않는 것이 올바릅니다. 이는 SecurityConfig의 공개 접근 설정과 일치합니다.
312-319: 배너 이미지 업로드 경로가 적절합니다.
/banner/{banner_id}/경로 구조를 사용하여 파일을 체계적으로 관리하고 있습니다. Based on learnings, 이 경로 구조가 권장됩니다.
263-289: 소프트 삭제 구현이 올바르게 되어 있습니다.Banner와 BarBanner의
markAsDeleted()메서드를 확인한 결과, 두 엔티티 모두 소프트 삭제 시isDeleted=true와 함께isActivated=false를 정확히 설정하고 있습니다. 따라서 삭제된 레코드가 복구될 때 의도치 않게 활성 상태로 남는 것이 방지되어 있습니다.Likely an incorrect or invalid review comment.
src/main/java/side/onetime/service/AdminService.java (2)
11-13: import 정리가 적절하게 수행되었습니다.배너 관련 DTO import가 제거되고 관리자 관련 request DTO만 명시적으로 import되었습니다.
28-36: AdminService의 의존성이 적절하게 정리되었습니다.
BannerRepository,BarBannerRepository,S3Util의존성이 제거되어 AdminService가 관리자 계정 관리와 대시보드 기능에만 집중합니다. 단일 책임 원칙(SRP)에 더 부합하는 구조가 되었습니다.src/main/java/side/onetime/controller/AdminController.java (1)
10-12: 배너 관련 임포트 제거 및 구체적 임포트 사용 확인.배너 API가 Banner 도메인으로 이동됨에 따라 관련 임포트가 제거되었고, 와일드카드 대신 구체적인 admin request DTO들을 임포트하도록 변경되었습니다. 이는 더 명확한 의존성 관리를 위한 좋은 개선입니다.
src/main/java/side/onetime/controller/BannerController.java (2)
242-254: 배너 클릭 수 증가 API 구현 확인.인증 없이 접근 가능한 공개 API로 올바르게 설계되었습니다. 사용자가 배너를 클릭할 때 인증 없이 클릭 수를 증가시킬 수 있어야 하므로 적절한 구현입니다.
28-240: JWT 필터 우회로 인한 관리자 API 인증 부재.
JwtFilter의shouldNotFilter()메서드에서/api/v1/banners,/api/v1/bar-banners로 시작하는 모든 경로가 JWT 검증을 건너뜁니다(줄 95-96). 동시에SecurityConfig의PUBLIC_URLS도 이들 패턴을 포함합니다(줄 43-44). 결과적으로 배너 등록, 수정, 삭제와 같은 관리자 전용 API가 인증 없이 접근 가능합니다.메서드의
@RequestHeader("Authorization")파라미터는 단순 값 캡처일 뿐, Spring Security의 실제 인증 강제를 제공하지 않습니다.필수 수정 사항:
JwtFilter.shouldNotFilter()에서/api/v1/banners,/api/v1/bar-banners제거SecurityConfig.PUBLIC_URLS에서도 동일하게 제거- 공개 조회 엔드포인트(
/api/v1/banners/activated/all,/api/v1/bar-banners/activated/all)만 명시적으로 PUBLIC_URLS에 포함- 나머지 배너 API(
register,all,{id},update,delete)는 JWT 인증 필수⛔ Skipped due to learnings
Learnt from: CR Repo: onetime-with-members/backend PR: 0 File: CLAUDE.md:0-0 Timestamp: 2025-12-21T11:41:05.367Z Learning: Applies to src/main/java/**/auth/*.java : Use Spring Security, OAuth2 (Google, Kakao, Naver), and JWT (JJWT) for authentication and authorizationLearnt from: bbbang105 Repo: onetime-with-members/backend PR: 269 File: src/main/java/side/onetime/util/S3Util.java:63-73 Timestamp: 2025-08-29T11:40:58.895Z Learning: 배너 이미지 저장 시 /banner/{banner_id}/ 경로 구조를 사용하여 파일을 더 체계적으로 관리하는 것을 선호합니다.
| "/api/v1/tokens/**", | ||
| "/api/v1/admin/**", | ||
| "/api/v1/banners/**", | ||
| "/api/v1/bar-banners/**", |
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.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the SecurityConfig.java file to understand the context
cat -n src/main/java/side/onetime/global/config/SecurityConfig.java | head -60Repository: onetime-with-members/backend
Length of output: 2571
🏁 Script executed:
# Search for BarBanner controller endpoints
rg -n "BarBanner|bar-banner" --type java -i | head -30Repository: onetime-with-members/backend
Length of output: 4153
🏁 Script executed:
# Look for the controller class with GetMapping
rg -n "@GetMapping.*BarBanner|BarBannerController" --type java -A 3Repository: onetime-with-members/backend
Length of output: 54
🏁 Script executed:
# Check for BannerService or similar service classes
rg -n "class.*BannerService|BarBannerService" --type java -A 10Repository: onetime-with-members/backend
Length of output: 972
🏁 Script executed:
# Look at the entire PUBLIC_URLS section to understand the pattern
rg -n "PUBLIC_URLS|getPublicUrls" --type java -B 3 -A 15Repository: onetime-with-members/backend
Length of output: 3283
🏁 Script executed:
# Find the controller class that handles bar-banners endpoints
rg -n "BarBanner.*Controller|bar-banner.*Controller" --type java -B 2 -A 15Repository: onetime-with-members/backend
Length of output: 54
🏁 Script executed:
# Alternative search for the controller
rg -n "@RestController.*Banner|@RequestMapping.*banner" --type java -B 2 -A 20Repository: onetime-with-members/backend
Length of output: 54
🏁 Script executed:
# Search for any controller that maps to /api/v1/bar-banners
rg -n "bar-banner" --type java -B 5 -A 5Repository: onetime-with-members/backend
Length of output: 22353
🏁 Script executed:
# Let me verify the controller class declaration and mapping
head -30 src/main/java/side/onetime/controller/BannerController.javaRepository: onetime-with-members/backend
Length of output: 1140
BarBanner 엔드포인트에 대한 과도한 공개 접근 설정 - 심각한 보안 취약점
/api/v1/bar-banners/** 와일드카드는 인증이 필요한 모든 CRUD 작업을 공개적으로 노출합니다:
POST /bar-banners/register- 누구나 배너 생성 가능 (인증 필수 엔드포인트)PATCH /bar-banners/{id}- 누구나 배너 수정 가능 (인증 필수 엔드포인트)DELETE /bar-banners/{id}- 누구나 배너 삭제 가능 (인증 필수 엔드포인트)GET /bar-banners/all- 누구나 페이지네이션된 전체 배너 조회 가능, 미발행 배너 포함 (인증 필수 엔드포인트)GET /bar-banners/{id}- 누구나 특정 배너 조회 가능 (인증 필수 엔드포인트)
오직 GET /bar-banners/activated/all 엔드포인트만 공개하도록 제한해야 합니다. PUBLIC_URLS의 /api/v1/bar-banners/** 엔트리를 제거하고 /api/v1/bar-banners/activated/all 경로만 명시적으로 공개 접근을 허용하도록 변경하세요.
🤖 Prompt for AI Agents
In src/main/java/side/onetime/global/config/SecurityConfig.java around line 44,
the PUBLIC_URLS entry "/api/v1/bar-banners/**" overly exposes all BarBanner CRUD
endpoints; remove that wildcard from PUBLIC_URLS and instead add only
"/api/v1/bar-banners/activated/all" as an explicitly permitted (anonymous)
endpoint, ensuring all other /api/v1/bar-banners/** routes remain protected by
authentication/authorization rules (adjust any antMatchers/authorizeRequests
ordering if necessary so the specific public path is checked before secured
patterns).
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.
인증 필수인 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.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check BannerService implementation for admin authentication validation
echo "=== BannerService admin authentication checks ==="
ast-grep --pattern 'verifyAdminUser($$$)' src/main/java/side/onetime/service/BannerService.java
echo -e "\n=== BannerService registerBarBanner method ==="
ast-grep --pattern $'registerBarBanner($$$) {
$$$
}'
echo -e "\n=== Check for admin verification in BannerService ==="
rg -n "verifyAdmin|checkAdmin|validateAdmin" src/main/java/side/onetime/service/BannerService.java -A 2 -B 2
Length of output: 152
🏁 Script executed:
#!/bin/bash
# Check JwtFilter shouldNotFilter implementation
echo "=== JwtFilter shouldNotFilter method ==="
ast-grep --pattern $'shouldNotFilter($$$) {
$$$
}'
echo -e "\n=== JwtFilter complete implementation ==="
cat src/main/java/side/onetime/global/filter/JwtFilter.java
Length of output: 6402
🏁 Script executed:
#!/bin/bash
# Check BannerController getAllActivatedBarBanners endpoint
echo "=== BannerController getAllActivatedBarBanners ==="
ast-grep --pattern $'getAllActivatedBarBanners($$$) {
$$$
}'
echo -e "\n=== Check which bar-banner endpoints have Authorization header ==="
rg -n "@.*Mapping.*bar-banner" src/main/java/side/onetime/controller/BannerController.java -A 5 -B 2
Length of output: 2573
🏁 Script executed:
#!/bin/bash
# Get complete BannerService implementation to check admin verification
echo "=== BannerService complete implementation ==="
cat src/main/java/side/onetime/service/BannerService.java
Length of output: 12028
🏁 Script executed:
#!/bin/bash
# Search for admin verification utility methods in JwtUtil
echo "=== JwtUtil admin verification methods ==="
rg -n "verifyAdmin|checkAdmin|AdminUser" src/main/java/side/onetime/util/JwtUtil.java -A 3 -B 1
Length of output: 479
프론트엔드 검증은 보안 메커니즘이 아니며, curl이나 Postman 등으로 쉽게 우회할 수 있습니다.
현재 구현을 확인한 결과:
현재 상태:
JwtFilter의shouldNotFilter에서/api/v1/bar-banners경로를 JWT 검증에서 완전히 제외SecurityConfig의PUBLIC_URLS에/api/v1/bar-banners/**추가로 Spring Security 인증 완전히 우회BannerService는 admin 메서드에서jwtUtil.getAdminUserFromHeader()를 호출하여 토큰 검증 수행
문제점:
보안 계층(JwtFilter, SecurityConfig)이 완전히 우회되고, 서비스 계층에서만 예외를 던지는 방식은 올바른 보안 아키텍처가 아닙니다:
# 인증 없이 admin API 호출 가능 (서비스에서 예외 발생)
curl -X POST https://api.example.com/api/v1/bar-banners/register \
-H "Content-Type: application/json" \
-d '{"title":"test"}'올바른 보안 계층 구조:
- Filter Layer (JwtFilter): JWT 토큰 검증 및 인증 정보 설정
- Security Layer (SecurityConfig): 경로별 접근 권한 제어
- Service Layer (BannerService): 비즈니스 로직 및 역할 기반 인가 (defense-in-depth)
권장 수정사항:
JwtFilter.shouldNotFilter()에서/api/v1/bar-banners제거하거나, 특정 경로만 제외:
// 활성화된 띠배너 조회만 JWT 필터 제외
(isGet && path.equals("/api/v1/bar-banners/activated/all"))SecurityConfig.PUBLIC_URLS에서 와일드카드 제거하고 공개 엔드포인트만 명시:
private static final String[] PUBLIC_URLS = {
// ... 기존 항목들
"/api/v1/banners/activated/all",
"/api/v1/bar-banners/activated/all", // 공개 엔드포인트만 허용
// "/api/v1/bar-banners/**", // 제거이렇게 하면:
- ✅
GET /bar-banners/activated/all: 공개 접근 (의도된 동작) - ✅ 나머지 admin 엔드포인트들: JwtFilter에서 토큰 검증 → SecurityConfig에서 인증 확인 → Service에서 admin 역할 확인 (다층 방어)
현재 방식은 보안 계층을 우회하고 서비스 레이어에서만 검증하기 때문에, 보안 아키텍처 관점에서 개선이 필요합니다.
🧠 Learnings used
Learnt from: CR
Repo: onetime-with-members/backend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-21T11:41:05.367Z
Learning: Applies to src/main/java/**/auth/*.java : Use Spring Security, OAuth2 (Google, Kakao, Naver), and JWT (JJWT) for authentication and authorization
Learnt from: CR
Repo: onetime-with-members/backend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-21T11:41:05.367Z
Learning: Applies to src/main/java/**/controller/*Controller.java : Use RESTful API endpoints with /api/v1/ prefix
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.
JwtFilter에서는 토큰에서 userId를 읽어와서 SecurityContext에 User를 적용하고 있어
그래서 사용자가 사용하지 않거나 인증이 필요하지 않은 API (어드민, 배너)는 JwtFilter를 생략해야해
SecurityConfig에서 PUBLIC_URLS, AUTHENTICATED_URLS는 확인이 필요할 거 같네
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.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check which Banner and BarBanner endpoints should be public vs admin-only
echo "=== BannerController endpoints and their authorization requirements ==="
rg -n "@.*Mapping" src/main/java/side/onetime/controller/BannerController.java -A 8 -B 2 | head -120
echo -e "\n=== AdminController endpoints ==="
rg -n "@.*Mapping" src/main/java/side/onetime/controller/AdminController.java -A 5 -B 2 | head -60
Length of output: 7672
이해했습니다! JwtFilter가 userId를 추출해서 User를 SecurityContext에 설정하는 용도라면, 어드민/배너 API는 JwtFilter를 건너뛰는 게 맞습니다.
현재 SecurityConfig를 확인한 결과, PUBLIC_URLS의 와일드카드 설정이 과도하게 넓습니다:
🔍 현재 문제점
private static final String[] PUBLIC_URLS = {
"/api/v1/admin/**", // ⚠️ 모든 어드민 API가 permitAll
"/api/v1/banners/**", // ⚠️ 모든 배너 API가 permitAll
"/api/v1/bar-banners/**", // ⚠️ 모든 띠배너 API가 permitAll
// ...
};이 설정은 Spring Security 관점에서 모든 하위 경로를 인증 없이 접근 가능하게 만듭니다:
- ✅
/admin/register,/admin/login(의도: 공개) - ❌
/admin/profile,/admin/all(의도: 어드민 토큰 필요) → 하지만 permitAll 적용됨 - ✅
/banners/activated/all(의도: 공개) - ❌
/banners/register,/banners/{id}(의도: 어드민 토큰 필요) → 하지만 permitAll 적용됨
서비스 레이어에서 어드민 토큰 검증을 하고 있어서 동작은 하지만, Spring Security 설정과 실제 보안 로직이 불일치합니다.
✅ 권장 수정안
코드 조회 결과, 실제로 공개 접근이 필요한 엔드포인트만 명시하세요:
private static final String[] PUBLIC_URLS = {
"/login/**",
"/api/v1/events/**",
"/api/v1/schedules/**",
"/api/v1/members/**",
"/api/v1/urls/**",
"/api/v1/tokens/**",
// 어드민 - 공개 엔드포인트만
"/api/v1/admin/register",
"/api/v1/admin/login",
// 배너 - 공개 엔드포인트만
"/api/v1/banners/activated/all",
"/api/v1/banners/*/clicks",
"/api/v1/bar-banners/activated/all",
"/api/v1/users/onboarding",
"/api/v1/users/logout",
"/actuator/health"
};어드민 토큰이 필요한 엔드포인트는 PUBLIC_URLS에서 제거하고, 대신 아래 중 하나를 선택하세요:
옵션 1: 현재 구조 유지 (간단)
어드민 토큰이 필요한 API들을 별도 패턴으로 추가:
private static final String[] ADMIN_URLS = {
"/api/v1/admin/profile",
"/api/v1/admin/all",
"/api/v1/admin/status",
"/api/v1/admin/withdraw",
"/api/v1/banners/**", // activated/all과 clicks 제외한 나머지
"/api/v1/bar-banners/**" // activated/all 제외한 나머지
};
// SecurityFilterChain에서
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(SWAGGER_URLS).permitAll()
.requestMatchers(PUBLIC_URLS).permitAll()
.requestMatchers(ADMIN_URLS).permitAll() // 서비스 레이어에서 어드민 검증
.requestMatchers(AUTHENTICATED_URLS).authenticated()
.anyRequest().authenticated()
)옵션 2: 더 명확한 구조 (권장)
공개 API만 permitAll(), 나머지는 .anyRequest().permitAll()로 처리하되, JwtFilter에서 user 경로만 필터링:
// JwtFilter.shouldNotFilter() - user API 아닌 것들은 모두 skip
return !path.startsWith("/api/v1/users/") &&
!path.startsWith("/api/v1/fixed-schedules/");이렇게 하면:
- User API → JwtFilter 통과 → AUTHENTICATED_URLS로 인증 필요
- Admin/Banner API → JwtFilter 건너뜀 → permitAll → 서비스 레이어 검증
어떤 방식을 선호하시나요?
🧬 Code Graph Analysis Results
[src/main/java/side/onetime/global/config/SecurityConfig.java]
-
Snippet 1: PUBLIC_URLS and AUTHENTICATED_URLS definitions (relevant to which endpoints are publicly accessible vs require authentication)
Summary:- Parameters: none
- Returns: none (constants)
- Details: Defines two arrays of URL patterns used to configure security rules:
- PUBLIC_URLS: endpoints accessible without authentication (e.g., login, events, schedules, member-related APIs, admin, banners, onboarding, logout, health, etc.).
- AUTHENTICATED_URLS: endpoints that require authentication (e.g., user-related APIs, fixed-schedules).
- Related behavior: these arrays are used in the SecurityFilterChain to determine permitAll vs authenticated requirements.
Code:
private static final String[] PUBLIC_URLS = {
"/login/",
"/api/v1/events/",
"/api/v1/schedules/",
"/api/v1/members/",
"/api/v1/urls/",
"/api/v1/tokens/",
"/api/v1/admin/",
"/api/v1/banners/",
"/api/v1/bar-banners/**",
"/api/v1/users/onboarding",
"/api/v1/users/logout",
"/actuator/health"
};private static final String[] AUTHENTICATED_URLS = {
"/api/v1/users/",
"/api/v1/fixed-schedules/",
}; -
Snippet 2: SecurityFilterChain integration showing how PUBLIC_URLS and AUTHENTICATED_URLS influence access control (and JwtFilter usage)
Summary:- Parameters: HttpSecurity httpSecurity
- Returns: SecurityFilterChain
- Exceptions: Exception
- Details: Configures HTTP security to:
- Disable HTTP Basic and CSRF
- Apply CORS configuration
- Permit all for SWAGGER_URLS and PUBLIC_URLS
- Require authentication for AUTHENTICATED_URLS and any other request
- Configure OAuth2 login success/failure handlers
- Add JwtFilter before the UsernamePasswordAuthenticationFilter
- This ties the user comment about JwtFilter reading userId from token and the need to skip JwtFilter for non-authenticated/public endpoints.
Code:
@bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.httpBasic(HttpBasicConfigurer::disable)
.cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(SWAGGER_URLS).permitAll()
.requestMatchers(PUBLIC_URLS).permitAll()
.requestMatchers(AUTHENTICATED_URLS).authenticated()
.anyRequest().authenticated()
)
.oauth2Login(oauth -> oauth
.successHandler(oAuthLoginSuccessHandler)
.failureHandler(oAuthLoginFailureHandler)
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);return httpSecurity.build();
}
🧠 Learnings used
Learnt from: CR
Repo: onetime-with-members/backend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-21T11:41:05.367Z
Learning: Applies to src/main/java/**/auth/*.java : Use Spring Security, OAuth2 (Google, Kakao, Naver), and JWT (JJWT) for authentication and authorization
Learnt from: CR
Repo: onetime-with-members/backend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-21T11:41:05.367Z
Learning: Applies to src/main/java/**/controller/*Controller.java : Use RESTful API endpoints with /api/v1/ prefix
Learnt from: bbbang105
Repo: onetime-with-members/backend PR: 246
File: src/main/java/side/onetime/service/TokenService.java:37-37
Timestamp: 2025-06-04T16:25:22.271Z
Learning: JwtUtil의 getClaimFromToken 메서드는 JWT 파싱 및 클레임 추출 과정에서 발생하는 JwtException과 IllegalArgumentException을 catch하여 _TOKEN_CLAIM_EXTRACTION_ERROR로 변환하여 던진다. 따라서 클레임이 누락되거나 추출에 실패하는 경우에 대한 별도의 null 체크는 불필요하다.
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.
차후 PR에서 진행 예정
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!
bbbang105
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! :)
✅ PR 유형
🚀 작업 내용
📝️ 관련 이슈
💬 기타 사항 or 추가 코멘트
Summary by CodeRabbit
릴리스 노트
새 기능
리팩토링
✏️ Tip: You can customize this high-level summary in your review settings.