From 2ff98cd55f40571755c118f5a6c2e5c76ce263ec Mon Sep 17 00:00:00 2001 From: lehojun Date: Thu, 29 May 2025 17:30:35 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[FEAT]=20#35=20=EA=B2=8C=EC=8B=9C=EA=B8=80?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiPayload/code/status/ErrorStatus.java | 1 + .../com/example/be/config/SecurityConfig.java | 13 +++++------ .../example/be/repository/PostRepository.java | 2 ++ .../example/be/service/PostServiceImpl.java | 12 ++++++++++ .../example/be/service/UserServiceImpl.java | 22 +++++++++---------- .../be/web/controller/PostController.java | 9 +++++++- 6 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/example/be/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/example/be/apiPayload/code/status/ErrorStatus.java index bdca8d7..d3da085 100644 --- a/src/main/java/com/example/be/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/example/be/apiPayload/code/status/ErrorStatus.java @@ -26,6 +26,7 @@ public enum ErrorStatus implements BaseErrorCode { //게시글 관련 에러 _NOT_FOUND_POST(HttpStatus.NOT_FOUND, "POST401", "해당 게시글을 찾을 수 없습니다."), + _NOT_USER_POST(HttpStatus.NOT_FOUND, "POST402", "해당 유저의 게시글이 아닙니다."), //댓글 관련 에러 _NOT_FOUND_COMMENT(HttpStatus.NOT_FOUND, "COMMENT404", "해당 댓글을 찾을 수 없습니다."); diff --git a/src/main/java/com/example/be/config/SecurityConfig.java b/src/main/java/com/example/be/config/SecurityConfig.java index d313f7d..ae9b4df 100644 --- a/src/main/java/com/example/be/config/SecurityConfig.java +++ b/src/main/java/com/example/be/config/SecurityConfig.java @@ -26,22 +26,21 @@ public class SecurityConfig { private final OAuthLoginSuccessHandler oAuthLoginSuccessHandler; private final OAuthLoginFailureHandler oAuthLoginFailureHandler; - // CORS 설정 @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of( - "https://studylink.store", // 프론트엔드 도메인 - "https://api.studylink.store", // 백엔드 API 도메인 - "https://swagger.studylink.store", // Swagger UI 도메인 (필요시) + "https://studylink.store", + "https://api.studylink.store", + "https://swagger.studylink.store", "http://localhost:6080", - "http://localhost:5173" + "http://localhost:5173" // 프론트엔드 개발 서버 )); config.setAllowedHeaders(List.of("*")); - config.setExposedHeaders(List.of("Authorization")); + config.setExposedHeaders(List.of("Authorization", "Set-Cookie")); // Set-Cookie 헤더 노출 config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")); config.setAllowCredentials(true); - config.setMaxAge(3600L); // 프리플라이트 요청 캐싱 시간 (1시간) + config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); diff --git a/src/main/java/com/example/be/repository/PostRepository.java b/src/main/java/com/example/be/repository/PostRepository.java index e63bd21..37c050a 100644 --- a/src/main/java/com/example/be/repository/PostRepository.java +++ b/src/main/java/com/example/be/repository/PostRepository.java @@ -15,4 +15,6 @@ public interface PostRepository extends JpaRepository { @EntityGraph(attributePaths = {"comments", "comments.user", "user"}) Optional findById(Long id); + + Optional findByIdAndUserId(Long id, Long userId); } diff --git a/src/main/java/com/example/be/service/PostServiceImpl.java b/src/main/java/com/example/be/service/PostServiceImpl.java index 1ea6bfe..8ad8fe6 100644 --- a/src/main/java/com/example/be/service/PostServiceImpl.java +++ b/src/main/java/com/example/be/service/PostServiceImpl.java @@ -146,5 +146,17 @@ public PostDTO.PostDetailResponseDTO getPostDetail(Long postId, HttpServletReque .build(); } + public CommonDTO.IsSuccessDTO deletePost(Long postId, HttpServletRequest request) { + User user= getUserFromRequest(request); + + postRepository.findById(postId).orElseThrow(() -> + new PostHandler(ErrorStatus._NOT_FOUND_POST)); + + postRepository.findByIdAndUserId(postId, user.getId()).orElseThrow(()-> + new PostHandler(ErrorStatus._NOT_USER_POST)); + + return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build(); + } + } diff --git a/src/main/java/com/example/be/service/UserServiceImpl.java b/src/main/java/com/example/be/service/UserServiceImpl.java index f88de64..480f46e 100644 --- a/src/main/java/com/example/be/service/UserServiceImpl.java +++ b/src/main/java/com/example/be/service/UserServiceImpl.java @@ -85,29 +85,29 @@ public CommonDTO.IsSuccessDTO login(UserDTO.LoginRequestDto request, HttpServlet String accessToken = jwtUtil.generateAccessToken(user.getUserId(), ACCESS_TOKEN_EXPIRATION_TIME); String origin = httpRequest.getHeader("Origin"); - boolean isSecure = origin == null || !origin.contains("localhost"); + boolean isLocalhost = origin != null && origin.contains("localhost"); -// 액세스 토큰 - if (isSecure) { - // 배포 환경: Secure + SameSite=None + // 액세스 토큰 쿠키 설정 + if (isLocalhost) { + // 로컬 개발 환경: SameSite=None, Secure=false response.addHeader("Set-Cookie", - String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; Secure; SameSite=None", + String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None", accessToken, (int) (ACCESS_TOKEN_EXPIRATION_TIME / 1000))); } else { - // 로컬 환경: SameSite=None (Secure 없음) + // 배포 환경: SameSite=None, Secure=true response.addHeader("Set-Cookie", - String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None", + String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; Secure; SameSite=None", accessToken, (int) (ACCESS_TOKEN_EXPIRATION_TIME / 1000))); } -// 리프레시 토큰 - if (isSecure) { + // 리프레시 토큰 쿠키 설정 + if (isLocalhost) { response.addHeader("Set-Cookie", - String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; Secure; SameSite=None", + String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None", refreshToken, (int) (REFRESH_TOKEN_EXPIRATION_TIME / 1000))); } else { response.addHeader("Set-Cookie", - String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None", + String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; Secure; SameSite=None", refreshToken, (int) (REFRESH_TOKEN_EXPIRATION_TIME / 1000))); } diff --git a/src/main/java/com/example/be/web/controller/PostController.java b/src/main/java/com/example/be/web/controller/PostController.java index db2dac9..9d85196 100644 --- a/src/main/java/com/example/be/web/controller/PostController.java +++ b/src/main/java/com/example/be/web/controller/PostController.java @@ -50,7 +50,6 @@ public ApiResponse getPostDetail( return ApiResponse.onSuccess(postService.getPostDetail(postId, request)); } - // 좋아요 토글 API 추가 @PostMapping("/{postId}/like") @Operation(summary = "게시글 좋아요 토글 API", description = "게시글에 좋아요를 누르거나 취소합니다.") public ApiResponse togglePostLike( @@ -59,4 +58,12 @@ public ApiResponse togglePostLike( return ApiResponse.onSuccess(postLikeService.togglePostLike(postId, request)); } + @PostMapping("/{postId}/delete") + @Operation(summary = "게시글 삭제 API", description = "게시글을 삭제합니다.") + public ApiResponse delete( + @Parameter(description = "게시글 ID") @PathVariable Long postId, + HttpServletRequest request) { + return ApiResponse.onSuccess(postService.deletePost(postId, request)); + } + } From 2c8e50012bb145ecfac505043b6691546a1f8a52 Mon Sep 17 00:00:00 2001 From: lehojun Date: Fri, 30 May 2025 19:16:46 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[FEAT]=20#35=20=EA=B2=8C=EC=8B=9C=EA=B8=80?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/be/service/PostServiceImpl.java | 6 +-- .../example/be/service/UserServiceImpl.java | 51 +++++++++---------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/example/be/service/PostServiceImpl.java b/src/main/java/com/example/be/service/PostServiceImpl.java index 8ad8fe6..4150efa 100644 --- a/src/main/java/com/example/be/service/PostServiceImpl.java +++ b/src/main/java/com/example/be/service/PostServiceImpl.java @@ -152,11 +152,11 @@ public CommonDTO.IsSuccessDTO deletePost(Long postId, HttpServletRequest request postRepository.findById(postId).orElseThrow(() -> new PostHandler(ErrorStatus._NOT_FOUND_POST)); - postRepository.findByIdAndUserId(postId, user.getId()).orElseThrow(()-> + Post post = postRepository.findByIdAndUserId(postId, user.getId()).orElseThrow(()-> new PostHandler(ErrorStatus._NOT_USER_POST)); + postRepository.delete(post); + return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build(); } - - } diff --git a/src/main/java/com/example/be/service/UserServiceImpl.java b/src/main/java/com/example/be/service/UserServiceImpl.java index 480f46e..7885e94 100644 --- a/src/main/java/com/example/be/service/UserServiceImpl.java +++ b/src/main/java/com/example/be/service/UserServiceImpl.java @@ -85,31 +85,26 @@ public CommonDTO.IsSuccessDTO login(UserDTO.LoginRequestDto request, HttpServlet String accessToken = jwtUtil.generateAccessToken(user.getUserId(), ACCESS_TOKEN_EXPIRATION_TIME); String origin = httpRequest.getHeader("Origin"); - boolean isLocalhost = origin != null && origin.contains("localhost"); - - // 액세스 토큰 쿠키 설정 - if (isLocalhost) { - // 로컬 개발 환경: SameSite=None, Secure=false - response.addHeader("Set-Cookie", - String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None", - accessToken, (int) (ACCESS_TOKEN_EXPIRATION_TIME / 1000))); - } else { +// boolean isLocalhost = origin != null && origin.contains("localhost"); +// +// // 액세스 토큰 쿠키 설정 +// if (isLocalhost) { +// // 로컬 개발 환경: SameSite=None, Secure=false +// response.addHeader("Set-Cookie", +// String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None", +// accessToken, (int) (ACCESS_TOKEN_EXPIRATION_TIME / 1000))); +// response.addHeader("Set-Cookie", +// String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None", +// refreshToken, (int) (REFRESH_TOKEN_EXPIRATION_TIME / 1000))); +// } else { // 배포 환경: SameSite=None, Secure=true response.addHeader("Set-Cookie", String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; Secure; SameSite=None", accessToken, (int) (ACCESS_TOKEN_EXPIRATION_TIME / 1000))); - } - - // 리프레시 토큰 쿠키 설정 - if (isLocalhost) { - response.addHeader("Set-Cookie", - String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None", - refreshToken, (int) (REFRESH_TOKEN_EXPIRATION_TIME / 1000))); - } else { response.addHeader("Set-Cookie", String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; Secure; SameSite=None", refreshToken, (int) (REFRESH_TOKEN_EXPIRATION_TIME / 1000))); - } +// } return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build(); } @@ -145,23 +140,23 @@ public CommonDTO.IsSuccessDTO logout(HttpServletResponse response, HttpServletRe } // Origin 헤더로 환경 판단 - String origin = request.getHeader("Origin"); - boolean isSecure = origin == null || !origin.contains("localhost"); +// String origin = request.getHeader("Origin"); +// boolean isSecure = origin == null || !origin.contains("localhost"); // 쿠키 삭제 - addHeader 방식 사용 - if (isSecure) { +// if (isSecure) { // 배포 환경 response.addHeader("Set-Cookie", "accessToken=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=None"); response.addHeader("Set-Cookie", "refreshToken=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=None"); - } else { - // 로컬 환경 - response.addHeader("Set-Cookie", - "accessToken=; Path=/; Max-Age=0; HttpOnly; SameSite=None"); - response.addHeader("Set-Cookie", - "refreshToken=; Path=/; Max-Age=0; HttpOnly; SameSite=None"); - } +// } else { +// // 로컬 환경 +// response.addHeader("Set-Cookie", +// "accessToken=; Path=/; Max-Age=0; HttpOnly; SameSite=None"); +// response.addHeader("Set-Cookie", +// "refreshToken=; Path=/; Max-Age=0; HttpOnly; SameSite=None"); +// } return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build(); }