From 626879ae62202d0e583527bfb8617e773dc6941e Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 10:43:50 +0900 Subject: [PATCH 01/19] =?UTF-8?q?Lv0=20:=20jwt=5Fsecret=5Fkey=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/main/resources/application.yml diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 000000000..13274a4f0 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,3 @@ +jwt: + secret: + key: sodlfqodnazoavmtmvmfld3rlghkdlxlddlqslekdighghghghghnavercom \ No newline at end of file From d4de510aa20cfd78305c7410d970933c3b5f4463 Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 11:08:49 +0900 Subject: [PATCH 02/19] =?UTF-8?q?lv0=20-2=20:=20db=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 13274a4f0..4d057c18b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,11 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/work + driver-class-name: com.mysql.cj.jdbc.Driver + username: root + password: 12345678 + jwt: secret: - key: sodlfqodnazoavmtmvmfld3rlghkdlxlddlqslekdighghghghghnavercom \ No newline at end of file + key: sodlfqodnazoavmtmvmfld3rlghkdlxlddlqslekdighghghghghnavercom + From 7ef4879ce939f47da861be8b07f36b01653180f9 Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 11:22:06 +0900 Subject: [PATCH 03/19] =?UTF-8?q?lv0=20-2=20:=20jpa=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4d057c18b..b2cf8da8c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,6 +5,14 @@ spring: username: root password: 12345678 + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + format_sql: true + jwt: secret: key: sodlfqodnazoavmtmvmfld3rlghkdlxlddlqslekdighghghghghnavercom From 428baf26ce0369849031657a67c57e106b5e5e35 Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 12:25:56 +0900 Subject: [PATCH 04/19] =?UTF-8?q?lv1=20:=20jwtFilter=20=EC=88=98=EC=A0=95,?= =?UTF-8?q?=20@Auth=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EA=B3=BC=20=EA=B4=80=EB=A0=A8=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EB=A5=BC=20=EC=A0=84=EB=8B=AC=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20WebConfig=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/AuthUserArgumentResolver.java | 11 ++++++++ .../org/example/expert/config/JwtFilter.java | 4 ++- .../org/example/expert/config/WebConfig.java | 26 +++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/example/expert/config/WebConfig.java diff --git a/src/main/java/org/example/expert/config/AuthUserArgumentResolver.java b/src/main/java/org/example/expert/config/AuthUserArgumentResolver.java index db00211de..26c6bd777 100644 --- a/src/main/java/org/example/expert/config/AuthUserArgumentResolver.java +++ b/src/main/java/org/example/expert/config/AuthUserArgumentResolver.java @@ -7,11 +7,14 @@ import org.example.expert.domain.user.enums.UserRole; import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; +// 어노테이션 추가 +@Component public class AuthUserArgumentResolver implements HandlerMethodArgumentResolver { @Override @@ -44,3 +47,11 @@ public Object resolveArgument( return new AuthUser(userId, email, userRole); } } + +/** + * AuthUserArgumentResolver의 역할: + * * 1. 전달자 역할: JwtFilter에서 HttpServletRequest에 저장한 유저 정보(ID, 이메일, 권한)를 꺼내옵니다. + * 2. 객체 변환: 꺼낸 정보들을 컨트롤러가 바로 사용할 수 있도록 AuthUser 객체로 변환(포장)합니다. + * 3. 자동 주입: 컨트롤러 메서드의 파라미터에 @Auth 어노테이션이 있으면, 생성된 AuthUser 객체를 자동으로 넘겨줍니다. + * * 결과적으로 컨트롤러에서 복잡한 로직 없이 유저 정보를 '전달'받아 바로 쓸 수 있게 해주는 고마운 배달부입니다! + */ \ No newline at end of file diff --git a/src/main/java/org/example/expert/config/JwtFilter.java b/src/main/java/org/example/expert/config/JwtFilter.java index a94288b0f..1ddb50866 100644 --- a/src/main/java/org/example/expert/config/JwtFilter.java +++ b/src/main/java/org/example/expert/config/JwtFilter.java @@ -59,7 +59,9 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha httpRequest.setAttribute("userId", Long.parseLong(claims.getSubject())); httpRequest.setAttribute("email", claims.get("email")); - httpRequest.setAttribute("userRole", claims.get("userRole")); + httpRequest.setAttribute("userRole", userRole.name()); + + // userRole.name()으로 수정 ; Resolver와 jwtfilter 의 형변환 타입 일치 if (url.startsWith("/admin") && !UserRole.ADMIN.equals(userRole)) { log.warn("권한 부족: userId={}, role={}, URI={}", claims.getSubject(), userRole, url); diff --git a/src/main/java/org/example/expert/config/WebConfig.java b/src/main/java/org/example/expert/config/WebConfig.java new file mode 100644 index 000000000..0cff40b1f --- /dev/null +++ b/src/main/java/org/example/expert/config/WebConfig.java @@ -0,0 +1,26 @@ +package org.example.expert.config; + +import lombok.RequiredArgsConstructor; +import org.example.expert.domain.common.annotation.Auth; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + //HttpServletRequest -> AuthUser -> @Auth AuthUser authUser로 전달하는 객체 생성 + + private final AuthUserArgumentResolver authUserArgumentResolver; + + /* + * 4. 스프링이 컨트롤러의 파라미터를 처리할 때 사용할 '정보 리스트'에 커스텀 Resolver를 추가하는 작업 + */ + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(authUserArgumentResolver); + } +} From f7c705863a9f0fc300d3b280f6d8169103447d0e Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 12:35:57 +0900 Subject: [PATCH 05/19] =?UTF-8?q?lv2-1=20:=20authService=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/expert/domain/auth/service/AuthService.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/example/expert/domain/auth/service/AuthService.java b/src/main/java/org/example/expert/domain/auth/service/AuthService.java index 3dd3bb7c1..f0fbfef16 100644 --- a/src/main/java/org/example/expert/domain/auth/service/AuthService.java +++ b/src/main/java/org/example/expert/domain/auth/service/AuthService.java @@ -26,13 +26,16 @@ public class AuthService { @Transactional public SignupResponse signup(SignupRequest signupRequest) { + // 인코딩 전에 검증 로직을 가장먼저 시행할 수 있게 순서를 맨위로 지정한다. lv2 - 1 + if (userRepository.existsByEmail(signupRequest.getEmail())) { + throw new InvalidRequestException("이미 존재하는 이메일입니다."); + } + + String encodedPassword = passwordEncoder.encode(signupRequest.getPassword()); UserRole userRole = UserRole.of(signupRequest.getUserRole()); - if (userRepository.existsByEmail(signupRequest.getEmail())) { - throw new InvalidRequestException("이미 존재하는 이메일입니다."); - } User newUser = new User( signupRequest.getEmail(), From 4a9dddd35e918ffcfc152e144624fb6beba1fc9a Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 12:46:45 +0900 Subject: [PATCH 06/19] =?UTF-8?q?lv2-2=20:=20else=EB=AC=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/example/expert/client/WeatherClient.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/example/expert/client/WeatherClient.java b/src/main/java/org/example/expert/client/WeatherClient.java index d8ec223e7..83b59eed0 100644 --- a/src/main/java/org/example/expert/client/WeatherClient.java +++ b/src/main/java/org/example/expert/client/WeatherClient.java @@ -29,10 +29,12 @@ public String getTodayWeather() { WeatherDto[] weatherArray = responseEntity.getBody(); if (!HttpStatus.OK.equals(responseEntity.getStatusCode())) { throw new ServerException("날씨 데이터를 가져오는데 실패했습니다. 상태 코드: " + responseEntity.getStatusCode()); - } else { - if (weatherArray == null || weatherArray.length == 0) { + } + + // 리얼 else문 제거하기 + if (weatherArray == null || weatherArray.length == 0) { throw new ServerException("날씨 데이터가 없습니다."); - } + } String today = getCurrentDate(); From acbc2582a6f1e64f7b60a197b0214ad47f9ca589 Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 12:53:19 +0900 Subject: [PATCH 07/19] =?UTF-8?q?lv2-3=20:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20if=20=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/UserChangePasswordRequest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/org/example/expert/domain/user/dto/request/UserChangePasswordRequest.java b/src/main/java/org/example/expert/domain/user/dto/request/UserChangePasswordRequest.java index 3ba93dde5..1fdee1cdd 100644 --- a/src/main/java/org/example/expert/domain/user/dto/request/UserChangePasswordRequest.java +++ b/src/main/java/org/example/expert/domain/user/dto/request/UserChangePasswordRequest.java @@ -1,9 +1,12 @@ package org.example.expert.domain.user.dto.request; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import org.example.expert.domain.common.exception.InvalidRequestException; @Getter @NoArgsConstructor @@ -12,6 +15,17 @@ public class UserChangePasswordRequest { @NotBlank private String oldPassword; + + + //service에 불필요한 if문 제거 후 size와 pattern 어노테이션 사용 후 제약조건 설정\ + @Size(min = 8, message = "새 비밀번호는 8자 이상이어야 합니다.") + //if (userChangePasswordRequest.getNewPassword().length() < 8 || + @Pattern(regexp = ".*\\d.*", message = "숫자를 포함해야 합니다.") + //!userChangePasswordRequest.getNewPassword().matches(".*\\d.*") || + @Pattern(regexp = ".*[A-Z].*", message = "대문자를 포함해야 합니다.") + //!userChangePasswordRequest.getNewPassword().matches(".*[A-Z].*")) @NotBlank private String newPassword; } + + From 8f3ccee7a17ae7b3ba86982f04b3d63dd0a22a8c Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 13:33:04 +0900 Subject: [PATCH 08/19] =?UTF-8?q?lv3=20:=20=EC=BF=BC=EB=A6=AC=EB=AC=B8=20?= =?UTF-8?q?=EB=B6=84=EC=84=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../expert/domain/todo/repository/TodoRepository.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/org/example/expert/domain/todo/repository/TodoRepository.java b/src/main/java/org/example/expert/domain/todo/repository/TodoRepository.java index 064e74f9a..7e9d0d271 100644 --- a/src/main/java/org/example/expert/domain/todo/repository/TodoRepository.java +++ b/src/main/java/org/example/expert/domain/todo/repository/TodoRepository.java @@ -11,9 +11,16 @@ public interface TodoRepository extends JpaRepository { + + //목록조회 + // todo entity 불러오기 // todo와 관련있는 유저정보 불러오고 수정시간 내림차순으로 정렬하기 @Query("SELECT t FROM Todo t LEFT JOIN FETCH t.user u ORDER BY t.modifiedAt DESC") + // page 형태로 Page findAllByOrderByModifiedAtDesc(Pageable pageable); + + // 상세조회 + // todo entity 불러오기 // todo와 관련있는 유저정보 불러오는데 특정id에 관한것만 불러오기 @Query("SELECT t FROM Todo t " + "LEFT JOIN FETCH t.user " + "WHERE t.id = :todoId") From 3cc70db683deff91ed0fcb310c36b5c7fc722507 Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 13:49:23 +0900 Subject: [PATCH 09/19] =?UTF-8?q?lv3=20:=20n+1=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=ED=9B=84=20=EC=88=98=EC=A0=95(fetch=20joi?= =?UTF-8?q?n=20->=20EntityGraph)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../expert/domain/todo/repository/TodoRepository.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/expert/domain/todo/repository/TodoRepository.java b/src/main/java/org/example/expert/domain/todo/repository/TodoRepository.java index 7e9d0d271..34a945c6a 100644 --- a/src/main/java/org/example/expert/domain/todo/repository/TodoRepository.java +++ b/src/main/java/org/example/expert/domain/todo/repository/TodoRepository.java @@ -3,6 +3,7 @@ import org.example.expert.domain.todo.entity.Todo; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -14,15 +15,20 @@ public interface TodoRepository extends JpaRepository { //목록조회 // todo entity 불러오기 // todo와 관련있는 유저정보 불러오고 수정시간 내림차순으로 정렬하기 - @Query("SELECT t FROM Todo t LEFT JOIN FETCH t.user u ORDER BY t.modifiedAt DESC") + + // "LEFT JOIN FETCH t.user u" fetch join문을 entityGraph로 변경 : user table 조회 후 Mapping + @EntityGraph(attributePaths = {"user"}) + @Query("SELECT t FROM Todo t ORDER BY t.modifiedAt DESC") // page 형태로 Page findAllByOrderByModifiedAtDesc(Pageable pageable); // 상세조회 // todo entity 불러오기 // todo와 관련있는 유저정보 불러오는데 특정id에 관한것만 불러오기 + + // "LEFT JOIN FETCH t.user " fetch join문을 entityGraph로 변경 : user table 조회 후 id Mapping + @EntityGraph(attributePaths = {"user"}) @Query("SELECT t FROM Todo t " + - "LEFT JOIN FETCH t.user " + "WHERE t.id = :todoId") Optional findByIdWithUser(@Param("todoId") Long todoId); From 2cb5b32b3bbbb2c85b76b23315f820c8ab1ed005 Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 14:34:48 +0900 Subject: [PATCH 10/19] =?UTF-8?q?lv4-1=20:=20passwordEncoderTest=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/example/expert/config/PasswordEncoderTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/example/expert/config/PasswordEncoderTest.java b/src/test/java/org/example/expert/config/PasswordEncoderTest.java index 694643d7e..32db3a9c7 100644 --- a/src/test/java/org/example/expert/config/PasswordEncoderTest.java +++ b/src/test/java/org/example/expert/config/PasswordEncoderTest.java @@ -19,8 +19,10 @@ class PasswordEncoderTest { String rawPassword = "testPassword"; String encodedPassword = passwordEncoder.encode(rawPassword); + + // 먼저 입력 비밀번호부터 작성하고 인코딩 패스워드 순으로 작성해야함 ; 강의 & 이전 프로젝트 참고함 // when - boolean matches = passwordEncoder.matches(encodedPassword, rawPassword); + boolean matches = passwordEncoder.matches(rawPassword, encodedPassword); // then assertTrue(matches); From 5643b0add83a6bd742e7e6d272d297997ff86cf4 Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 16:31:16 +0900 Subject: [PATCH 11/19] =?UTF-8?q?Lv4-2=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EB=B6=84=EC=84=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/manager/service/ManagerServiceTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/org/example/expert/domain/manager/service/ManagerServiceTest.java b/src/test/java/org/example/expert/domain/manager/service/ManagerServiceTest.java index 47ddd6205..9a55121a0 100644 --- a/src/test/java/org/example/expert/domain/manager/service/ManagerServiceTest.java +++ b/src/test/java/org/example/expert/domain/manager/service/ManagerServiceTest.java @@ -39,12 +39,21 @@ class ManagerServiceTest { private ManagerService managerService; @Test + + // todo가 없다면 NullPointException Error 던지기; public void manager_목록_조회_시_Todo가_없다면_NPE_에러를_던진다() { + // given + // id 가 1임 long todoId = 1L; + // 근데 empty인 상태임 given(todoRepository.findById(todoId)).willReturn(Optional.empty()); // when & then + // 이 부분에서는 NPE 에러를 던지는게 아니라 Manager not found를 던진다. + // 그렇다면 바꿀점 : + // 메서드명 : NPE 에러를 InvalidRequestException으로 바꿔준다. + // 로직 : todo가 없는거라 Manager not found가 아니라 todo not found로 바꿔야할듯 InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> managerService.getManagers(todoId)); assertEquals("Manager not found", exception.getMessage()); } From 330e20397ad22793cbf6350db69925e5121c120a Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 16:34:15 +0900 Subject: [PATCH 12/19] =?UTF-8?q?Lv4-2=20:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../expert/domain/manager/service/ManagerServiceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/example/expert/domain/manager/service/ManagerServiceTest.java b/src/test/java/org/example/expert/domain/manager/service/ManagerServiceTest.java index 9a55121a0..0f8f30123 100644 --- a/src/test/java/org/example/expert/domain/manager/service/ManagerServiceTest.java +++ b/src/test/java/org/example/expert/domain/manager/service/ManagerServiceTest.java @@ -41,7 +41,7 @@ class ManagerServiceTest { @Test // todo가 없다면 NullPointException Error 던지기; - public void manager_목록_조회_시_Todo가_없다면_NPE_에러를_던진다() { + public void manager_목록_조회_시_Todo가_없다면_InvalidRequestException을_던진다() { // given // id 가 1임 @@ -55,7 +55,7 @@ class ManagerServiceTest { // 메서드명 : NPE 에러를 InvalidRequestException으로 바꿔준다. // 로직 : todo가 없는거라 Manager not found가 아니라 todo not found로 바꿔야할듯 InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> managerService.getManagers(todoId)); - assertEquals("Manager not found", exception.getMessage()); + assertEquals("Todo not found", exception.getMessage()); } @Test From 09ab5e22db8cf782074c1b33913b8ccd8504eb69 Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 16:49:42 +0900 Subject: [PATCH 13/19] =?UTF-8?q?lv4-2-2=20:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=B6=84=EC=84=9D=20=EB=B0=8F=20=EC=A3=BC=EC=84=9D=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../expert/domain/comment/service/CommentServiceTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/java/org/example/expert/domain/comment/service/CommentServiceTest.java b/src/test/java/org/example/expert/domain/comment/service/CommentServiceTest.java index ba6797dcd..ba065b51e 100644 --- a/src/test/java/org/example/expert/domain/comment/service/CommentServiceTest.java +++ b/src/test/java/org/example/expert/domain/comment/service/CommentServiceTest.java @@ -36,6 +36,9 @@ class CommentServiceTest { @Test public void comment_등록_중_할일을_찾지_못해_에러가_발생한다() { // given + // 댓글 저장 + // 댓글내용을 저장하고 권한유저(아이디, 이메일, 권한(역할) 확인 + // todo_id가 비어있는 상황 -- 테스트 코드만 수정해야함. long todoId = 1; CommentSaveRequest request = new CommentSaveRequest("contents"); AuthUser authUser = new AuthUser(1L, "email", UserRole.USER); @@ -43,6 +46,8 @@ class CommentServiceTest { given(todoRepository.findById(anyLong())).willReturn(Optional.empty()); // when + // 유저, 아이디, 내용 저장 + // todo를 찾지 못하는 건 서버에러가 아닌 InvalidRequestException으로 넘어가야함 ServerException exception = assertThrows(ServerException.class, () -> { commentService.saveComment(authUser, todoId, request); }); From 55cb8fbf7185d950dde06ff36ce33c7c38e50579 Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 16:53:10 +0900 Subject: [PATCH 14/19] =?UTF-8?q?lv4-2-2=20:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../expert/domain/comment/service/CommentServiceTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/example/expert/domain/comment/service/CommentServiceTest.java b/src/test/java/org/example/expert/domain/comment/service/CommentServiceTest.java index ba065b51e..90a9be03d 100644 --- a/src/test/java/org/example/expert/domain/comment/service/CommentServiceTest.java +++ b/src/test/java/org/example/expert/domain/comment/service/CommentServiceTest.java @@ -5,6 +5,7 @@ import org.example.expert.domain.comment.entity.Comment; import org.example.expert.domain.comment.repository.CommentRepository; import org.example.expert.domain.common.dto.AuthUser; +import org.example.expert.domain.common.exception.InvalidRequestException; import org.example.expert.domain.common.exception.ServerException; import org.example.expert.domain.todo.entity.Todo; import org.example.expert.domain.todo.repository.TodoRepository; @@ -48,7 +49,7 @@ class CommentServiceTest { // when // 유저, 아이디, 내용 저장 // todo를 찾지 못하는 건 서버에러가 아닌 InvalidRequestException으로 넘어가야함 - ServerException exception = assertThrows(ServerException.class, () -> { + InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> { commentService.saveComment(authUser, todoId, request); }); From ad374a9292a33535a24b85e346427e080a2ad9a0 Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 17:11:32 +0900 Subject: [PATCH 15/19] =?UTF-8?q?Lv4-2-3=20:=20managerService=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/expert/domain/manager/service/ManagerService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/example/expert/domain/manager/service/ManagerService.java b/src/main/java/org/example/expert/domain/manager/service/ManagerService.java index ad3c08ce4..afd4edd43 100644 --- a/src/main/java/org/example/expert/domain/manager/service/ManagerService.java +++ b/src/main/java/org/example/expert/domain/manager/service/ManagerService.java @@ -28,6 +28,8 @@ public class ManagerService { private final UserRepository userRepository; private final TodoRepository todoRepository; + + // todo에 user가 null인 경우는 보이지 않음... 조건문을 한번 추가해보겠음. @Transactional public ManagerSaveResponse saveManager(AuthUser authUser, long todoId, ManagerSaveRequest managerSaveRequest) { // 일정을 만든 유저 @@ -35,6 +37,8 @@ public ManagerSaveResponse saveManager(AuthUser authUser, long todoId, ManagerSa Todo todo = todoRepository.findById(todoId) .orElseThrow(() -> new InvalidRequestException("Todo not found")); + + // if (!ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) { throw new InvalidRequestException("일정을 생성한 유저만 담당자를 지정할 수 있습니다."); } From 113a66a69bbdc2fae6d46bb064bbd52e0697c6cb Mon Sep 17 00:00:00 2001 From: cherry pick Date: Thu, 5 Mar 2026 17:16:31 +0900 Subject: [PATCH 16/19] =?UTF-8?q?Lv4-2-3=20:=20service=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=97=90=20=EC=A1=B0=EA=B1=B4=EB=AC=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95(user=20=3D=3Dnull=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../expert/domain/manager/service/ManagerService.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/expert/domain/manager/service/ManagerService.java b/src/main/java/org/example/expert/domain/manager/service/ManagerService.java index afd4edd43..b0fb714cd 100644 --- a/src/main/java/org/example/expert/domain/manager/service/ManagerService.java +++ b/src/main/java/org/example/expert/domain/manager/service/ManagerService.java @@ -38,8 +38,12 @@ public ManagerSaveResponse saveManager(AuthUser authUser, long todoId, ManagerSa .orElseThrow(() -> new InvalidRequestException("Todo not found")); - // - if (!ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) { + //user가 null인 경우를 작성했으나 하단 메시지와 동일해서 OR문으로 재작성 +// if(todo.getUser()==null) { +// throw new InvalidRequestException("일정을 생성한 유저만 담당자를 지정할 수 있습니다."); +// } + + if (todo.getUser()==null||!ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) { throw new InvalidRequestException("일정을 생성한 유저만 담당자를 지정할 수 있습니다."); } From 10ec09fe0a16bbb14480464b29b2e25f5dfdb57b Mon Sep 17 00:00:00 2001 From: cherry pick Date: Fri, 6 Mar 2026 17:19:03 +0900 Subject: [PATCH 17/19] =?UTF-8?q?lv5-1=20:=20intercepotr=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../expert/config/AdminCheckInterceptor.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/org/example/expert/config/AdminCheckInterceptor.java diff --git a/src/main/java/org/example/expert/config/AdminCheckInterceptor.java b/src/main/java/org/example/expert/config/AdminCheckInterceptor.java new file mode 100644 index 000000000..60658101f --- /dev/null +++ b/src/main/java/org/example/expert/config/AdminCheckInterceptor.java @@ -0,0 +1,28 @@ +package org.example.expert.config; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.example.expert.domain.common.exception.InvalidRequestException; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.management.Attribute; + +public class AdminCheckInterceptor implements HandlerInterceptor { + + @Override + + // 역할 가져오기 + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + + Object Role = request.getAttribute("userRole"); + + // 역할이랑 일치하는지 확인 + if (Role == null || !"Admin".equals(Role.toString())) { + throw new InvalidRequestException(" 어드민 권한이 필요합니다."); + } + + // 특이사항 없으면 true + return true; + } +} From fb1deb893cf16bbc20983f2aa0dae50ad2924d5c Mon Sep 17 00:00:00 2001 From: cherry pick Date: Fri, 6 Mar 2026 17:42:56 +0900 Subject: [PATCH 18/19] =?UTF-8?q?LV5-2=20:=20interceptor=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/example/expert/config/WebConfig.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/org/example/expert/config/WebConfig.java b/src/main/java/org/example/expert/config/WebConfig.java index 0cff40b1f..651868df5 100644 --- a/src/main/java/org/example/expert/config/WebConfig.java +++ b/src/main/java/org/example/expert/config/WebConfig.java @@ -4,6 +4,7 @@ import org.example.expert.domain.common.annotation.Auth; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @@ -15,6 +16,7 @@ public class WebConfig implements WebMvcConfigurer { //HttpServletRequest -> AuthUser -> @Auth AuthUser authUser로 전달하는 객체 생성 private final AuthUserArgumentResolver authUserArgumentResolver; + private final AdminCheckInterceptor adminCheckInterceptor; /* * 4. 스프링이 컨트롤러의 파라미터를 처리할 때 사용할 '정보 리스트'에 커스텀 Resolver를 추가하는 작업 @@ -23,4 +25,13 @@ public class WebConfig implements WebMvcConfigurer { public void addArgumentResolvers(List resolvers) { resolvers.add(authUserArgumentResolver); } + + + //인터셉터 추가 /스프링이 제공하는 레지스트리 및 작성한 adminCheckInterceptor(admin 관련 주소 전부 가져오기)를 활용 + @Override + public void addInterceptors(InterceptorRegistry registry){ + registry.addInterceptor(adminCheckInterceptor).addPathPatterns("/admin/**"); + + + } } From 3da08a98e97486219aa3e6ad11abecb62a3ffee5 Mon Sep 17 00:00:00 2001 From: cherry pick Date: Fri, 6 Mar 2026 20:25:29 +0900 Subject: [PATCH 19/19] =?UTF-8?q?Lv5-3=20:=20=EC=9D=B8=ED=84=B0=EC=85=89?= =?UTF-8?q?=ED=84=B0=20=EB=A1=9C=EA=B9=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/expert/config/AdminCheckInterceptor.java | 12 ++++++++++-- .../common/aspect/AdminAccessLoggingAspect.java | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/example/expert/domain/common/aspect/AdminAccessLoggingAspect.java diff --git a/src/main/java/org/example/expert/config/AdminCheckInterceptor.java b/src/main/java/org/example/expert/config/AdminCheckInterceptor.java index 60658101f..5ed36bf4d 100644 --- a/src/main/java/org/example/expert/config/AdminCheckInterceptor.java +++ b/src/main/java/org/example/expert/config/AdminCheckInterceptor.java @@ -2,11 +2,14 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; import org.example.expert.domain.common.exception.InvalidRequestException; +import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; +import java.time.LocalDateTime; -import javax.management.Attribute; - +@Slf4j +@Component public class AdminCheckInterceptor implements HandlerInterceptor { @Override @@ -22,6 +25,11 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons throw new InvalidRequestException(" 어드민 권한이 필요합니다."); } + + //로깅 인포 추가! + log.info("인증된 관리자 접근, Time:{}, URL:{}", + LocalDateTime.now(), request.getRequestURL()); + // 특이사항 없으면 true return true; } diff --git a/src/main/java/org/example/expert/domain/common/aspect/AdminAccessLoggingAspect.java b/src/main/java/org/example/expert/domain/common/aspect/AdminAccessLoggingAspect.java new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/src/main/java/org/example/expert/domain/common/aspect/AdminAccessLoggingAspect.java @@ -0,0 +1 @@ +