From 1413ee8813b8a9aa824bafc1aa0aca07f3033d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Tue, 21 Jan 2025 00:35:36 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Refactor=20:=20Jwt=EB=A6=AC=ED=8E=99?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20Provider=20->=20manager=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EB=B3=80=EA=B2=BD,=20RefreshToken=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20+=20redis=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../petmatz/api/chatting/ChatController.java | 2 +- .../api/chatting/MatchingController.java | 2 +- .../api/main_page/MainPageController.java | 2 +- .../com/petmatz/api/pet/PetController.java | 2 +- .../api/petmission/PetMissionController.java | 2 +- .../api/sosboard/SosBoardController.java | 2 +- .../user/controller/PastUserController.java | 149 ----- .../api/user/controller/UserController.java | 4 +- .../filter/JwtAuthenticationFilter.java | 8 +- .../security/handler/OAuthSuccessHandler.java | 6 +- .../security/jwt/JwtExtractProvider.java | 7 + .../JwtExtractProviderImpl.java | 9 +- .../JwtProvider.java => jwt/JwtManager.java} | 88 ++- .../security/utils/JwtExtractProvider.java | 7 - .../domain/chatting/ChatRoomService.java | 1 - .../component/ChatMessageUpdater.java | 2 +- .../match/service/MatchScoreService.java | 2 +- .../domain/match/service/MatchService.java | 2 +- .../component/AuthenticationComponent.java | 6 +- .../domain/user/component/UserReader.java | 1 + .../domain/user/component/UserService.java | 67 -- .../domain/user/component/UserUtils.java | 1 - .../response/EditKakaoProfileResponseDto.java | 22 - .../user/response/HeartingResponseDto.java | 21 - .../domain/user/service/AuthService.java | 4 +- .../user/service/CustomOAuthUserService.java | 75 --- .../domain/user/service/HeartService.java | 4 +- .../domain/user/service/KakaoUserService.java | 2 +- .../domain/user/service/LocationService.java | 2 +- .../domain/user/service/PageService.java | 5 +- .../domain/user/service/PasswordService.java | 7 +- .../domain/user/service/RankServiceImpl.java | 2 +- .../domain/user/service/RecommendService.java | 2 +- .../domain/user/service/UserService.java | 98 ++- .../domain/user/service/UserServiceImpl.java | 622 ------------------ .../com/petmatz/infra/jwt/JwtProvider.java | 67 ++ .../redis/component/RedisTokenComponent.java | 23 + 37 files changed, 270 insertions(+), 1058 deletions(-) delete mode 100644 src/main/java/com/petmatz/api/user/controller/PastUserController.java create mode 100644 src/main/java/com/petmatz/common/security/jwt/JwtExtractProvider.java rename src/main/java/com/petmatz/common/security/{utils => jwt}/JwtExtractProviderImpl.java (89%) rename src/main/java/com/petmatz/common/security/{utils/JwtProvider.java => jwt/JwtManager.java} (52%) delete mode 100644 src/main/java/com/petmatz/common/security/utils/JwtExtractProvider.java delete mode 100644 src/main/java/com/petmatz/domain/user/component/UserService.java delete mode 100644 src/main/java/com/petmatz/domain/user/response/EditKakaoProfileResponseDto.java delete mode 100644 src/main/java/com/petmatz/domain/user/response/HeartingResponseDto.java delete mode 100644 src/main/java/com/petmatz/domain/user/service/CustomOAuthUserService.java delete mode 100644 src/main/java/com/petmatz/domain/user/service/UserServiceImpl.java create mode 100644 src/main/java/com/petmatz/infra/jwt/JwtProvider.java create mode 100644 src/main/java/com/petmatz/infra/redis/component/RedisTokenComponent.java diff --git a/src/main/java/com/petmatz/api/chatting/ChatController.java b/src/main/java/com/petmatz/api/chatting/ChatController.java index b49eafe..6320545 100644 --- a/src/main/java/com/petmatz/api/chatting/ChatController.java +++ b/src/main/java/com/petmatz/api/chatting/ChatController.java @@ -2,7 +2,7 @@ import com.petmatz.api.chatting.dto.*; import com.petmatz.api.global.dto.Response; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.chatting.ChatMessageService; import com.petmatz.domain.chatting.ChatRoomService; import com.petmatz.domain.chatting.dto.ChatMessageInfo; diff --git a/src/main/java/com/petmatz/api/chatting/MatchingController.java b/src/main/java/com/petmatz/api/chatting/MatchingController.java index 177906c..1d52aae 100644 --- a/src/main/java/com/petmatz/api/chatting/MatchingController.java +++ b/src/main/java/com/petmatz/api/chatting/MatchingController.java @@ -3,7 +3,7 @@ import com.petmatz.api.chatting.dto.ChatRoomMetaDataInfoResponse; import com.petmatz.api.chatting.dto.MatchRequest; import com.petmatz.api.global.dto.Response; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.chatting.ChatRoomService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; diff --git a/src/main/java/com/petmatz/api/main_page/MainPageController.java b/src/main/java/com/petmatz/api/main_page/MainPageController.java index 33498ff..25479d0 100644 --- a/src/main/java/com/petmatz/api/main_page/MainPageController.java +++ b/src/main/java/com/petmatz/api/main_page/MainPageController.java @@ -2,7 +2,7 @@ import com.petmatz.api.global.dto.Response; import com.petmatz.api.main_page.dto.MainPagePetMissionResponse; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.petmission.PetMissionService; import com.petmatz.domain.petmission.entity.UserToPetMissionEntity; import io.swagger.v3.oas.annotations.Operation; diff --git a/src/main/java/com/petmatz/api/pet/PetController.java b/src/main/java/com/petmatz/api/pet/PetController.java index 1cb7cd4..f547263 100644 --- a/src/main/java/com/petmatz/api/pet/PetController.java +++ b/src/main/java/com/petmatz/api/pet/PetController.java @@ -6,7 +6,7 @@ import com.petmatz.api.pet.dto.PetInfoResponse; import com.petmatz.api.pet.dto.PetRequest; import com.petmatz.api.pet.dto.PetUpdateRequest; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.global.S3ImgDataInfo; import com.petmatz.domain.pet.PetService; import com.petmatz.domain.pet.dto.OpenApiPetInfo; diff --git a/src/main/java/com/petmatz/api/petmission/PetMissionController.java b/src/main/java/com/petmatz/api/petmission/PetMissionController.java index 22ca190..96b33f9 100644 --- a/src/main/java/com/petmatz/api/petmission/PetMissionController.java +++ b/src/main/java/com/petmatz/api/petmission/PetMissionController.java @@ -3,7 +3,7 @@ import com.petmatz.api.global.dto.Response; import com.petmatz.api.petmission.dto.*; import com.petmatz.common.constants.PetMissionStatusZip; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.chatting.ChatMessageService; import com.petmatz.domain.chatting.dto.ChatMessageInfo; import com.petmatz.domain.petmission.PetMissionService; diff --git a/src/main/java/com/petmatz/api/sosboard/SosBoardController.java b/src/main/java/com/petmatz/api/sosboard/SosBoardController.java index c715717..d187f65 100644 --- a/src/main/java/com/petmatz/api/sosboard/SosBoardController.java +++ b/src/main/java/com/petmatz/api/sosboard/SosBoardController.java @@ -3,7 +3,7 @@ import com.petmatz.api.global.dto.Response; import com.petmatz.api.pet.dto.PetResponse; import com.petmatz.api.sosboard.dto.*; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.sosboard.SosBoardService; import com.petmatz.domain.sosboard.dto.*; import com.petmatz.domain.user.entity.User; diff --git a/src/main/java/com/petmatz/api/user/controller/PastUserController.java b/src/main/java/com/petmatz/api/user/controller/PastUserController.java deleted file mode 100644 index f0cd8ad..0000000 --- a/src/main/java/com/petmatz/api/user/controller/PastUserController.java +++ /dev/null @@ -1,149 +0,0 @@ -//package com.petmatz.api.user.controller; -// -//import com.petmatz.api.user.request.*; -//import com.petmatz.domain.user.service.AuthService; -//import com.petmatz.domain.user.response.*; -//import com.petmatz.domain.user.service.UserService; -//import com.petmatz.user.common.LogInResponseDto; -//import jakarta.servlet.http.HttpServletResponse; -//import jakarta.validation.Valid; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.http.ResponseEntity; -//import org.springframework.web.bind.annotation.*; -// -//import java.net.MalformedURLException; -// -//@Slf4j -//@RestController -//@RequiredArgsConstructor -//@RequestMapping("/api/auth") -//public class PastUserController { -// private final UserService userService; -// private final AuthService authService; -// -// @PostMapping("/email-certification") -// public ResponseEntity emailCertification(@RequestBody @Valid EmailCertificationRequestDto requestBody) { -// ResponseEntity response = userService.emailCertification(requestBody); -// log.info("[emailCertification]: { accountId: " + requestBody.getAccountId() + "}"); -// return response; -// } // 하나 했음 -// -// @PostMapping("/check-certification") -// public ResponseEntity checkCertification(@RequestBody @Valid CheckCertificationRequestDto requestBody) { -// ResponseEntity response = userService.checkCertification(CheckCertificationRequestDto.of(requestBody)); -// log.info("[checkCertification]: {accountId: " + requestBody.getAccountId() + ", certificationNumber: " + requestBody.getCertificationNumber() + "}"); -// return response; -// } // 했음 -// -// @PostMapping("/sign-up") -// public ResponseEntity signUp(@RequestBody @Valid SignUpRequestDto requestBody) throws MalformedURLException { -// SignUpResponseDto responseDto = authService.signUp(SignUpRequestDto.of(requestBody)); -// log.info("[signUp]: { accountId: " + requestBody.getAccountId() + ", password: " + requestBody.getPassword()); -// return ResponseEntity.ok(responseDto); -// } // o -// -// @PostMapping("/sign-in") -// public ResponseEntity signIn( -// @RequestBody @Valid SignInRequestDto requestBody, -// HttpServletResponse response) { -// -// ResponseEntity result = userService.signIn(SignInRequestDto.of(requestBody), response); -// log.info("[signIn]: { accountId: " + requestBody.getAccountId() + ", result: " + result.getStatusCode() + " }"); -// return result; -// } // o -// -// @PostMapping("/delete-user") -// public ResponseEntity deleteUser(@RequestBody @Valid DeleteIdRequestDto requestBody) { -// ResponseEntity response = userService.deleteId(requestBody); -// log.info("[deleteUser]:{password: " + requestBody.getPassword() + "}"); -// return response; -// } // o -// -// //---------------------------------------------------------------------------------------------------------------------------------// -// @GetMapping("/get-myprofile") -// public ResponseEntity getMypage() { -// ResponseEntity response = userService.getMypage(); -// log.info("[getMypage]"); -// return response; -// } // o -// -// @GetMapping("/get-otherprofile") -// public ResponseEntity getOtherMypage(@RequestParam @Valid Long userId) { -// ResponseEntity response = userService.getOtherMypage(userId); -// log.info("[getOtherMypage]"); -// return response; -// } // o -// -// @PostMapping("/edit-myprofile") -// public ResponseEntity editMyProfile(@RequestBody @Valid EditMyProfileRequestDto requestBody) { -// ResponseEntity response = userService.editMyProfile(EditMyProfileRequestDto.of(requestBody)); -// log.info("[editMyProfile]"); -// return response; -// } // o -// -// @PostMapping("/send-repassword") -// public ResponseEntity sendRepassword(@RequestBody @Valid SendRepasswordRequestDto requestBody) { -// ResponseEntity response = userService.sendRepassword(requestBody); -// log.info("[sendRepassword]: {accountId: " + requestBody.getAccountId() + "}"); -// return response; -// } // o -// -// @PostMapping("/repassword") -// public ResponseEntity repassword(@RequestBody @Valid RepasswordRequestDto requestBody) { -// ResponseEntity response = userService.repassword(RepasswordRequestDto.of(requestBody)); -// log.info("[repassword]: {currentPassword: " + requestBody.getCurrentPassword() + ", newPassword: " + requestBody.getNewPassword() + "}"); -// return response; -// } // o -// -// //--------------------------------------------------------------------------------------------------------------------------------------------------------------- -// @PostMapping("/hearting") -// public ResponseEntity hearting(@RequestBody @Valid HeartingRequestDto requestBody) { -// ResponseEntity response = userService.hearting(requestBody); -// log.info("[hearting]: {heartedId: " + requestBody.getHeartedId() + "}"); -// return response; -// } // o -// -// @GetMapping("/get-heartlist") -// public ResponseEntity getHeartedList() { -// ResponseEntity response = userService.getHeartedList(); -// log.info("[getHeartedList]"); -// return response; -// } // o -// -// @PostMapping("/update-location") -// public ResponseEntity updateLocation(@RequestBody @Valid UpdateLocationRequestDto requestBody) { -// ResponseEntity response = userService.updateLocation(UpdateLocationRequestDto.of(requestBody)); -// log.info("[updateLocation]"); -// return response; -// } // o -// -// @PostMapping("/update-recommendation") -// public ResponseEntity updateRecommend(@RequestBody @Valid UpdateRecommendationRequestDto requestBody) { -// ResponseEntity response = userService.updateRecommend(requestBody); -// log.info("[updateRecommend]"); -// return response; -// } -// -// @GetMapping("/get-recommended") -// public ResponseEntity getRecommend(@RequestBody @Valid UpdateRecommendationRequestDto requestBody) { -// ResponseEntity response = userService.getRecommend(requestBody); -// log.info("[getRecommend]"); -// return response; -// } -// -// @PostMapping("/logout") -// public ResponseEntity logout(HttpServletResponse res) { -// ResponseEntity response = userService.logout(res); -// log.info("[logout]"); -// return response; -// } -// -// @PostMapping("/edit-kakaoprofile") -// public ResponseEntity editKakaoProfile(@RequestBody @Valid EditKakaoProfileRequestDto requestBody) { -// ResponseEntity response = userService.editKakaoProfile(EditKakaoProfileRequestDto.of(requestBody)); -// log.info("[editKakaoProfile]"); -// return response; -// } -//} -// diff --git a/src/main/java/com/petmatz/api/user/controller/UserController.java b/src/main/java/com/petmatz/api/user/controller/UserController.java index 2c35b30..f7728be 100644 --- a/src/main/java/com/petmatz/api/user/controller/UserController.java +++ b/src/main/java/com/petmatz/api/user/controller/UserController.java @@ -4,11 +4,9 @@ import com.petmatz.api.user.request.DeleteIdRequestDto; import com.petmatz.api.user.request.EditKakaoProfileRequestDto; import com.petmatz.domain.user.service.KakaoUserService; -import com.petmatz.domain.user.component.UserService; -import com.petmatz.domain.user.response.EditKakaoProfileResponseDto; +import com.petmatz.domain.user.service.UserService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController diff --git a/src/main/java/com/petmatz/common/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/petmatz/common/security/filter/JwtAuthenticationFilter.java index 07fa20e..7a78928 100644 --- a/src/main/java/com/petmatz/common/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/petmatz/common/security/filter/JwtAuthenticationFilter.java @@ -1,6 +1,6 @@ package com.petmatz.common.security.filter; -import com.petmatz.common.security.utils.JwtProvider; +import com.petmatz.common.security.jwt.JwtManager; import com.petmatz.domain.user.constant.LoginRole; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.repository.UserRepository; @@ -34,7 +34,7 @@ @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { private final UserRepository userRepository; - private final JwtProvider jwtProvider; + private final JwtManager jwtManager; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { @@ -45,11 +45,13 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (token == null) { filterChain.doFilter(request, response); return; + // 여기 예외 처리해야댐 } // JWT 유효성 검증 및 사용자 ID 추출 - Long userId = jwtProvider.validateAndGetUserId(token); // validate 메서드가 userId를 반환하도록 수정 + Long userId = jwtManager.validateAndGetUserId(token); // validate 메서드가 userId를 반환하도록 수정 if (userId == null) { + getRefreshTokenFromCookies filterChain.doFilter(request, response); return; } diff --git a/src/main/java/com/petmatz/common/security/handler/OAuthSuccessHandler.java b/src/main/java/com/petmatz/common/security/handler/OAuthSuccessHandler.java index 8abb7e9..78d1835 100644 --- a/src/main/java/com/petmatz/common/security/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/petmatz/common/security/handler/OAuthSuccessHandler.java @@ -1,6 +1,6 @@ package com.petmatz.common.security.handler; -import com.petmatz.common.security.utils.JwtProvider; +import com.petmatz.common.security.jwt.JwtManager; import com.petmatz.domain.user.entity.CustomOAuthUser; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.repository.UserRepository; @@ -19,7 +19,7 @@ @RequiredArgsConstructor public class OAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - private final JwtProvider jwtProvider; + private final JwtManager jwtManager; private final UserRepository userRepository; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { @@ -30,7 +30,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String accountId = oAuth2User.getName(); // JWT 생성 - String token = jwtProvider.create(userId, accountId); + String token = jwtManager.createAccessToken(userId, accountId); // JWT 쿠키 설정 ResponseCookie jwtCookie = ResponseCookie.from("jwt", token) diff --git a/src/main/java/com/petmatz/common/security/jwt/JwtExtractProvider.java b/src/main/java/com/petmatz/common/security/jwt/JwtExtractProvider.java new file mode 100644 index 0000000..89ec77e --- /dev/null +++ b/src/main/java/com/petmatz/common/security/jwt/JwtExtractProvider.java @@ -0,0 +1,7 @@ +package com.petmatz.common.security.jwt; + + +public interface JwtExtractProvider { + Long findIdFromJwt(); + String findAccountIdFromJwt(); // Email 로도 사용 +} diff --git a/src/main/java/com/petmatz/common/security/utils/JwtExtractProviderImpl.java b/src/main/java/com/petmatz/common/security/jwt/JwtExtractProviderImpl.java similarity index 89% rename from src/main/java/com/petmatz/common/security/utils/JwtExtractProviderImpl.java rename to src/main/java/com/petmatz/common/security/jwt/JwtExtractProviderImpl.java index 97930bd..07bb744 100644 --- a/src/main/java/com/petmatz/common/security/utils/JwtExtractProviderImpl.java +++ b/src/main/java/com/petmatz/common/security/jwt/JwtExtractProviderImpl.java @@ -1,6 +1,7 @@ -package com.petmatz.common.security.utils; +package com.petmatz.common.security.jwt; import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.infra.redis.component.RedisTokenComponent; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -13,7 +14,7 @@ @RequiredArgsConstructor public class JwtExtractProviderImpl implements JwtExtractProvider { - private final JwtProvider jwtProvider; // JWT를 검증하고 ID를 추출하는 클래스 + private final JwtManager jwtManager; // JWT를 검증하고 ID를 추출하는 클래스 private final UserRepository userRepository; @Override @@ -33,7 +34,7 @@ public Long findIdFromJwt() { return (Long) principal; // Principal이 Long 타입인 경우 직접 반환 } else if (principal instanceof String) { // Principal이 String인 경우 JWT에서 ID 추출 - return jwtProvider.validateAndGetUserId((String) principal); + return jwtManager.validateAndGetUserId((String) principal); } else { throw new IllegalArgumentException("Invalid principal type: " + principal.getClass().getName()); } @@ -63,7 +64,7 @@ public String findAccountIdFromJwt() { return userRepository.findAccountIdByUserId(userId); // Repository 메서드 사용 } else if (principal instanceof String) { // Principal이 String 타입인 경우 JWT로 간주하고 accountId 추출 - Map claims = jwtProvider.validate((String) principal); + Map claims = jwtManager.validate((String) principal); if (claims != null && claims.containsKey("accountId")) { return (String) claims.get("accountId"); } diff --git a/src/main/java/com/petmatz/common/security/utils/JwtProvider.java b/src/main/java/com/petmatz/common/security/jwt/JwtManager.java similarity index 52% rename from src/main/java/com/petmatz/common/security/utils/JwtProvider.java rename to src/main/java/com/petmatz/common/security/jwt/JwtManager.java index b7ebc52..4db1706 100644 --- a/src/main/java/com/petmatz/common/security/utils/JwtProvider.java +++ b/src/main/java/com/petmatz/common/security/jwt/JwtManager.java @@ -1,12 +1,16 @@ -package com.petmatz.common.security.utils; +package com.petmatz.common.security.jwt; +import com.petmatz.infra.redis.component.RedisTokenComponent; +import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.security.Key; import java.time.Instant; @@ -19,25 +23,32 @@ * JWT 토큰 생성 및 검증을 담당하는 클래스. * 주어진 사용자 ID로 JWT 토큰을 생성 -> 토큰의 유효성을 검증하여 사용자 ID를 반환 */ + @Component @Slf4j -public class JwtProvider { +@RequiredArgsConstructor +public class JwtManager { + + @Value("${secret-access-key}") + private String accessKey; - // JWT 토큰의 서명에 사용할 비밀 키 - @Value("${secret-key}") - private String secretKey; + @Value("${secret-refresh-key}") + private String refreshKey; + + private final RedisTokenComponent redisTokenComponent; /** * 주어진 사용자 ID와 계정 ID로 JWT 토큰을 생성하는 메서드. * 토큰은 1시간 동안 유효하며, 사용자 ID를 서브젝트로 설정하고 계정 ID를 클레임으로 추가. + * * @return 생성된 JWT 토큰 문자열 */ - public String create(Long userId, String accountId) { + public String createAccessToken(Long userId, String accountId) { // 토큰 만료 시간 설정 (1시간 후) Date expireDate = Date.from(Instant.now().plus(24, ChronoUnit.HOURS)); // 비밀 키 생성 - Key key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + Key key = Keys.hmacShaKeyFor(accessKey.getBytes(StandardCharsets.UTF_8)); // JWT 토큰 생성 String jwt = Jwts.builder() @@ -51,13 +62,25 @@ public String create(Long userId, String accountId) { return jwt; } - /** - * 주어진 JWT 토큰의 유효성을 검증하고, 사용자 ID와 계정 ID를 반환. - * @param jwt JWT 토큰 - * @return 유효한 경우 사용자 ID와 계정 ID를 포함한 맵 반환, 유효하지 않은 경우 null 반환 - */ + public String createRefreshToken(Long userId) { + Date expireDate = Date.from(Instant.now().plus(2, ChronoUnit.WEEKS)); + SecretKey secretKey = Keys.hmacShaKeyFor(refreshKey.getBytes(StandardCharsets.UTF_8)); + + String refreshToken = Jwts.builder() + .signWith(secretKey, SignatureAlgorithm.RS256) + .setSubject(userId.toString()) + .setIssuedAt(new Date()) + .setExpiration(expireDate) + .compact(); + + redisTokenComponent.saveRefreshToken(userId, refreshToken); + + return refreshToken; + } + + // 토큰 검증 public Map validate(String jwt) { - Key key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + Key key = Keys.hmacShaKeyFor(accessKey.getBytes(StandardCharsets.UTF_8)); try { var claims = Jwts.parserBuilder() @@ -77,15 +100,18 @@ public Map validate(String jwt) { result.put("accountId", accountId); return result; + } catch (ExpiredJwtException e) { + log.warn("Access Token expired: {}", jwt); + return null; } catch (Exception e) { - e.printStackTrace(); - return null; // 예외 발생 시 null 반환 + log.error("Invalid Access Token: {}", jwt, e); + return null; } } public Long validateAndGetUserId(String token) { try { - Key key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + Key key = Keys.hmacShaKeyFor(accessKey.getBytes(StandardCharsets.UTF_8)); String subject = Jwts.parserBuilder() .setSigningKey(key) .build() @@ -94,10 +120,38 @@ public Long validateAndGetUserId(String token) { .getSubject(); return Long.parseLong(subject); // subject를 Long 타입으로 변환 + } catch (ExpiredJwtException e) { + log.warn("Access Token expired" + e); + return null; } catch (Exception e) { - e.printStackTrace(); + log.error("Invalid Access Token: {}" + e); return null; } } + // refresh Token 으로 재발급 + public String refreshAccessToken(String refreshToken) { + try { + SecretKey refreshKey = Keys.hmacShaKeyFor(this.refreshKey.getBytes(StandardCharsets.UTF_8)); + + // Refresh Token 검증 및 파싱 + var claims = Jwts.parserBuilder() + .setSigningKey(refreshKey) + .build() + .parseClaimsJws(refreshToken) + .getBody(); + + Long userId = Long.parseLong(claims.getSubject()); + + return createRefreshToken(userId); + } catch (ExpiredJwtException e) { + // Refresh Token 만료 시 예외 처리 + log.error("Refresh token has expired." + e); + return null; + } catch (Exception e) { + // 기타 검증 실패 시 예외 처리 + log.error("Invalid refresh token." + e); + return null; + } + } } \ No newline at end of file diff --git a/src/main/java/com/petmatz/common/security/utils/JwtExtractProvider.java b/src/main/java/com/petmatz/common/security/utils/JwtExtractProvider.java deleted file mode 100644 index 7e1a39a..0000000 --- a/src/main/java/com/petmatz/common/security/utils/JwtExtractProvider.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.petmatz.common.security.utils; - - -public interface JwtExtractProvider { - Long findIdFromJwt(); - String findAccountIdFromJwt(); -} diff --git a/src/main/java/com/petmatz/domain/chatting/ChatRoomService.java b/src/main/java/com/petmatz/domain/chatting/ChatRoomService.java index 971d4a5..1da31c2 100644 --- a/src/main/java/com/petmatz/domain/chatting/ChatRoomService.java +++ b/src/main/java/com/petmatz/domain/chatting/ChatRoomService.java @@ -1,6 +1,5 @@ package com.petmatz.domain.chatting; -import com.petmatz.common.security.utils.JwtExtractProvider; import com.petmatz.domain.chatting.component.*; import com.petmatz.domain.chatting.docs.ChatReadStatusDocs; import com.petmatz.domain.chatting.dto.ChatRoomInfo; diff --git a/src/main/java/com/petmatz/domain/chatting/component/ChatMessageUpdater.java b/src/main/java/com/petmatz/domain/chatting/component/ChatMessageUpdater.java index e33b4f7..f798576 100644 --- a/src/main/java/com/petmatz/domain/chatting/component/ChatMessageUpdater.java +++ b/src/main/java/com/petmatz/domain/chatting/component/ChatMessageUpdater.java @@ -1,6 +1,6 @@ package com.petmatz.domain.chatting.component; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.chatting.docs.ChatReadStatusDocs; import com.petmatz.domain.chatting.docs.ChatRoomDocs; import com.petmatz.domain.chatting.docs.ChatRoomMetadataDocs; diff --git a/src/main/java/com/petmatz/domain/match/service/MatchScoreService.java b/src/main/java/com/petmatz/domain/match/service/MatchScoreService.java index f388e83..2044dd9 100644 --- a/src/main/java/com/petmatz/domain/match/service/MatchScoreService.java +++ b/src/main/java/com/petmatz/domain/match/service/MatchScoreService.java @@ -1,6 +1,6 @@ package com.petmatz.domain.match.service; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.match.component.BoundingBoxCalculator; import com.petmatz.domain.match.component.MatchScoreCalculator; import com.petmatz.domain.match.component.MatchScoreProcessor; diff --git a/src/main/java/com/petmatz/domain/match/service/MatchService.java b/src/main/java/com/petmatz/domain/match/service/MatchService.java index 3ec21bd..3b1d015 100644 --- a/src/main/java/com/petmatz/domain/match/service/MatchService.java +++ b/src/main/java/com/petmatz/domain/match/service/MatchService.java @@ -1,7 +1,7 @@ package com.petmatz.domain.match.service; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.match.component.MatchScoreProcessor; import com.petmatz.domain.match.component.UserMapper; import com.petmatz.domain.match.dto.response.DetailedMatchResultResponse; diff --git a/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java b/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java index b55f55a..1fb88e7 100644 --- a/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java @@ -1,6 +1,6 @@ package com.petmatz.domain.user.component; -import com.petmatz.common.security.utils.JwtProvider; +import com.petmatz.common.security.jwt.JwtManager; import com.petmatz.domain.user.entity.Certification; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.exception.UserException; @@ -26,7 +26,7 @@ public class AuthenticationComponent { private final CertificationRepository certificationRepository; private final UserRepository userRepository; - private final JwtProvider jwtProvider; + private final JwtManager jwtManager; private final UserUtils userUtils; private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); @@ -44,7 +44,7 @@ public User validateSignInCredentials(SignInInfo info) throws AuthenticationExce } public String createJwtToken(User user) { - return jwtProvider.create(user.getId(), user.getAccountId()); + return jwtManager.createAccessToken(user.getId(), user.getAccountId()); } /** diff --git a/src/main/java/com/petmatz/domain/user/component/UserReader.java b/src/main/java/com/petmatz/domain/user/component/UserReader.java index 8e54c8d..e738d9c 100644 --- a/src/main/java/com/petmatz/domain/user/component/UserReader.java +++ b/src/main/java/com/petmatz/domain/user/component/UserReader.java @@ -15,6 +15,7 @@ public class UserReader { /** * 이것도 utils를 만들어서 곧 지울듯 + * 종원님 이거 다 쓰시면 지워주세욥 */ private final UserRepository userRepository; diff --git a/src/main/java/com/petmatz/domain/user/component/UserService.java b/src/main/java/com/petmatz/domain/user/component/UserService.java deleted file mode 100644 index 41fe880..0000000 --- a/src/main/java/com/petmatz/domain/user/component/UserService.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.petmatz.domain.user.component; - -import com.petmatz.api.user.request.DeleteIdRequestDto; -import com.petmatz.common.security.utils.JwtExtractProvider; -import com.petmatz.domain.pet.entity.Pet; -import com.petmatz.domain.pet.repository.PetRepository; -import com.petmatz.domain.user.entity.User; -import com.petmatz.domain.user.info.UserInfo; -import com.petmatz.domain.user.repository.CertificationRepository; -import com.petmatz.domain.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Component -@RequiredArgsConstructor -@Slf4j -public class UserService { - - /** - * 여기 종원님이랑 머지하고 펫레포 최신버전 받아오고 수정 예정 25.01.14 - */ - - private final UserRepository userRepository; - private final PetRepository petRepository; - - - private final CertificationRepository certificationRepository; - private final JwtExtractProvider jwtExtractProvider; - private final UserUtils userUtils; - private final PasswordComponent passwordComponent; - - - @Transactional - public void deleteId(DeleteIdRequestDto dto) { - Long userId = jwtExtractProvider.findIdFromJwt(); - User user = userUtils.findIdUser(userId); - - String password = dto.getPassword(); - String encodedPassword = user.getPassword(); - - passwordComponent.validatePassword(password, encodedPassword); - certificationRepository.deleteById(userId); - // 사용자 삭제 - List pets = petRepository.findAllByUserId(user.getId()); // Pet 엔티티에서 User를 참조하는 기준으로 조회 - // 명시적으로 Pet 삭제 - petRepository.deleteAll(pets); - userRepository.delete(user); - } - - - public UserInfo selectUserInfo(String receiverEmail) { - User otherUser = userRepository.findByAccountId(receiverEmail); - return otherUser.of(); - } - - public String findByUserEmail(Long userId) { - return userRepository.findById(userId).get().getAccountId(); - } - - public void deleteUser(Long userUUID) { - userRepository.deleteUserById(userUUID); - } -} diff --git a/src/main/java/com/petmatz/domain/user/component/UserUtils.java b/src/main/java/com/petmatz/domain/user/component/UserUtils.java index 42df080..5adc216 100644 --- a/src/main/java/com/petmatz/domain/user/component/UserUtils.java +++ b/src/main/java/com/petmatz/domain/user/component/UserUtils.java @@ -1,6 +1,5 @@ package com.petmatz.domain.user.component; -import com.petmatz.common.security.utils.JwtExtractProvider; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.exception.UserException; import com.petmatz.domain.user.repository.HeartRepository; diff --git a/src/main/java/com/petmatz/domain/user/response/EditKakaoProfileResponseDto.java b/src/main/java/com/petmatz/domain/user/response/EditKakaoProfileResponseDto.java deleted file mode 100644 index 59cda5a..0000000 --- a/src/main/java/com/petmatz/domain/user/response/EditKakaoProfileResponseDto.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.petmatz.domain.user.response; - -import com.petmatz.user.common.LogInResponseDto; -import com.petmatz.user.common.ResponseCode; -import com.petmatz.user.common.ResponseMessage; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; - -public class EditKakaoProfileResponseDto extends LogInResponseDto { - public EditKakaoProfileResponseDto(){ - } - - public static ResponseEntity idNotFound(){ - LogInResponseDto responseBody = new LogInResponseDto(ResponseCode.ID_NOT_FOUND, ResponseMessage.ID_NOT_FOUND); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(responseBody); - } - - public static ResponseEntity editFailed(){ - LogInResponseDto responseBody = new LogInResponseDto(ResponseCode.EDIT_FAIL, ResponseMessage.EDIT_FAIL); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(responseBody); - } -} diff --git a/src/main/java/com/petmatz/domain/user/response/HeartingResponseDto.java b/src/main/java/com/petmatz/domain/user/response/HeartingResponseDto.java deleted file mode 100644 index dad0643..0000000 --- a/src/main/java/com/petmatz/domain/user/response/HeartingResponseDto.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.petmatz.domain.user.response; - -import com.petmatz.user.common.LogInResponseDto; -import com.petmatz.user.common.ResponseCode; -import com.petmatz.user.common.ResponseMessage; -import lombok.Getter; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; - -@Getter -public class HeartingResponseDto extends LogInResponseDto { - - private HeartingResponseDto(){ - super(); - } - - public static ResponseEntity heartedIdNotFound(){ - LogInResponseDto responseBody = new LogInResponseDto(ResponseCode.HEARTED_ID_NOT_FOUND, ResponseMessage.HEARTED_ID_NOT_FOUND); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(responseBody); - } -} diff --git a/src/main/java/com/petmatz/domain/user/service/AuthService.java b/src/main/java/com/petmatz/domain/user/service/AuthService.java index 4d1bc78..a4e2ef7 100644 --- a/src/main/java/com/petmatz/domain/user/service/AuthService.java +++ b/src/main/java/com/petmatz/domain/user/service/AuthService.java @@ -1,6 +1,6 @@ package com.petmatz.domain.user.service; -import com.petmatz.common.security.utils.JwtProvider; +import com.petmatz.common.security.jwt.JwtManager; import com.petmatz.domain.aws.AwsClient; import com.petmatz.domain.aws.vo.S3Imge; import com.petmatz.domain.user.component.AuthenticationComponent; @@ -43,7 +43,7 @@ public class AuthService { private final CertificationRepository certificationRepository; private final GeocodingComponent geocodingComponent; private final AwsClient awsClient; // 추후에 수정 - private final JwtProvider jwtProvider; + private final JwtManager jwtManager; private final AuthenticationComponent authenticationComponent; private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); diff --git a/src/main/java/com/petmatz/domain/user/service/CustomOAuthUserService.java b/src/main/java/com/petmatz/domain/user/service/CustomOAuthUserService.java deleted file mode 100644 index 5ec17e2..0000000 --- a/src/main/java/com/petmatz/domain/user/service/CustomOAuthUserService.java +++ /dev/null @@ -1,75 +0,0 @@ -//package com.petmatz.domain.user.service; -// -//import com.petmatz.domain.user.constant.LoginRole; -//import com.petmatz.domain.user.constant.LoginType; -//import com.petmatz.domain.user.entity.CustomOAuthUser; -//import com.petmatz.domain.user.entity.User; -//import com.petmatz.domain.user.repository.UserRepository; -//import lombok.RequiredArgsConstructor; -//import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; -//import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -//import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -//import org.springframework.security.oauth2.core.user.OAuth2User; -//import org.springframework.stereotype.Service; -// -//import java.util.Map; -// -//@Service -//@RequiredArgsConstructor -//public class CustomOAuthUserService extends DefaultOAuth2UserService { -// -// private final UserRepository userRepository; -// -// @Override -// public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { -// OAuth2User oAuth2User = super.loadUser(userRequest); -// Map attributes = oAuth2User.getAttributes(); -// -// // 1. Check the registration ID (ensure it's Kakao) -// String registrationId = userRequest.getClientRegistration().getRegistrationId(); -// if (!"kakao".equals(registrationId)) { -// throw new OAuth2AuthenticationException("Unsupported registrationId: " + registrationId); -// } -// -// // 2. Extract attributes -// String kakaoAccountId = attributes.get("id").toString(); // 고유 ID -// Map kakaoAccount = (Map) attributes.get("kakao_account"); -// -// // Extract email (required for accountId) -// String email = (String) kakaoAccount.get("email"); -// if (email == null || email.isEmpty()) { -// throw new OAuth2AuthenticationException("Email is required for Kakao login."); -// } -// -// // Extract nickname -// Map profile = (Map) kakaoAccount.get("profile"); -// String nickname = (String) profile.getOrDefault("nickname", "Unknown User"); -// -// // Extract profile image (optional) -// String profileImage = (String) profile.getOrDefault("profile_image_url", ""); -// -// // 3. Check if user exists or create new user -// User user = userRepository.findByAccountId(email); -// if (user == null) { -// user = createNewKakaoUser(email, nickname, profileImage); // 신규 사용자 생성 -// } -// -// // 4. Return CustomOAuthUser -// return new CustomOAuthUser(user.getId(), user.getAccountId(), attributes, oAuth2User.getAuthorities()); -// } -// -// private User createNewKakaoUser(String email, String nickname, String profileImage) { -// User newUser = User.builder() -// .accountId(email) // 이메일을 accountId에 저장 -// .password("password") // 기본 비밀번호 설정 (필요시 변경) -// .nickname(nickname) -// .profileImg(profileImage) // 프로필 이미지 저장 -// .loginRole(LoginRole.ROLE_USER) // 기본 역할 설정 -// .loginType(LoginType.KAKAO) // 로그인 타입 -// .careCompletionCount(0) -// .recommendationCount(0) -// .build(); -// -// return userRepository.save(newUser); -// } -//} \ No newline at end of file diff --git a/src/main/java/com/petmatz/domain/user/service/HeartService.java b/src/main/java/com/petmatz/domain/user/service/HeartService.java index fecbfdb..7bf51cf 100644 --- a/src/main/java/com/petmatz/domain/user/service/HeartService.java +++ b/src/main/java/com/petmatz/domain/user/service/HeartService.java @@ -2,12 +2,10 @@ import com.petmatz.api.user.request.HeartedUserDto; import com.petmatz.api.user.request.HeartingRequestDto; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.user.component.HeartComponent; import com.petmatz.domain.user.component.UserUtils; import com.petmatz.domain.user.entity.User; -import com.petmatz.domain.user.repository.HeartRepository; -import com.petmatz.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; diff --git a/src/main/java/com/petmatz/domain/user/service/KakaoUserService.java b/src/main/java/com/petmatz/domain/user/service/KakaoUserService.java index e04dd27..638a3aa 100644 --- a/src/main/java/com/petmatz/domain/user/service/KakaoUserService.java +++ b/src/main/java/com/petmatz/domain/user/service/KakaoUserService.java @@ -1,6 +1,6 @@ package com.petmatz.domain.user.service; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.user.component.GeocodingComponent; import com.petmatz.domain.user.component.UserUtils; import com.petmatz.domain.user.constant.LoginRole; diff --git a/src/main/java/com/petmatz/domain/user/service/LocationService.java b/src/main/java/com/petmatz/domain/user/service/LocationService.java index 82649b1..0db8c8a 100644 --- a/src/main/java/com/petmatz/domain/user/service/LocationService.java +++ b/src/main/java/com/petmatz/domain/user/service/LocationService.java @@ -1,6 +1,6 @@ package com.petmatz.domain.user.service; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.user.component.UserUtils; import com.petmatz.domain.user.entity.KakaoRegion; import com.petmatz.domain.user.entity.User; diff --git a/src/main/java/com/petmatz/domain/user/service/PageService.java b/src/main/java/com/petmatz/domain/user/service/PageService.java index add1de4..964553d 100644 --- a/src/main/java/com/petmatz/domain/user/service/PageService.java +++ b/src/main/java/com/petmatz/domain/user/service/PageService.java @@ -1,18 +1,15 @@ package com.petmatz.domain.user.service; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.aws.AwsClient; import com.petmatz.domain.aws.vo.S3Imge; import com.petmatz.domain.user.component.UserUtils; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.info.EditMyProfileInfo; -import com.petmatz.domain.user.repository.HeartRepository; -import com.petmatz.domain.user.repository.UserRepository; import com.petmatz.domain.user.response.EditMyProfileResponseDto; import com.petmatz.domain.user.response.GetMyProfileResponseDto; import com.petmatz.domain.user.response.GetOtherProfileResponseDto; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/com/petmatz/domain/user/service/PasswordService.java b/src/main/java/com/petmatz/domain/user/service/PasswordService.java index 87b3c35..96ecead 100644 --- a/src/main/java/com/petmatz/domain/user/service/PasswordService.java +++ b/src/main/java/com/petmatz/domain/user/service/PasswordService.java @@ -1,20 +1,15 @@ package com.petmatz.domain.user.service; import com.petmatz.api.user.request.SendRepasswordRequestDto; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.user.component.PasswordComponent; import com.petmatz.domain.user.component.UserUtils; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.info.RepasswordInfo; import com.petmatz.domain.user.provider.RePasswordProvider; -import com.petmatz.domain.user.response.RepasswordResponseDto; import com.petmatz.domain.user.response.SendRepasswordResponseDto; -import com.petmatz.domain.user.service.RePasswordEmailProvider; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/com/petmatz/domain/user/service/RankServiceImpl.java b/src/main/java/com/petmatz/domain/user/service/RankServiceImpl.java index 8cd4a83..4e12723 100644 --- a/src/main/java/com/petmatz/domain/user/service/RankServiceImpl.java +++ b/src/main/java/com/petmatz/domain/user/service/RankServiceImpl.java @@ -1,6 +1,6 @@ package com.petmatz.domain.user.service; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.repository.UserRepository; import com.petmatz.domain.user.response.RankUserResponse; diff --git a/src/main/java/com/petmatz/domain/user/service/RecommendService.java b/src/main/java/com/petmatz/domain/user/service/RecommendService.java index 8e75cf6..5357eab 100644 --- a/src/main/java/com/petmatz/domain/user/service/RecommendService.java +++ b/src/main/java/com/petmatz/domain/user/service/RecommendService.java @@ -1,7 +1,7 @@ package com.petmatz.domain.user.service; import com.petmatz.api.user.request.UpdateRecommendationRequestDto; -import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.jwt.JwtExtractProvider; import com.petmatz.domain.user.component.RecommendComponent; import com.petmatz.domain.user.component.UserUtils; import com.petmatz.domain.user.entity.Recommendation; diff --git a/src/main/java/com/petmatz/domain/user/service/UserService.java b/src/main/java/com/petmatz/domain/user/service/UserService.java index a6166a7..3c57bb8 100644 --- a/src/main/java/com/petmatz/domain/user/service/UserService.java +++ b/src/main/java/com/petmatz/domain/user/service/UserService.java @@ -1,35 +1,69 @@ package com.petmatz.domain.user.service; -import com.petmatz.api.user.request.*; -import com.petmatz.domain.user.info.*; -import com.petmatz.domain.user.response.*; -import com.petmatz.user.common.LogInResponseDto; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.http.ResponseEntity; - -public interface UserService { -// ResponseEntity emailCertification(EmailCertificationRequestDto dto); -// ResponseEntity checkCertification(CheckCertificationInfo info); -// ResponseEntity signUp(SignUpInfo info); -// ResponseEntity signIn(SignInInfo info, HttpServletResponse response); -// ResponseEntity logout(HttpServletResponse response); -// ResponseEntity deleteId(DeleteIdRequestDto dto); -// ResponseEntity getMypage(); -// ResponseEntity getOtherMypage(Long userId); -// ResponseEntity sendRepassword(SendRepasswordRequestDto dto); -// ResponseEntity repassword(RepasswordInfo info); -// ResponseEntity editMyProfile(EditMyProfileInfo info); -// ResponseEntity hearting(HeartingRequestDto dto); -// ResponseEntity getHeartedList(); -// ResponseEntity updateLocation(UpdateLocationInfo info); -// ResponseEntity updateRecommend(UpdateRecommendationRequestDto dto); -// ResponseEntity editKakaoProfile(EditKakaoProfileInfo of); - - String findByUserEmail(Long userId); - - GetMyUserDto receiverEmail(String accountId); - - void deleteUser(Long accountId); - - ResponseEntity getRecommend(UpdateRecommendationRequestDto dto); +import com.petmatz.api.user.request.DeleteIdRequestDto; +import com.petmatz.common.security.jwt.JwtExtractProvider; +import com.petmatz.domain.pet.entity.Pet; +import com.petmatz.domain.pet.repository.PetRepository; +import com.petmatz.domain.user.component.PasswordComponent; +import com.petmatz.domain.user.component.UserUtils; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.info.UserInfo; +import com.petmatz.domain.user.repository.CertificationRepository; +import com.petmatz.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Component +@RequiredArgsConstructor +@Slf4j +public class UserService { + + /** + * 여기 종원님이랑 머지하고 펫레포 최신버전 받아오고 수정 예정 25.01.14 + */ + + private final UserRepository userRepository; + private final PetRepository petRepository; + + + private final CertificationRepository certificationRepository; + private final JwtExtractProvider jwtExtractProvider; + private final UserUtils userUtils; + private final PasswordComponent passwordComponent; + + + @Transactional + public void deleteId(DeleteIdRequestDto dto) { + Long userId = jwtExtractProvider.findIdFromJwt(); + User user = userUtils.findIdUser(userId); + + String password = dto.getPassword(); + String encodedPassword = user.getPassword(); + + passwordComponent.validatePassword(password, encodedPassword); + certificationRepository.deleteById(userId); + // 사용자 삭제 + List pets = petRepository.findAllByUserId(user.getId()); // Pet 엔티티에서 User를 참조하는 기준으로 조회 + // 명시적으로 Pet 삭제 + petRepository.deleteAll(pets); + userRepository.delete(user); + } + + + public UserInfo selectUserInfo(String receiverEmail) { + User otherUser = userRepository.findByAccountId(receiverEmail); + return otherUser.of(); + } + + public String findByUserEmail(Long userId) { + return userRepository.findById(userId).get().getAccountId(); + } + + public void deleteUser(Long userUUID) { + userRepository.deleteUserById(userUUID); + } } diff --git a/src/main/java/com/petmatz/domain/user/service/UserServiceImpl.java b/src/main/java/com/petmatz/domain/user/service/UserServiceImpl.java deleted file mode 100644 index dc09236..0000000 --- a/src/main/java/com/petmatz/domain/user/service/UserServiceImpl.java +++ /dev/null @@ -1,622 +0,0 @@ -//package com.petmatz.domain.user.service; -// -//import com.petmatz.api.user.request.*; -//import com.petmatz.common.security.utils.JwtExtractProvider; -//import com.petmatz.common.security.utils.JwtProvider; -//import com.petmatz.domain.aws.AwsClient; -//import com.petmatz.domain.aws.vo.S3Imge; -//import com.petmatz.domain.pet.entity.Pet; -//import com.petmatz.domain.pet.repository.PetRepository; -//import com.petmatz.domain.user.component.GeocodingComponent; -//import com.petmatz.domain.user.entity.*; -//import com.petmatz.domain.user.info.*; -//import com.petmatz.domain.user.provider.CertificationNumberProvider; -//import com.petmatz.domain.user.provider.RePasswordProvider; -//import com.petmatz.domain.user.repository.CertificationRepository; -//import com.petmatz.domain.user.repository.HeartRepository; -//import com.petmatz.domain.user.repository.RecommendationRepository; -//import com.petmatz.domain.user.repository.UserRepository; -//import com.petmatz.domain.user.response.*; -//import com.petmatz.user.common.LogInResponseDto; -//import jakarta.servlet.http.HttpServletResponse; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.http.ResponseCookie; -//import org.springframework.http.ResponseEntity; -//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -//import org.springframework.security.crypto.password.PasswordEncoder; -//import org.springframework.stereotype.Service; -//import org.springframework.transaction.annotation.Transactional; -// -//import java.time.LocalDateTime; -//import java.util.List; -//import java.util.Optional; -// -//@Service -//@Slf4j -//@RequiredArgsConstructor -//public class UserServiceImpl implements UserService { -// -// private final UserRepository userRepository; -// private final CertificationRepository certificationRepository; -// private final HeartRepository heartRepository; -// private final RecommendationRepository recommendationRepository; -// private final JwtProvider jwtProvider; -// private final EmailProvider emailProvider; -// private final RePasswordEmailProvider rePasswordEmailProvider; -// private final PetRepository petRepository; -// -// private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); -// private final JwtExtractProvider jwtExtractProvider; -// -// private final GeocodingComponent geocodingComponent; -// -// private final AwsClient awsClient; -// -// -// @Override -// public ResponseEntity emailCertification(EmailCertificationRequestDto dto) { -// try { -// String accountId = dto.getAccountId(); -// -// //이메일 전송과 동시에 아이디 중복검사 -// boolean isExistId = userRepository.existsByAccountId(accountId); -// if (isExistId) return EmailCertificationResponseDto.duplicateId(); -// -// // 인증 번호 생성 및 이메일 전송 -// String certificationNumber = CertificationNumberProvider.generateNumber(); -// boolean isSendSuccess = emailProvider.sendVerificationEmail(accountId, certificationNumber); -// if (!isSendSuccess) return EmailCertificationResponseDto.mailSendFail(); -// -// // 인증 엔티티 저장 -// Certification certification = Certification.builder().accountId(accountId).certificationNumber(certificationNumber).isVerified(false).build(); -// certificationRepository.save(certification); -// -// } catch (Exception e) { -// log.info("이메일 인증 실패: {}", e); -// return EmailCertificationResponseDto.databaseError(); -// } -// return EmailCertificationResponseDto.success(); -// } -// -// -// @Override -// @Transactional -// public ResponseEntity checkCertification(CheckCertificationInfo info) { -// try { -// String accountId = info.getAccountId(); -// String certificationNumber = info.getCertificationNumber(); -// -// Certification certification = certificationRepository.findTopByAccountIdOrderByCreatedAtDesc(accountId); -// if (certification == null) return CheckCertificationResponseDto.certificationFail(); -// -// boolean isMatch = certification.getAccountId().equals(accountId) -// && certification.getCertificationNumber().equals(certificationNumber); -// -// if (!isMatch) return CheckCertificationResponseDto.certificationFail(); -// -// // 인증 시간 검증 -// LocalDateTime createdAt = certification.getCreatedAt(); -// if (createdAt.isBefore(LocalDateTime.now().minusMinutes(5))) { // 5분 유효시간 -// return CheckCertificationResponseDto.certificationExpired(); -// } -// -// // 인증 완료 상태 업데이트 -// certification.markAsVerified(); -// certificationRepository.save(certification); -// -// } catch (Exception e) { -// log.info("인증 번호 확인 실패: {}", e); -// return CheckCertificationResponseDto.databaseError(); -// } -// -// return CheckCertificationResponseDto.success(); -// } -// -// -// @Override -// @Transactional -// public ResponseEntity signUp(SignUpInfo info) { -// try { -// String accountId = info.getAccountId(); -// String certificationNumber = info.getCertificationNumber(); -// -// // 1. 필수 정보 누락 확인 -// if (accountId == null || certificationNumber == null || info.getPassword() == null) { -// return SignUpResponseDto.missingRequiredFields(); -// } -// -// // 2. 인증 번호 확인 -// Certification certification = certificationRepository.findTopByAccountIdOrderByCreatedAtDesc(accountId); -// if (certification == null || !certification.getIsVerified()) { -// return SignUpResponseDto.certificationFail(); // 인증되지 않은 경우 -// } -// -// // 3. 중복된 ID 확인 -// if (userRepository.existsByAccountId(accountId)) { -// return SignUpResponseDto.duplicateId(); -// } -// -// // 5. 비밀번호 암호화 후 저장 -// String encodedPassword = passwordEncoder.encode(info.getPassword()); -// -// // 6. GeocodingService를 통해 지역명과 6자리 행정코드 가져오기 -// GeocodingComponent.KakaoRegion kakaoRegion = geocodingComponent.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); -// if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { -// return SignUpResponseDto.locationFail(); -// } -// -// //6-1 Img 정제 -// S3Imge petImg = awsClient.UploadImg(info.getAccountId(), info.getProfileImg(), "CUSTOM_USER_IMG", null); -// -// // 7. 새로운 User 생성 및 저장 -// User user = UserFactory.createNewUser(info, encodedPassword, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger(), petImg.uploadURL()); -// userRepository.save(user); -// -// // 8. 인증 엔티티 삭제 -// certificationRepository.deleteAllByAccountId(accountId); -// -// // 9. 성공 응답 반환 -// return SignUpResponseDto.success(user.getId(), petImg.checkResultImg()); -// -// } catch (RuntimeException e) { -// log.error("회원 가입 실패: {}", e.getMessage(), e); -// throw e; -// } catch (Exception e) { -// log.error("회원 가입 중 처리되지 않은 예외 발생: {}", e.getMessage(), e); -// return SignUpResponseDto.unknownError(); -// } -// } -// -// -// @Override -// public ResponseEntity signIn(SignInInfo info, HttpServletResponse response) { -// try { -// String accountId = info.getAccountId(); -// User user = userRepository.findByAccountId(accountId); -// // 사용자 존재 여부 확인 -// if (user == null) { -// log.info("사용자 조회 실패: {}", accountId); -// return SignInResponseDto.signInFail(); -// } -// -// // 비밀번호 확인 -// String password = info.getPassword(); -// String encodedPassword = user.getPassword(); -// if (!passwordEncoder.matches(password, encodedPassword)) { -// log.info("비밀번호 불일치: {}", accountId); -// return SignInResponseDto.signInFail(); -// } -// -// // JWT 생성 (userId를 subject로, accountId를 클레임으로 설정) -// String token = jwtProvider.create(user.getId(), user.getAccountId()); -// log.info("JWT 생성 완료: {}", token); -// -// ResponseCookie responseCookie = ResponseCookie.from("jwt", token) -// .httpOnly(true) // XSS 방지 -// .secure(true) // HTTPS만 허용 -// .path("/") // 모든 경로에서 접근 가능 -// .sameSite("None") // SameSite=None 설정 -// .maxAge((3600)) -// .build(); -// response.addHeader("Set-Cookie", responseCookie.toString()); -// // 로그인 성공 응답 반환 -// return SignInResponseDto.success(user); // User 객체 전달 -// } catch (Exception e) { -// log.error("로그인 처리 중 예외 발생", e); -// return SignInResponseDto.signInFail(); -// } -// } -// -// -// @Override -// public ResponseEntity logout(HttpServletResponse response) { -// try { -// // 만료된 쿠키 설정 -// ResponseCookie expiredCookie = ResponseCookie.from("jwt", "") -// .httpOnly(true) // XSS 방지 -// .secure(true) // HTTPS만 허용 -// .path("/") // 모든 경로에서 접근 가능 -// .sameSite("None") // SameSite=None 설정 -// .maxAge(0) // 즉시 만료 -// .build(); -// -// response.addHeader("Set-Cookie", expiredCookie.toString()); -// -// log.info("JWT 쿠키 제거 및 로그아웃 처리 완료"); -// return LogInResponseDto.success(); -// } catch (Exception e) { -// log.error("로그아웃 처리 중 예외 발생", e); -// return LogInResponseDto.validationFail(); -// } -// } -// -// -// @Override -// @Transactional -// public ResponseEntity deleteId(DeleteIdRequestDto dto) { -// try { -// Long userId = jwtExtractProvider.findIdFromJwt(); -// boolean exists = userRepository.existsById(userId); -// if (!exists) { -// return DeleteIdResponseDto.idNotFound(); -// } -// -// User user = userRepository.findById(userId) -// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); -// -// -// // 비밀번호 일치 확인 -// String password = dto.getPassword(); -// String encodedPassword = user.getPassword(); -// boolean isMatched = passwordEncoder.matches(password, encodedPassword); -// if (!isMatched) return DeleteIdResponseDto.wrongPassword(); // 비밀번호 불일치 -// -// certificationRepository.deleteById(userId); -// // 사용자 삭제 -// List pets = petRepository.findAllByUserId(user.getId()); // Pet 엔티티에서 User를 참조하는 기준으로 조회 -// // 명시적으로 Pet 삭제 -// petRepository.deleteAll(pets); -// userRepository.delete(user); -// } catch (Exception e) { -// log.info("회원 삭제 실패: {}", e); -// return DeleteIdResponseDto.databaseError(); // 데이터베이스 오류 처리 -// } -// return DeleteIdResponseDto.success(); // 삭제 성공 응답 -// } -// -// -// @Override -// public ResponseEntity getMypage() { -// try { -// String userId = jwtExtractProvider.findAccountIdFromJwt(); -// User user = userRepository.findByAccountId(userId); -// -// boolean exists = userRepository.existsByAccountId(userId); -// if (!exists) { -// return GetMyProfileResponseDto.idNotFound(); -// } -// -// return GetMyProfileResponseDto.success(user); -// -// } catch (Exception e) { -// e.printStackTrace(); -// return GetMyProfileResponseDto.databaseError(); -// } -// } -// -// -// @Override -// public ResponseEntity getOtherMypage(Long userId) { -// try { -// // 현재 로그인한 사용자 ID 가져오기 -// Long myId = jwtExtractProvider.findIdFromJwt(); -// if (!userRepository.existsById(myId)) { -// return GetMyProfileResponseDto.idNotFound(); -// } -// -// // 조회 대상 사용자 정보 가져오기 -// User user = userRepository.findById(userId) -// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); -// -// // 조회 대상 사용자가 존재하는지 확인 -// boolean exists = userRepository.existsById(userId); -// if (!exists) { -// return GetOtherProfileResponseDto.userNotFound(); -// } -// -// // 현재 로그인한 사용자가 조회 대상 사용자를 찜했는지 확인 -// boolean isMyHeartUser = heartRepository.existsByMyIdAndHeartedId(myId, userId); -// -// // 응답 생성 -// return GetOtherProfileResponseDto.success(user, isMyHeartUser); -// -// } catch (Exception e) { -// e.printStackTrace(); -// return GetOtherProfileResponseDto.userNotFound(); -// } -// } -// -// -// //TODO 고쳐야함. -// @Override -// @Transactional -// public ResponseEntity editMyProfile(EditMyProfileInfo info) { -// try { -// System.out.println("info ::" + info.isCareAvailable()); -// Long userId = jwtExtractProvider.findIdFromJwt(); -// String userEmail = jwtExtractProvider.findAccountIdFromJwt(); -// boolean exists = userRepository.existsById(userId); -// if (!exists) { -// return EditMyProfileResponseDto.idNotFound(); -// } -// -// User user = userRepository.findById(userId) -// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); -// -// //6-1 Img 정제 -// S3Imge petImg = awsClient.UploadImg(userEmail, info.getProfileImg(), "CUSTOM_USER_IMG", null); -// -// // 병합된 DTO를 기반으로 엔티티 생성 -// String resultImg = user.updateImgURL(info.getProfileImg(), petImg); -// user.updateProfile(info); -// -//// 반환해야 함 아래꺼 -// return EditMyProfileResponseDto.success(resultImg); -// -// } catch (Exception e) { -// log.info("프로필 수정 실패: {}", e); -// return EditMyProfileResponseDto.editFailed(); -// } -// } -// -// -// @Override -// @Transactional -// public ResponseEntity hearting(HeartingRequestDto dto) { -// try { -// Long heartedId = dto.getHeartedId(); -// -// // 대상 사용자가 존재하는지 확인 -// boolean exists = userRepository.existsById(heartedId); -// if (!exists) { -// return HeartingResponseDto.heartedIdNotFound(); -// } -// -// // 현재 사용자 ID 가져오기 -// Long userId = jwtExtractProvider.findIdFromJwt(); -// User user = userRepository.findById(userId) -// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); -// -// // DB에서 myId와 heartedId로 Heart 레코드 확인 -// Optional existingHeart = heartRepository.findByMyIdAndHeartedId(user.getId(), heartedId); -// -// if (existingHeart.isPresent()) { -// // 찜하기 해제 (DB에서 삭제) -// heartRepository.delete(existingHeart.get()); -// return HeartingResponseDto.success(); // 찜하기 해제 성공 응답 -// } -// -// // 찜하기 진행 (DB에 저장) -// Heart heart = UserFactory.createHeart(userId, heartedId); -// heartRepository.save(heart); -// -// return HeartingResponseDto.success(); // 찜하기 성공 응답 -// -// } catch (Exception e) { -// log.info("찜하기 실패: {}", e); -// return HeartingResponseDto.databaseError(); // 데이터베이스 오류 응답 -// } -// } -// -// -// @Override -// public ResponseEntity getHeartedList() { -// try { -// Long userId = jwtExtractProvider.findIdFromJwt(); -// User user = userRepository.findById(userId) -// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); -// -// // Heart 리스트 조회 -// List heartList = heartRepository.findAllByMyId(user.getId()); -// -// // Heart 정보와 관련된 User 데이터 매핑 -// List heartedUsers = heartList.stream() -// .map(heart -> { -// User heartedUser = userRepository.findById(heart.getHeartedId()) -// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + heart.getHeartedId())); -// -// return new HeartedUserDto( -// heart.getMyId(), -// heart.getHeartedId(), -// heartedUser.getNickname(), -// heartedUser.getProfileImg(), -// heartedUser.getCareAvailable(), -// heartedUser.getPreferredSizes() -// ); -// }) -// .toList(); -// -// // 성공 응답 반환 -// return GetHeartingListResponseDto.success(heartedUsers); -// -// } catch (Exception e) { -// log.info("찜리스트 받아오기 실패: {}", e); -// return HeartingResponseDto.databaseError(); -// } -// } -// -// -// @Override -// @Transactional -// public ResponseEntity sendRepassword(SendRepasswordRequestDto dto) { -// try { -// String accountId = dto.getAccountId(); -// if (!userRepository.existsByAccountId(accountId)) { -// return GetMyProfileResponseDto.idNotFound(); -// } -// User user = userRepository.findByAccountId(accountId); -// -// String rePasswordNum = RePasswordProvider.generatePassword(); -// log.info("Generated Repassword: {}", rePasswordNum); -// -//// boolean isSendSuccess = rePasswordEmailProvider.sendVerificationEmail(accountId, rePasswordNum); -//// if (!isSendSuccess) return SendRepasswordResponseDto.mailSendFail(); -// -// String encodedRePasswordNum = passwordEncoder.encode(rePasswordNum); -// -// user.updatePassword(encodedRePasswordNum); -// -// } catch (Exception e) { -// log.info("임시비밀번호 재설정 실패: {}", e); -// return SendRepasswordResponseDto.databaseError(); -// } -// return SendRepasswordResponseDto.success(); -// } -// -// -// @Override -// @Transactional -// public ResponseEntity repassword(RepasswordInfo info) { -// try { -// Long userId = jwtExtractProvider.findIdFromJwt(); -// User user = userRepository.findById(userId) -// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); -// -// String currentPassword = info.getCurrentPassword(); -// -// boolean isPasswordValid = passwordEncoder.matches(currentPassword, user.getPassword()); -// if (!isPasswordValid) { -// return RepasswordResponseDto.wrongPassword(); -// } -// String newPassword = info.getNewPassword(); -// -// String encodedNewPassword = passwordEncoder.encode(newPassword); -// -// user.updatePassword(encodedNewPassword); -// -// } catch (Exception e) { -// log.info("비밀번호 재설정 실패: {}", e); -// return RepasswordResponseDto.databaseError(); -// } -// return RepasswordResponseDto.success(); -// } -// -// -// @Override -// @Transactional -// public ResponseEntity updateLocation(UpdateLocationInfo info) { -// try { -// // JWT에서 사용자 ID 추출 -// Long userId = jwtExtractProvider.findIdFromJwt(); -// -// // 사용자 엔티티 조회 -// User user = userRepository.findById(userId) -// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); -// -// // 사용자 존재 여부 확인 -// boolean exists = userRepository.existsById(userId); -// if (!exists) { -// return UpdateLocationResponseDto.userNotFound(); -// } -// -// // GeocodingService에서 지역명과 행정코드 가져오기 -// GeocodingComponent.KakaoRegion kakaoRegion = geocodingComponent.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); -// if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { -// return UpdateLocationResponseDto.wrongLocation(); // Kakao API 호출 실패 처리 -// } -// -// user.updateLocation(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); -// -// // 성공 응답 반환 -// return UpdateLocationResponseDto.success(kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); -// } catch (Exception e) { -// log.error("위치 업데이트 실패: {}", e.getMessage(), e); -// return UpdateLocationResponseDto.wrongLocation(); -// } -// } -// -// @Override -// @Transactional -// public ResponseEntity updateRecommend(UpdateRecommendationRequestDto dto) { -// try { -// Long recommendedId = dto.getUserId(); -// Long myId = jwtExtractProvider.findIdFromJwt(); -// User recommendedUser = userRepository.findById(recommendedId) -// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + recommendedId)); -// -// boolean exists = userRepository.existsById(recommendedId); -// if (!exists) { -// return UpdateRecommendationResponseDto.userNotFound(); -// } -// Integer recommendationCount = recommendedUser.getRecommendationCount() + 1; -// -// recommendedUser.updateRecommendation(recommendationCount); -// -// Recommendation recommendation = UserFactory.createRecommendation(myId, recommendedId); -// recommendationRepository.save(recommendation); -// -// } catch (Exception e) { -// log.info("추천수 업데이트 실패: {}", e); -// return UpdateRecommendationResponseDto.userNotFound(); -// } -// return UpdateRecommendationResponseDto.success(); -// } -// -// @Override -// public ResponseEntity getRecommend(UpdateRecommendationRequestDto dto) { -// try { -// Long recommendedId = dto.getUserId(); -// Long myId = jwtExtractProvider.findIdFromJwt(); -// -// boolean exists = recommendationRepository.existsByMyIdAndRecommendedId(myId,recommendedId); -// return GetRecommendationResponseDto.success(exists); -// -// } catch (Exception e) { -// log.info("추천수 업데이트 실패: {}", e); -// return GetRecommendationResponseDto.userNotFound(); -// } -// } -// -// @Override -// @Transactional -// public ResponseEntity editKakaoProfile(EditKakaoProfileInfo info) { -// try { -// Long userId = jwtExtractProvider.findIdFromJwt(); -// -// // userId가 null인 경우 예외 처리 -// if (userId == null) { -// log.warn("JWT에서 추출된 userId가 null입니다."); -// return EditKakaoProfileResponseDto.idNotFound(); -// } -// -// boolean exists = userRepository.existsById(userId); -// if (!exists) { -// log.warn("존재하지 않는 사용자 ID: {}", userId); -// return EditKakaoProfileResponseDto.idNotFound(); -// } -// -// User user = userRepository.findById(userId) -// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); -// -// GeocodingComponent.KakaoRegion kakaoRegion = geocodingComponent.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); -// if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { -// return UpdateLocationResponseDto.wrongLocation(); // Kakao API 호출 실패 처리 -// } -// -// user.updateKakaoProfile(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); -// } catch (Exception e) { -// log.error("프로필 수정 실패", e); -// return EditKakaoProfileResponseDto.editFailed(); -// } -// return EditKakaoProfileResponseDto.success(); -// } -// -// @Override -// public GetMyUserDto receiverEmail(String accountId) { -// try { -// User user = userRepository.findByAccountId(accountId); -// return new GetMyUserDto(user); -// } catch (Exception e) { -// e.printStackTrace(); -// } -// return new GetMyUserDto(); -// } -// -// @Override -// public void deleteUser(Long userUUID) { -// userRepository.deleteUserById(userUUID); -// } -// -// -// public UserInfo selectUserInfo(String receiverEmail) { -// User otherUser = userRepository.findByAccountId(receiverEmail); -// return otherUser.of(); -// } -// -// @Override -// public String findByUserEmail(Long userId) { -// return userRepository.findById(userId).get().getAccountId(); -// } -// -//} diff --git a/src/main/java/com/petmatz/infra/jwt/JwtProvider.java b/src/main/java/com/petmatz/infra/jwt/JwtProvider.java new file mode 100644 index 0000000..7e94cdb --- /dev/null +++ b/src/main/java/com/petmatz/infra/jwt/JwtProvider.java @@ -0,0 +1,67 @@ +package com.petmatz.infra.jwt; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +@Component +public class JwtProvider { + @Value("${secret-access-key}") + private String accessKey; + + @Value("${secret-refresh-key}") + private String refreshKey; + + public String createAccessToken(Long userId, String accountId) { + // 토큰 만료 시간 설정 (1시간 후) + Date expireDate = Date.from(Instant.now().plus(24, ChronoUnit.HOURS)); + + // 비밀 키 생성 + Key key = Keys.hmacShaKeyFor(accessKey.getBytes(StandardCharsets.UTF_8)); + + // JWT 토큰 생성 + return Jwts.builder() + .signWith(key, SignatureAlgorithm.HS256) // 서명 알고리즘 및 키 설정 + .setSubject(userId.toString()) // 서브젝트에 사용자 ID 설정 + .claim("accountId", accountId) // 계정 ID를 클레임으로 추가 + .setIssuedAt(new Date()) // 토큰 발행 시간 + .setExpiration(expireDate) // 토큰 만료 시간 + .compact(); + } + + public String createRefreshToken(Long userId) { + Date expireDate = Date.from(Instant.now().plus(2, ChronoUnit.WEEKS)); + SecretKey secretKey = Keys.hmacShaKeyFor(refreshKey.getBytes(StandardCharsets.UTF_8)); + + return Jwts.builder() + .signWith(secretKey, SignatureAlgorithm.RS256) + .setSubject(userId.toString()) + .setIssuedAt(new Date()) + .setExpiration(expireDate) + .compact(); + } + + public boolean validate(String jwt, boolean isAccessToken) { + try { + Key key; + if (isAccessToken) { + key = Keys.hmacShaKeyFor(accessKey.getBytes(StandardCharsets.UTF_8)); + } else { + key = Keys.hmacShaKeyFor(refreshKey.getBytes(StandardCharsets.UTF_8)); + } + Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(jwt); + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/src/main/java/com/petmatz/infra/redis/component/RedisTokenComponent.java b/src/main/java/com/petmatz/infra/redis/component/RedisTokenComponent.java new file mode 100644 index 0000000..52b66ea --- /dev/null +++ b/src/main/java/com/petmatz/infra/redis/component/RedisTokenComponent.java @@ -0,0 +1,23 @@ +package com.petmatz.infra.redis.component; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +@Component +@RequiredArgsConstructor +public class RedisTokenComponent { + + private final RedisTemplate redisTemplate; + + public void saveRefreshToken(Long userId, String refreshToken) { + String redisKey = "refreshToken:" + userId; + try { + redisTemplate.opsForValue().set(redisKey, refreshToken, Duration.ofDays(30)); + } catch (Exception e) { + throw new IllegalStateException("일단은 리프레시 토큰 저장 오류입니다!"); + } + } +} From fc72be495f4c7221c0e35c25dee2c181a82f0158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Tue, 28 Jan 2025 21:07:06 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Feat=20:=20refresh=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=9E=AC=EB=B0=9C=EA=B8=89=20+=20=EC=A2=85=EC=9B=90=EB=8B=98?= =?UTF-8?q?=20=EC=9E=84=EC=8B=9C=20=EC=BB=A4=EB=B0=8B=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../petmatz/api/chatting/ChatController.java | 1 - .../exception/GlobalExceptionHandler.java | 32 +++++++++ .../api/user/controller/JwtController.java | 24 +++++++ .../user/controller/PasswordController.java | 1 - .../filter/JwtAuthenticationFilter.java | 2 - .../common/security/jwt/JwtManager.java | 21 ++++-- .../component/AuthenticationComponent.java | 18 +++-- .../user/component/CookieComponent.java | 36 ++++++++++ .../domain/user/component/EmailComponent.java | 3 - .../user/component/GeocodingComponent.java | 5 +- .../domain/user/component/HeartComponent.java | 2 +- .../user/component/PasswordComponent.java | 2 +- .../domain/user/component/UserReader.java | 4 -- .../domain/user/component/UserUtils.java | 7 +- ...MatchErrorCode.java => UserErrorCode.java} | 20 +++++- .../user/repository/UserRepository.java | 4 ++ .../domain/user/service/AuthService.java | 18 +++-- .../domain/user/service/KakaoUserService.java | 2 +- .../infra/email/EmailProviderImpl.java | 2 +- .../email/RePasswordEmailProviderImpl.java | 2 +- .../com/petmatz/infra/jwt/JwtProvider.java | 67 ------------------- .../redis/component/RedisTokenComponent.java | 5 +- 22 files changed, 164 insertions(+), 114 deletions(-) create mode 100644 src/main/java/com/petmatz/api/global/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/com/petmatz/api/user/controller/JwtController.java create mode 100644 src/main/java/com/petmatz/domain/user/component/CookieComponent.java rename src/main/java/com/petmatz/domain/user/exception/{MatchErrorCode.java => UserErrorCode.java} (84%) delete mode 100644 src/main/java/com/petmatz/infra/jwt/JwtProvider.java diff --git a/src/main/java/com/petmatz/api/chatting/ChatController.java b/src/main/java/com/petmatz/api/chatting/ChatController.java index 6320545..69803c2 100644 --- a/src/main/java/com/petmatz/api/chatting/ChatController.java +++ b/src/main/java/com/petmatz/api/chatting/ChatController.java @@ -7,7 +7,6 @@ import com.petmatz.domain.chatting.ChatRoomService; import com.petmatz.domain.chatting.dto.ChatMessageInfo; import com.petmatz.domain.user.info.UserInfo; -import com.petmatz.domain.user.service.UserServiceImpl; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; diff --git a/src/main/java/com/petmatz/api/global/exception/GlobalExceptionHandler.java b/src/main/java/com/petmatz/api/global/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..4419aa1 --- /dev/null +++ b/src/main/java/com/petmatz/api/global/exception/GlobalExceptionHandler.java @@ -0,0 +1,32 @@ +package com.petmatz.api.global.exception; + +import com.petmatz.api.global.dto.Response; +import com.petmatz.common.exception.BaseErrorCode; +import com.petmatz.common.exception.ErrorReason; +import com.petmatz.domain.user.exception.UserErrorCode; +import com.petmatz.domain.user.exception.UserException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(UserException.class) + public ResponseEntity> handleUserException(UserException ex) { + // 예외에서 에러 코드 가져오기 + BaseErrorCode baseErrorCode = ex.getErrorCode(); + ErrorReason errorReason = baseErrorCode.getErrorReason(); + + int statusCode = errorReason.status(); + String message = errorReason.message(); + String errorCode2 = errorReason.errorCode(); + + // 실패 응답 생성 + Response response = Response.error(errorCode2, message); + + // 상태 코드와 함께 응답 반환 + return new ResponseEntity<>(response, HttpStatus.valueOf(statusCode)); + } +} diff --git a/src/main/java/com/petmatz/api/user/controller/JwtController.java b/src/main/java/com/petmatz/api/user/controller/JwtController.java new file mode 100644 index 0000000..ee36ce6 --- /dev/null +++ b/src/main/java/com/petmatz/api/user/controller/JwtController.java @@ -0,0 +1,24 @@ +package com.petmatz.api.user.controller; + +import com.petmatz.api.global.dto.Response; +import com.petmatz.common.security.jwt.JwtManager; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseCookie; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class JwtController { + + private final JwtManager jwtManager; + + @PostMapping("/token/reissue") + public Response reissueAccessToken(HttpServletResponse response, String refreshToken) { + jwtManager.refreshAccessToken(response, refreshToken); + return Response.success(); + } +} diff --git a/src/main/java/com/petmatz/api/user/controller/PasswordController.java b/src/main/java/com/petmatz/api/user/controller/PasswordController.java index d02a359..da7f6e4 100644 --- a/src/main/java/com/petmatz/api/user/controller/PasswordController.java +++ b/src/main/java/com/petmatz/api/user/controller/PasswordController.java @@ -3,7 +3,6 @@ import com.petmatz.api.global.dto.Response; import com.petmatz.api.user.request.RepasswordRequestDto; import com.petmatz.api.user.request.SendRepasswordRequestDto; -import com.petmatz.domain.user.component.PasswordService; import com.petmatz.domain.user.response.RepasswordResponseDto; import com.petmatz.domain.user.response.SendRepasswordResponseDto; import com.petmatz.domain.user.service.PasswordService; diff --git a/src/main/java/com/petmatz/common/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/petmatz/common/security/filter/JwtAuthenticationFilter.java index 7a78928..36eba19 100644 --- a/src/main/java/com/petmatz/common/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/petmatz/common/security/filter/JwtAuthenticationFilter.java @@ -45,13 +45,11 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (token == null) { filterChain.doFilter(request, response); return; - // 여기 예외 처리해야댐 } // JWT 유효성 검증 및 사용자 ID 추출 Long userId = jwtManager.validateAndGetUserId(token); // validate 메서드가 userId를 반환하도록 수정 if (userId == null) { - getRefreshTokenFromCookies filterChain.doFilter(request, response); return; } diff --git a/src/main/java/com/petmatz/common/security/jwt/JwtManager.java b/src/main/java/com/petmatz/common/security/jwt/JwtManager.java index 4db1706..95c4ea6 100644 --- a/src/main/java/com/petmatz/common/security/jwt/JwtManager.java +++ b/src/main/java/com/petmatz/common/security/jwt/JwtManager.java @@ -1,13 +1,17 @@ package com.petmatz.common.security.jwt; +import com.petmatz.domain.user.component.CookieComponent; +import com.petmatz.domain.user.component.UserUtils; import com.petmatz.infra.redis.component.RedisTokenComponent; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseCookie; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; @@ -27,7 +31,7 @@ @Component @Slf4j @RequiredArgsConstructor -public class JwtManager { +public class JwtManager { @Value("${secret-access-key}") private String accessKey; @@ -36,6 +40,8 @@ public class JwtManager { private String refreshKey; private final RedisTokenComponent redisTokenComponent; + private final CookieComponent cookieComponent; + private final UserUtils userUtils; /** * 주어진 사용자 ID와 계정 ID로 JWT 토큰을 생성하는 메서드. @@ -130,7 +136,7 @@ public Long validateAndGetUserId(String token) { } // refresh Token 으로 재발급 - public String refreshAccessToken(String refreshToken) { + public void refreshAccessToken(HttpServletResponse response, String refreshToken) { try { SecretKey refreshKey = Keys.hmacShaKeyFor(this.refreshKey.getBytes(StandardCharsets.UTF_8)); @@ -142,16 +148,21 @@ public String refreshAccessToken(String refreshToken) { .getBody(); Long userId = Long.parseLong(claims.getSubject()); + String accountId = userUtils.findAccountIdByUserId(userId); - return createRefreshToken(userId); + String storedToken = redisTokenComponent.getRefreshTokenFromRedis(userId); + if (storedToken == null || !storedToken.equals(refreshToken)) { + log.error("Redis에 없는 리프레시 토큰이거나. 일치하지 않는 토큰입니당 다시 한번 확인해주세요"); + } + + String accessToken = createAccessToken(userId, accountId); + cookieComponent.setAccessTokenCookie(response, accessToken); } catch (ExpiredJwtException e) { // Refresh Token 만료 시 예외 처리 log.error("Refresh token has expired." + e); - return null; } catch (Exception e) { // 기타 검증 실패 시 예외 처리 log.error("Invalid refresh token." + e); - return null; } } } \ No newline at end of file diff --git a/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java b/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java index 1fb88e7..9bb6f72 100644 --- a/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java @@ -17,8 +17,8 @@ import java.security.cert.CertificateException; import java.time.LocalDateTime; -import static com.petmatz.domain.user.exception.MatchErrorCode.CERTIFICATION_EXPIRED; -import static com.petmatz.domain.user.exception.MatchErrorCode.MISS_MATCH_CODE; +import static com.petmatz.domain.user.exception.UserErrorCode.CERTIFICATION_EXPIRED; +import static com.petmatz.domain.user.exception.UserErrorCode.MISS_MATCH_CODE; @Component @RequiredArgsConstructor @@ -30,6 +30,14 @@ public class AuthenticationComponent { private final UserUtils userUtils; private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + public String createJwtAccessToken(User user) { + return jwtManager.createAccessToken(user.getId(), user.getAccountId()); + } + + public String createJwtRefreshToken(User user) { + return jwtManager.createRefreshToken(user.getId()); + } + public User validateSignInCredentials(SignInInfo info) throws AuthenticationException { String accountId = info.getAccountId(); String password = info.getPassword(); @@ -38,15 +46,11 @@ public User validateSignInCredentials(SignInInfo info) throws AuthenticationExce String encodedPassword = user.getPassword(); if (!passwordEncoder.matches(password, encodedPassword)) { - throw new AuthenticationException("비밀번호 불일치"); +// throw new UserException(); } return user; } - public String createJwtToken(User user) { - return jwtManager.createAccessToken(user.getId(), user.getAccountId()); - } - /** * 필수 정보 누락 확인 */ diff --git a/src/main/java/com/petmatz/domain/user/component/CookieComponent.java b/src/main/java/com/petmatz/domain/user/component/CookieComponent.java new file mode 100644 index 0000000..be37572 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/CookieComponent.java @@ -0,0 +1,36 @@ +package com.petmatz.domain.user.component; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.http.ResponseCookie; + + +@Component +public class CookieComponent { + + public void setAccessTokenCookie(HttpServletResponse response, String accessToken) { + ResponseCookie responseCookie = org.springframework.http.ResponseCookie.from("jwt", accessToken) + .httpOnly(true) // XSS 방지 + .secure(true) // HTTPS만 허용 + .path("/") // 모든 경로에서 접근 가능 + .sameSite("None") // SameSite=None 설정 + .maxAge(3600) // 1시간 유효 + .build(); + setCookieHeader(response, responseCookie); + } + + public void setRefreshTokenCookie(HttpServletResponse response, String refreshToken) { + ResponseCookie responseCookie = org.springframework.http.ResponseCookie.from("refreshToken", refreshToken) + .httpOnly(true) // XSS 방지 + .secure(true) // HTTPS만 허용 + .path("/") // 모든 경로에서 접근 가능 + .sameSite("None") // SameSite=None 설정 + .maxAge(60 * 60 * 24 * 14) // 2주 동안 유효 + .build(); + setCookieHeader(response, responseCookie); + } + + private static void setCookieHeader(HttpServletResponse response, ResponseCookie responseCookie) { + response.addHeader("Set-Cookie", responseCookie.toString()); + } +} diff --git a/src/main/java/com/petmatz/domain/user/component/EmailComponent.java b/src/main/java/com/petmatz/domain/user/component/EmailComponent.java index 04a250c..ffac476 100644 --- a/src/main/java/com/petmatz/domain/user/component/EmailComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/EmailComponent.java @@ -1,14 +1,11 @@ package com.petmatz.domain.user.component; import com.petmatz.domain.user.entity.Certification; -import com.petmatz.domain.user.exception.UserException; import com.petmatz.domain.user.repository.CertificationRepository; import com.petmatz.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import static com.petmatz.domain.user.exception.MatchErrorCode.USER_DUPLICATE; - @Component @RequiredArgsConstructor public class EmailComponent { diff --git a/src/main/java/com/petmatz/domain/user/component/GeocodingComponent.java b/src/main/java/com/petmatz/domain/user/component/GeocodingComponent.java index 6fc3304..eabbe04 100644 --- a/src/main/java/com/petmatz/domain/user/component/GeocodingComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/GeocodingComponent.java @@ -1,20 +1,17 @@ package com.petmatz.domain.user.component; -import com.fasterxml.jackson.annotation.JsonProperty; import com.petmatz.domain.user.entity.KakaoRegion; import com.petmatz.domain.user.exception.UserException; import com.petmatz.domain.user.response.KakaoGeocodingResponse; -import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; -import com.petmatz.domain.user.entity.KakaoRegion; import java.util.List; -import static com.petmatz.domain.user.exception.MatchErrorCode.INSUFFICIENT_LOCATION_DATA; +import static com.petmatz.domain.user.exception.UserErrorCode.INSUFFICIENT_LOCATION_DATA; @Service public class GeocodingComponent { diff --git a/src/main/java/com/petmatz/domain/user/component/HeartComponent.java b/src/main/java/com/petmatz/domain/user/component/HeartComponent.java index 90e9c71..61e727d 100644 --- a/src/main/java/com/petmatz/domain/user/component/HeartComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/HeartComponent.java @@ -14,7 +14,7 @@ import java.util.List; import java.util.Optional; -import static com.petmatz.domain.user.exception.MatchErrorCode.*; +import static com.petmatz.domain.user.exception.UserErrorCode.*; @RequiredArgsConstructor @Component diff --git a/src/main/java/com/petmatz/domain/user/component/PasswordComponent.java b/src/main/java/com/petmatz/domain/user/component/PasswordComponent.java index bd743c9..a885c5c 100644 --- a/src/main/java/com/petmatz/domain/user/component/PasswordComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/PasswordComponent.java @@ -6,7 +6,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; -import static com.petmatz.domain.user.exception.MatchErrorCode.PASSWORD_MISMATCH; +import static com.petmatz.domain.user.exception.UserErrorCode.PASSWORD_MISMATCH; @Component @RequiredArgsConstructor diff --git a/src/main/java/com/petmatz/domain/user/component/UserReader.java b/src/main/java/com/petmatz/domain/user/component/UserReader.java index e738d9c..307bc07 100644 --- a/src/main/java/com/petmatz/domain/user/component/UserReader.java +++ b/src/main/java/com/petmatz/domain/user/component/UserReader.java @@ -1,14 +1,10 @@ package com.petmatz.domain.user.component; import com.petmatz.domain.user.entity.User; -import com.petmatz.domain.user.exception.UserException; import com.petmatz.domain.user.repository.UserRepository; -import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import static com.petmatz.domain.user.exception.MatchErrorCode.USER_NOT_FOUND; - @Component @RequiredArgsConstructor public class UserReader { diff --git a/src/main/java/com/petmatz/domain/user/component/UserUtils.java b/src/main/java/com/petmatz/domain/user/component/UserUtils.java index 5adc216..903c81d 100644 --- a/src/main/java/com/petmatz/domain/user/component/UserUtils.java +++ b/src/main/java/com/petmatz/domain/user/component/UserUtils.java @@ -8,7 +8,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import static com.petmatz.domain.user.exception.MatchErrorCode.*; +import static com.petmatz.domain.user.exception.UserErrorCode.*; @Component @RequiredArgsConstructor @@ -42,6 +42,11 @@ public User findIdUser(Long userId) { return user; } + public String findAccountIdByUserId(Long userId) { + return userRepository.findByUserId(userId) + .orElseThrow(() -> new UserException(USER_NOT_FOUND)); + } + @Transactional public User getCurrentUser(Long userId) { diff --git a/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java b/src/main/java/com/petmatz/domain/user/exception/UserErrorCode.java similarity index 84% rename from src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java rename to src/main/java/com/petmatz/domain/user/exception/UserErrorCode.java index c0f7184..856f6dd 100644 --- a/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java +++ b/src/main/java/com/petmatz/domain/user/exception/UserErrorCode.java @@ -5,7 +5,7 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor -public enum MatchErrorCode implements BaseErrorCode { +public enum UserErrorCode implements BaseErrorCode { INSUFFICIENT_LOCATION_DATA(404, "INSUFFICIENT_LOCATION_DATA", "유요한 위치 정보를 가져올 수 없습니다."), MISS_KAKAO_LOACTION(400, "MISS_KAKAO_LOACTION", "카카오 지역 api를 호출 할 수 없습니다."), @@ -23,8 +23,6 @@ public enum MatchErrorCode implements BaseErrorCode { HEART_USER_DUPLICATE(400, "HEART_USER_DUPLICATE", "찜한 사용자가 이미 존재합니다."); - - private final Integer status; private final String errorCode; private final String message; @@ -33,5 +31,21 @@ public enum MatchErrorCode implements BaseErrorCode { public ErrorReason getErrorReason() { return ErrorReason.of(status, errorCode, message); } + + + + UserErrorCode(int status, String errorCode, String message) { + this.status = status; + this.errorCode = errorCode; + this.message = message; + } + + public int status() { + return status; + } + + public String message() { + return message; + } } diff --git a/src/main/java/com/petmatz/domain/user/repository/UserRepository.java b/src/main/java/com/petmatz/domain/user/repository/UserRepository.java index 5ce662c..9e92392 100644 --- a/src/main/java/com/petmatz/domain/user/repository/UserRepository.java +++ b/src/main/java/com/petmatz/domain/user/repository/UserRepository.java @@ -16,6 +16,8 @@ public interface UserRepository extends JpaRepository { boolean existsById(Long userId); boolean existsByAccountId(String accountId); Optional findById(Long userId); + + Optional findByUserId(Long userId); User findByAccountId(String accountId); @Query("SELECT u.accountId FROM User u WHERE u.id = :userId") @@ -27,4 +29,6 @@ public interface UserRepository extends JpaRepository { void deleteUserById(@Param("userId") Long userId); List findByRegionCodeOrderByRecommendationCountDesc(Integer regionCode); + + } diff --git a/src/main/java/com/petmatz/domain/user/service/AuthService.java b/src/main/java/com/petmatz/domain/user/service/AuthService.java index a4e2ef7..8435a2c 100644 --- a/src/main/java/com/petmatz/domain/user/service/AuthService.java +++ b/src/main/java/com/petmatz/domain/user/service/AuthService.java @@ -4,6 +4,7 @@ import com.petmatz.domain.aws.AwsClient; import com.petmatz.domain.aws.vo.S3Imge; import com.petmatz.domain.user.component.AuthenticationComponent; +import com.petmatz.domain.user.component.CookieComponent; import com.petmatz.domain.user.entity.Certification; import com.petmatz.domain.user.entity.KakaoRegion; import com.petmatz.domain.user.entity.User; @@ -44,6 +45,7 @@ public class AuthService { private final GeocodingComponent geocodingComponent; private final AwsClient awsClient; // 추후에 수정 private final JwtManager jwtManager; + private final CookieComponent cookieComponent; private final AuthenticationComponent authenticationComponent; private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); @@ -79,16 +81,12 @@ public SignUpResponseDto signUp(SignUpInfo info) throws MalformedURLException { public SignInResponseDto signIn(SignInInfo info, HttpServletResponse response) throws AuthenticationException { User user = authenticationComponent.validateSignInCredentials(info); - String token = authenticationComponent.createJwtToken(user); - - ResponseCookie responseCookie = ResponseCookie.from("jwt", token) - .httpOnly(true) // XSS 방지 - .secure(true) // HTTPS만 허용 - .path("/") // 모든 경로에서 접근 가능 - .sameSite("None") // SameSite=None 설정 - .maxAge((3600)) - .build(); - response.addHeader("Set-Cookie", responseCookie.toString()); + String accessToken = authenticationComponent.createJwtAccessToken(user); + String refreshToken = authenticationComponent.createJwtRefreshToken(user); + + cookieComponent.setAccessTokenCookie(response, accessToken); + cookieComponent.setRefreshTokenCookie(response, refreshToken); + // 로그인 성공 응답 반환 return new SignInResponseDto(user); } diff --git a/src/main/java/com/petmatz/domain/user/service/KakaoUserService.java b/src/main/java/com/petmatz/domain/user/service/KakaoUserService.java index 638a3aa..d59444c 100644 --- a/src/main/java/com/petmatz/domain/user/service/KakaoUserService.java +++ b/src/main/java/com/petmatz/domain/user/service/KakaoUserService.java @@ -15,7 +15,7 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import static com.petmatz.domain.user.exception.MatchErrorCode.*; +import static com.petmatz.domain.user.exception.UserErrorCode.*; @Component @RequiredArgsConstructor diff --git a/src/main/java/com/petmatz/infra/email/EmailProviderImpl.java b/src/main/java/com/petmatz/infra/email/EmailProviderImpl.java index a0502cc..b5cda07 100644 --- a/src/main/java/com/petmatz/infra/email/EmailProviderImpl.java +++ b/src/main/java/com/petmatz/infra/email/EmailProviderImpl.java @@ -11,7 +11,7 @@ import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; -import static com.petmatz.domain.user.exception.MatchErrorCode.FAIL_MAIL_SEND; +import static com.petmatz.domain.user.exception.UserErrorCode.FAIL_MAIL_SEND; @Component @RequiredArgsConstructor diff --git a/src/main/java/com/petmatz/infra/email/RePasswordEmailProviderImpl.java b/src/main/java/com/petmatz/infra/email/RePasswordEmailProviderImpl.java index 4a87bba..6f0da76 100644 --- a/src/main/java/com/petmatz/infra/email/RePasswordEmailProviderImpl.java +++ b/src/main/java/com/petmatz/infra/email/RePasswordEmailProviderImpl.java @@ -11,7 +11,7 @@ import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; -import static com.petmatz.domain.user.exception.MatchErrorCode.FAIL_MAIL_SEND; +import static com.petmatz.domain.user.exception.UserErrorCode.FAIL_MAIL_SEND; @Component @RequiredArgsConstructor diff --git a/src/main/java/com/petmatz/infra/jwt/JwtProvider.java b/src/main/java/com/petmatz/infra/jwt/JwtProvider.java deleted file mode 100644 index 7e94cdb..0000000 --- a/src/main/java/com/petmatz/infra/jwt/JwtProvider.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.petmatz.infra.jwt; - -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import javax.crypto.SecretKey; -import java.nio.charset.StandardCharsets; -import java.security.Key; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Date; - -@Component -public class JwtProvider { - @Value("${secret-access-key}") - private String accessKey; - - @Value("${secret-refresh-key}") - private String refreshKey; - - public String createAccessToken(Long userId, String accountId) { - // 토큰 만료 시간 설정 (1시간 후) - Date expireDate = Date.from(Instant.now().plus(24, ChronoUnit.HOURS)); - - // 비밀 키 생성 - Key key = Keys.hmacShaKeyFor(accessKey.getBytes(StandardCharsets.UTF_8)); - - // JWT 토큰 생성 - return Jwts.builder() - .signWith(key, SignatureAlgorithm.HS256) // 서명 알고리즘 및 키 설정 - .setSubject(userId.toString()) // 서브젝트에 사용자 ID 설정 - .claim("accountId", accountId) // 계정 ID를 클레임으로 추가 - .setIssuedAt(new Date()) // 토큰 발행 시간 - .setExpiration(expireDate) // 토큰 만료 시간 - .compact(); - } - - public String createRefreshToken(Long userId) { - Date expireDate = Date.from(Instant.now().plus(2, ChronoUnit.WEEKS)); - SecretKey secretKey = Keys.hmacShaKeyFor(refreshKey.getBytes(StandardCharsets.UTF_8)); - - return Jwts.builder() - .signWith(secretKey, SignatureAlgorithm.RS256) - .setSubject(userId.toString()) - .setIssuedAt(new Date()) - .setExpiration(expireDate) - .compact(); - } - - public boolean validate(String jwt, boolean isAccessToken) { - try { - Key key; - if (isAccessToken) { - key = Keys.hmacShaKeyFor(accessKey.getBytes(StandardCharsets.UTF_8)); - } else { - key = Keys.hmacShaKeyFor(refreshKey.getBytes(StandardCharsets.UTF_8)); - } - Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(jwt); - return true; - } catch (Exception e) { - return false; - } - } -} diff --git a/src/main/java/com/petmatz/infra/redis/component/RedisTokenComponent.java b/src/main/java/com/petmatz/infra/redis/component/RedisTokenComponent.java index 52b66ea..87948ba 100644 --- a/src/main/java/com/petmatz/infra/redis/component/RedisTokenComponent.java +++ b/src/main/java/com/petmatz/infra/redis/component/RedisTokenComponent.java @@ -11,7 +11,6 @@ public class RedisTokenComponent { private final RedisTemplate redisTemplate; - public void saveRefreshToken(Long userId, String refreshToken) { String redisKey = "refreshToken:" + userId; try { @@ -20,4 +19,8 @@ public void saveRefreshToken(Long userId, String refreshToken) { throw new IllegalStateException("일단은 리프레시 토큰 저장 오류입니다!"); } } + public String getRefreshTokenFromRedis(Long userId) { + String redisKey = "refreshToken:" + userId; + return (String) redisTemplate.opsForValue().get(redisKey); + } }