From bf010f1aa2ba2e77f4946aa70a931306b4e8ad28 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Mon, 10 Mar 2025 22:18:33 +0900 Subject: [PATCH 01/64] =?UTF-8?q?Refactor:=20=ED=86=A0=ED=81=B0,=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=ED=83=88=ED=87=B4=20=EB=A1=9C=EC=A7=81,=20Au?= =?UTF-8?q?th=20=EA=B4=80=EB=A0=A8=20DTO=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/CommonAuthController.java | 11 +- .../auth/controller/KakaoAuthController.java | 8 +- .../auth/controller/LocalAuthController.java | 7 +- .../dto/request/KakaoRequestDTO.java | 28 ----- .../dto/request/LocalRequestDTO.java | 10 +- ...eRequestDTO.java => SocialRequestDTO.java} | 4 +- .../dto/response/AppleResponseDTO.java | 42 ------- .../dto/response/CommonResponseDTO.java | 4 +- .../dto/response/LocalResponseDTO.java | 7 -- ...esponseDTO.java => SocialResponseDTO.java} | 34 ++++-- .../domain/auth/converter/AuthConverter.java | 4 +- .../domain/auth/service/AppleAuthService.java | 79 ++++--------- .../auth/service/CommonAuthService.java | 94 +++------------ .../domain/auth/service/KakaoAuthService.java | 66 +++-------- .../domain/auth/service/LocalAuthService.java | 16 +-- .../domain/auth/service/TokenService.java | 107 ++++++++++++++++++ 16 files changed, 215 insertions(+), 306 deletions(-) delete mode 100644 src/main/java/haru/harudrawer/domain/auth/controller/dto/request/KakaoRequestDTO.java rename src/main/java/haru/harudrawer/domain/auth/controller/dto/request/{AppleRequestDTO.java => SocialRequestDTO.java} (90%) delete mode 100644 src/main/java/haru/harudrawer/domain/auth/controller/dto/response/AppleResponseDTO.java rename src/main/java/haru/harudrawer/domain/auth/controller/dto/response/{KakaoResponseDTO.java => SocialResponseDTO.java} (74%) create mode 100644 src/main/java/haru/harudrawer/domain/auth/service/TokenService.java diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/CommonAuthController.java b/src/main/java/haru/harudrawer/domain/auth/controller/CommonAuthController.java index 2136191..b16adc5 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/CommonAuthController.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/CommonAuthController.java @@ -1,5 +1,6 @@ package haru.harudrawer.domain.auth.controller; +import haru.harudrawer.domain.auth.service.TokenService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -20,6 +21,7 @@ public class CommonAuthController { private final CommonAuthService commonAuthService; + private final TokenService tokenService; @PostMapping("/logout") @Operation(summary = "공통 로그아웃", description = "로그아웃을 처리합니다. Authorization 헤더에 accessToken을 첨부해서 요청하시면 됩니다. \n 응답 코드에 따른 자세한 결과는 PostMan API 명세서를 참고 부탁드립니다.") @@ -32,14 +34,14 @@ public ResponseEntity> logout(HttpServletRequest reque @PostMapping("/validate-token") @Operation(summary = "토큰 유효성 검증", description = "Access Token에 대해 유효성 검사를 처리합니다. \n 응답 코드에 따른 자세한 결과는 PostMan API 명세서를 참고 부탁드립니다.") public ResponseEntity> validateToken(HttpServletRequest request) { - commonAuthService.validateToken(request); + tokenService.validateToken(request); return ResponseEntity.ok(ApiResponse.of(ResponseCode.CONFIRM)); } @PutMapping("/me/email") @Operation(summary = "회원 이메일 수정", description = "회원 이메일 수정을 처리합니다. \n 응답 코드에 따른 자세한 결과는 PostMan API 명세서를 참고 부탁드립니다.") - public ResponseEntity> updateUserEmail(@RequestBody CommonRequestDTO.UpdateEmailDTO request) { + public ResponseEntity> updateUserEmail(@RequestBody CommonRequestDTO.UpdateEmailDTO request) { return ResponseEntity.ok(ApiResponse.of(commonAuthService.updateUserEmail(request))); } @@ -71,9 +73,8 @@ public ResponseEntity> getUserInfo @PostMapping("/reissue") @Operation(summary = "Access Token 재발급", description = "Access Token 재발급을 처리합니다. \n 응답 코드에 따른 자세한 결과는 PostMan API 명세서를 참고 부탁드립니다.") - public ResponseEntity> refreshAccessToken(@RequestBody CommonRequestDTO.TokenRefreshDTO request) { - LocalResponseDTO.LocalLoginResponseDTO token = commonAuthService.refreshToken(request); - + public ResponseEntity> refreshAccessToken(@RequestBody CommonRequestDTO.TokenRefreshDTO request) { + CommonResponseDTO.LoginResponseDTO token = tokenService.refreshToken(request); return ResponseEntity.ok(ApiResponse.of(token)); diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java b/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java index aa3b6d8..db18f5c 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java @@ -1,6 +1,7 @@ package haru.harudrawer.domain.auth.controller; +import haru.harudrawer.domain.auth.controller.dto.request.SocialRequestDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -8,7 +9,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import haru.harudrawer.domain.auth.controller.dto.request.KakaoRequestDTO; import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; import haru.harudrawer.domain.auth.service.KakaoAuthService; import haru.harudrawer.global.response.ApiResponse; @@ -38,7 +38,7 @@ public ResponseEntity> login(@Re @PostMapping("/signup/kakao") @Operation(summary = "카카오 회원가입", description = "카카오 소셜 회원가입을 처리합니다. 이메일, 닉네임을 제공해야 합니다.") - public ResponseEntity> signup(@RequestBody KakaoRequestDTO.KakaoSignupDTO request) { + public ResponseEntity> signup(@RequestBody SocialRequestDTO.SocialSignupDTO request) { kakaoAuthService.signup(request); return ResponseEntity.status(HttpStatus.CREATED) .body(ApiResponse.of(ResponseCode.CREATED)); @@ -46,8 +46,8 @@ public ResponseEntity> signup(@RequestBody KakaoReques @DeleteMapping("/kakao") @Operation(summary = "카카오 회원 탈퇴", description = "카카오 소셜 회원 탈퇴(연결 해제)를 처리합니다. kakaoAccessToken을 제공해야 합니다.") - public ResponseEntity> delete(@RequestParam String accessToken) { - kakaoAuthService.delete(accessToken); + public ResponseEntity> delete(@RequestParam String kakaoAccessToken) { + kakaoAuthService.delete(kakaoAccessToken); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); } diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/LocalAuthController.java b/src/main/java/haru/harudrawer/domain/auth/controller/LocalAuthController.java index 2d6ef72..d465749 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/LocalAuthController.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/LocalAuthController.java @@ -2,6 +2,7 @@ import haru.harudrawer.domain.auth.controller.dto.request.LocalRequestDTO; import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; +import haru.harudrawer.domain.auth.service.CommonAuthService; import haru.harudrawer.domain.auth.service.LocalAuthService; import haru.harudrawer.global.response.ApiResponse; import haru.harudrawer.global.response.ResponseCode; @@ -9,6 +10,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.mail.MessagingException; import jakarta.validation.Valid; +import jakarta.validation.constraints.Email; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -25,6 +27,7 @@ public class LocalAuthController { private final LocalAuthService localAuthService; + private final CommonAuthService commonAuthService; @PostMapping("/signup/local") @Operation(summary = "로컬 회원가입", description = "로컬 회원가입을 처리합니다. 이메일, 비밀번호, 닉네임을 제공해야 합니다. \n 응답코드에 따른 결과값은 포스트맨 API 명세서를 참고 부탁드립니다.") @@ -46,7 +49,7 @@ public ResponseEntity> login(@Va @DeleteMapping("/local") @Operation(summary = "로컬 회원탈퇴", description = "회원탈퇴를 처리합니다. Authorization 헤더에 accessToken을 첨부해서 요청하시면 됩니다. \n 응답 코드에 따른 자세한 결과는 PostMan API 명세서를 참고 부탁드립니다.") public ResponseEntity> withdrawal() { - localAuthService.delete(); + commonAuthService.deleteUser(); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); } @@ -69,7 +72,7 @@ public ResponseEntity> checkEmailForRecovery(@RequestP @PostMapping("/send-verification") @Operation(summary = "인증 번호 이메일 전송", description = "이메일로 인증 번호 전송을 처리합니다. 이메일을 제공해야 합니다. \n 응답코드에 따른 결과값은 포스트맨 API 명세서를 참고 부탁드립니다.") - public ResponseEntity> sendEmail(@RequestParam("userEmail") String userEmail) throws MessagingException { + public ResponseEntity> sendEmail(@RequestParam @Email String userEmail) throws MessagingException { localAuthService.sendEmail(userEmail); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/KakaoRequestDTO.java b/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/KakaoRequestDTO.java deleted file mode 100644 index 5a5fb84..0000000 --- a/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/KakaoRequestDTO.java +++ /dev/null @@ -1,28 +0,0 @@ -package haru.harudrawer.domain.auth.controller.dto.request; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; -import lombok.Builder; -import lombok.Getter; - -public class KakaoRequestDTO { - - @Builder - @Getter - public static class KakaoSignupDTO { - - @NotBlank(message = "이메일은 필수 입력 항목입니다.") - @Email(message = "이메일 형식이 올바르지 않습니다.") - @Size(max = 50, message = "이메일은 최대 50자까지 가능합니다.") - private String userEmail; - - @NotBlank(message = "닉네임은 필수 입력 항목입니다.") - @Size(max = 20, message = "닉네임은 최대 20자까지 가능합니다.") - private String nickName; - - } - /////////////////////////////////////////////////////////////////// - - -} diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/LocalRequestDTO.java b/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/LocalRequestDTO.java index 0c5ddb6..169f301 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/LocalRequestDTO.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/LocalRequestDTO.java @@ -2,6 +2,7 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; import lombok.Builder; import lombok.Getter; @@ -20,6 +21,7 @@ public static class SignUpRequestDTO{ @NotBlank(message = "비밀번호는 필수 입력 항목입니다.") @Size(min = 8, max = 20, message = "비밀번호는 8자 이상 20자 이하로 입력해주세요.") + @Pattern(regexp = "^(?=.*[A-Z])(?=.*[@$!%*?&]).{8,16}$", message = "비밀번호 형식이 잘못되었습니다.") private String password; @NotBlank(message = "닉네임은 필수 입력 항목입니다.") @@ -30,8 +32,6 @@ public static class SignUpRequestDTO{ @Size(max = 25, message = "핸드폰 번호는 최대 25자까지 가능합니다.") private String phoneNumber; - private String userImgUrl; - } @Getter @@ -50,18 +50,24 @@ public static class LoginRequestDTO{ @Builder public static class ResetPasswordDTO{ @NotBlank(message = "사용자 이메일은 필수 입력 항목입니다.") + @Email(message = "이메일 형식이 올바르지 않습니다.") private String userEmail; @NotBlank(message = "변경 비밀번호는 필수 입력 항목입니다.") + @Size(min = 8, max = 20, message = "비밀번호는 8자 이상 20자 이하로 입력해주세요.") + @Pattern(regexp = "^(?=.*[A-Z])(?=.*[@$!%*?&]).{8,16}$") private String newPassword; @NotBlank(message = "변경 확인 비밀번호는 필수 입력 항목입니다.") + @Size(min = 8, max = 20, message = "비밀번호는 8자 이상 20자 이하로 입력해주세요.") + @Pattern(regexp = "^(?=.*[A-Z])(?=.*[@$!%*?&]).{8,16}$") private String newPasswordCheck; } @Getter @Builder public static class VerifyCodeDTO { + @Email(message = "이메일 형식이 올바르지 않습니다.") private String userEmail; private String code; } diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/AppleRequestDTO.java b/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/SocialRequestDTO.java similarity index 90% rename from src/main/java/haru/harudrawer/domain/auth/controller/dto/request/AppleRequestDTO.java rename to src/main/java/haru/harudrawer/domain/auth/controller/dto/request/SocialRequestDTO.java index da098a4..e932e78 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/AppleRequestDTO.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/SocialRequestDTO.java @@ -6,11 +6,11 @@ import lombok.Builder; import lombok.Getter; -public class AppleRequestDTO { +public class SocialRequestDTO { @Builder @Getter - public static class AppleSignupDTO { + public static class SocialSignupDTO { @NotBlank(message = "이메일은 필수 입력 항목입니다.") @Email(message = "이메일 형식이 올바르지 않습니다.") diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/AppleResponseDTO.java b/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/AppleResponseDTO.java deleted file mode 100644 index c8b19e8..0000000 --- a/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/AppleResponseDTO.java +++ /dev/null @@ -1,42 +0,0 @@ -package haru.harudrawer.domain.auth.controller.dto.response; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Builder; -import lombok.Getter; - -public class AppleResponseDTO { - - - @Getter - @Builder - public static class AppleTokenInfoDTO{ - - @JsonProperty("access_token") - private String accessToken; - - @JsonProperty("expires_in") - private int expiresIn; - - @JsonProperty("id_token") - private String idToken; - - @JsonProperty("refresh_token") - private String refreshToken; - - @JsonProperty("token_type") - private String tokenType; - } - - @Getter - @Builder - public static class AppleLoginResponseDTO { - - private boolean requireSignup; - - private String appleEmail; - - private String accessToken; - private String refreshToken; - - } -} diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/CommonResponseDTO.java b/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/CommonResponseDTO.java index f64b8cd..5ed2803 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/CommonResponseDTO.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/CommonResponseDTO.java @@ -22,7 +22,6 @@ public static class GetUserInfoDTO { private Provider provider; - } @Getter @@ -30,9 +29,10 @@ public static class GetUserInfoDTO { @JsonInclude(JsonInclude.Include.NON_NULL) public static class LoginResponseDTO { + @JsonInclude(JsonInclude.Include.NON_DEFAULT) private boolean requiresSignup; - private String email; + private String userEmail; private TokenDTO tokens; } diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/LocalResponseDTO.java b/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/LocalResponseDTO.java index 43e3537..0e10d79 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/LocalResponseDTO.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/LocalResponseDTO.java @@ -5,11 +5,4 @@ public class LocalResponseDTO { - @Getter - @Builder - public static class LocalLoginResponseDTO{ - private String accessToken; - private String refreshToken; - } - } diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/KakaoResponseDTO.java b/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/SocialResponseDTO.java similarity index 74% rename from src/main/java/haru/harudrawer/domain/auth/controller/dto/response/KakaoResponseDTO.java rename to src/main/java/haru/harudrawer/domain/auth/controller/dto/response/SocialResponseDTO.java index 033089d..efba1da 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/KakaoResponseDTO.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/SocialResponseDTO.java @@ -8,16 +8,35 @@ import java.util.Date; import java.util.Map; -public class KakaoResponseDTO { +public class SocialResponseDTO { + /** + * APPLE DTO + */ @Getter @Builder - public static class KakaoLoginResponseDTO { - private boolean requiresSignup; - private String kakaoEmail; - private Map tokens; + public static class AppleTokenInfoDTO{ + + @JsonProperty("access_token") + private String accessToken; + + @JsonProperty("expires_in") + private int expiresIn; + + @JsonProperty("id_token") + private String idToken; + + @JsonProperty("refresh_token") + private String refreshToken; + + @JsonProperty("token_type") + private String tokenType; } + + /** + * KAKAO DTO + */ @Getter @Builder public static class KakaoTokenDTO{ @@ -63,11 +82,11 @@ public static class KakaoUserInfoDTO { //uuid 등 추가 정보 @JsonProperty("for_partner") - private KakaoResponseDTO.Partner partner; + private SocialResponseDTO.Partner partner; //사용자 이메일 정보 @JsonProperty("kakao_account") - private KakaoResponseDTO.KakaoAccount kakaoAccount; + private SocialResponseDTO.KakaoAccount kakaoAccount; } @Getter @@ -86,5 +105,4 @@ public static class Partner { @JsonProperty("uuid") private String uuid; } - } diff --git a/src/main/java/haru/harudrawer/domain/auth/converter/AuthConverter.java b/src/main/java/haru/harudrawer/domain/auth/converter/AuthConverter.java index 9eb185d..846884f 100644 --- a/src/main/java/haru/harudrawer/domain/auth/converter/AuthConverter.java +++ b/src/main/java/haru/harudrawer/domain/auth/converter/AuthConverter.java @@ -1,7 +1,7 @@ package haru.harudrawer.domain.auth.converter; +import haru.harudrawer.domain.auth.controller.dto.request.SocialRequestDTO; import org.springframework.stereotype.Component; -import haru.harudrawer.domain.auth.controller.dto.request.KakaoRequestDTO; import haru.harudrawer.domain.auth.entity.Provider; import haru.harudrawer.domain.auth.entity.Role; import haru.harudrawer.domain.auth.entity.User; @@ -9,7 +9,7 @@ @Component public class AuthConverter { - public User signupToKakaoUserEntity(KakaoRequestDTO.KakaoSignupDTO request) { + public User signupToKakaoUserEntity(SocialRequestDTO.SocialSignupDTO request) { return User.builder() .nickName(request.getNickName()) .userEmail(request.getUserEmail()) diff --git a/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java index ddfcb5e..070ce45 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java @@ -4,8 +4,8 @@ import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jwt.SignedJWT; -import haru.harudrawer.domain.auth.controller.dto.response.AppleResponseDTO; import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; +import haru.harudrawer.domain.auth.controller.dto.response.SocialResponseDTO; import haru.harudrawer.domain.auth.converter.AuthConverter; import haru.harudrawer.domain.auth.entity.Provider; import haru.harudrawer.domain.auth.entity.User; @@ -13,9 +13,6 @@ import haru.harudrawer.domain.auth.exception.AuthException; import haru.harudrawer.domain.auth.repository.AuthRepository; import haru.harudrawer.global.redis.RedisService; -import haru.harudrawer.global.s3.S3Service; -import haru.harudrawer.global.security.domain.CustomUserDetails; -import haru.harudrawer.global.security.jwt.JwtProvider; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @@ -29,7 +26,6 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -73,20 +69,31 @@ public class AppleAuthService { private String privateKeyFileUrl; private final AuthRepository authRepository; - private final JwtProvider jwtProvider; private final RestTemplate restTemplate; - private final S3Service s3Service; private final RedisService redisService; private final AuthConverter authConverter; + private final TokenService tokenService; + private final CommonAuthService commonAuthService; + /** + * 회원 가입 + */ + public User signup(String userEmail) { + User newUser = authConverter.userEmailToAppleUserEntity(userEmail); + + authRepository.save(newUser); + + return newUser; + } + /** * 애플 로그인 */ public CommonResponseDTO.LoginResponseDTO login(String authorizationCode) throws Exception { // 토큰 조회 - AppleResponseDTO.AppleTokenInfoDTO loginResponse = requestAppleToken(authorizationCode); + SocialResponseDTO.AppleTokenInfoDTO loginResponse = requestAppleToken(authorizationCode); // idToken에서 사용자 이메일 조회 String userEmail = extractEmailFromIdToken(loginResponse.getIdToken()); @@ -107,8 +114,7 @@ public CommonResponseDTO.LoginResponseDTO login(String authorizationCode) throws } // 사용자가 존재하지 않으면 회원가입 후 로그인 진행 - User newUser = authConverter.userEmailToAppleUserEntity(userEmail); - authRepository.save(newUser); + User newUser = signup(userEmail); return createLoginResponse(newUser); } @@ -118,56 +124,16 @@ public CommonResponseDTO.LoginResponseDTO login(String authorizationCode) throws */ private CommonResponseDTO.LoginResponseDTO createLoginResponse(User user) { // 토큰 생성 후 Redis에 저장 - CommonResponseDTO.TokenDTO tokens = createTokens(user); + CommonResponseDTO.TokenDTO tokens = tokenService.createTokens(user); redisService.saveRefreshToken(user.getUserEmail(), tokens.getRefreshToken()); return CommonResponseDTO.LoginResponseDTO.builder() .requiresSignup(false) - .email(user.getUserEmail()) + .userEmail(user.getUserEmail()) .tokens(tokens) .build(); } - /** - * 애플 회원 탈퇴 - */ - private void deleteUser() { - User user = authRepository.findById(jwtProvider.extractUserId()).orElseThrow( - () -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); - - // s3에 저장된 user의 이미지 삭제 - s3Service.deleteUserImgList(user); - - // DB 유저 삭제 - authRepository.delete(user); - } - - /** - * 서버 자체 AccessToken, RefreshToken 생성 - */ - private CommonResponseDTO.TokenDTO createTokens(User user) { - - CustomUserDetails userDetails = CustomUserDetails.builder() - .userId(user.getUserId()) - .email(user.getUserEmail()) - .password(null) - .provider(user.getProvider()) - .nickName(user.getNickName()) - .authorities(List.of(new SimpleGrantedAuthority(user.getRole().name()))) - .build(); - - String accessToken = jwtProvider.createAccessToken(userDetails); - - String refreshToken = jwtProvider.createRefreshToken(user.getUserEmail()); - - redisService.saveRefreshToken(user.getUserEmail(), refreshToken); - - return CommonResponseDTO.TokenDTO.builder() - .accessToken(accessToken) - .refreshToken(refreshToken) - .build(); - } - /* * Apple 요청 메소드 @@ -176,7 +142,7 @@ private CommonResponseDTO.TokenDTO createTokens(User user) { /** * 애플 토큰 요청 */ - private AppleResponseDTO.AppleTokenInfoDTO requestAppleToken(String authorizationCode) throws Exception { + private SocialResponseDTO.AppleTokenInfoDTO requestAppleToken(String authorizationCode) throws Exception { // clientSecret 생성 (앞서 구현한 makeClientSecret() 메서드 사용) String clientSecret = createClientSecret(); @@ -186,7 +152,7 @@ private AppleResponseDTO.AppleTokenInfoDTO requestAppleToken(String authorizatio return restTemplate.postForEntity( "https://appleid.apple.com/auth/token", createAppleRequestEntity(authorizationCode, clientSecret), - AppleResponseDTO.AppleTokenInfoDTO.class).getBody(); + SocialResponseDTO.AppleTokenInfoDTO.class).getBody(); } catch (HttpClientErrorException e){ // 에러 발생 시 로그 출력 및 예외 처리 log.error("Apple Request Token API 호출 실패: 상태 코드 {}, 응답 본문 {}", e.getStatusCode(), e.getResponseBodyAsString()); @@ -211,7 +177,7 @@ public void revokeAppleToken(String authorizationCode) throws Exception { appleRequestEntity.getHeaders().add("token", refreshToken); // user 삭제 - deleteUser(); + commonAuthService.deleteUser(); // revoke 요청 restTemplate.postForEntity( @@ -220,7 +186,6 @@ public void revokeAppleToken(String authorizationCode) throws Exception { String.class ); - } catch (HttpClientErrorException e) { log.error("Apple revoke API 호출 실패: 상태 코드 {}, 응답 본문 {}", e.getStatusCode(), e.getResponseBodyAsString()); throw new AuthException(AuthErrorCode.APPLE_AUTH_FAILED); @@ -268,7 +233,6 @@ private String createClientSecret() throws Exception { .compact(); } - /** * ClientSecret에 사용할 privateKey 생성 */ @@ -321,7 +285,6 @@ public PublicKey getApplePublicKey(String kid) throws Exception { throw new AuthException(AuthErrorCode.APPLE_PUBLIC_KEY_NOT_FOUND); } - /** * AppleIdToken에서 사용자 이메일 추출 * diff --git a/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java index 005d077..cbe65da 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java @@ -34,22 +34,15 @@ public class CommonAuthService { private final AuthRepository authRepository; private final PasswordEncoder passwordEncoder; private final RedisService redisService; + private final TokenService tokenService; + private final S3Service s3Service; - // Authorization 헤더에서 실제 JWT 토큰 문자열만 추출 - private String resolveToken(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (bearerToken != null && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); - } - return null; - } - /** * 사용자 로그 아웃 */ public void logout(HttpServletRequest request) { - String token = resolveToken(request); + String token = tokenService.resolveToken(request); // 토큰 유효성 검사 if (!jwtProvider.validateToken(token)) { @@ -57,19 +50,6 @@ public void logout(HttpServletRequest request) { } redisService.deleteRefreshToken(jwtProvider.getUserEmail(token)); - - - } - - /** - * 토큰 검증 - */ - public void validateToken(HttpServletRequest request) { - String token = resolveToken(request); - - if (!jwtProvider.validateToken(token)) { - throw new AuthException(AuthErrorCode.INVALID_TOKEN); - } } @@ -79,10 +59,10 @@ public void validateToken(HttpServletRequest request) { public CommonResponseDTO.GetUserInfoDTO getUserInfo(HttpServletRequest request) { // 토큰 검증 - validateToken(request); + tokenService.validateToken(request); // 토큰을 통해 사용자 이메일 조회 - String userEmail = jwtProvider.getUserEmail(resolveToken(request)); + String userEmail = jwtProvider.getUserEmail(tokenService.resolveToken(request)); User findUser = authRepository.findByUserEmail(userEmail).orElseThrow( () -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); @@ -99,27 +79,17 @@ public CommonResponseDTO.GetUserInfoDTO getUserInfo(HttpServletRequest request) /** * 사용자 이메일 수정 */ - public LocalResponseDTO.LocalLoginResponseDTO updateUserEmail(CommonRequestDTO.UpdateEmailDTO request) { + public CommonResponseDTO.LoginResponseDTO updateUserEmail(CommonRequestDTO.UpdateEmailDTO request) { User user = authRepository.findByUserId(jwtProvider.extractUserId()) .orElseThrow(() -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); user.updateEmail(request.getUserEmail()); - CustomUserDetails userDetails = CustomUserDetails.builder() - .userId(user.getUserId()) - .email(request.getUserEmail()) - .password(user.getPassword()) - .provider(user.getProvider()) - .authorities(Collections.singletonList(new SimpleGrantedAuthority(user.getRole().name()))) - .build(); - - String accessToken = jwtProvider.createAccessToken(userDetails); - String refreshToken = jwtProvider.createRefreshToken(request.getUserEmail()); + CommonResponseDTO.TokenDTO tokens = tokenService.createTokens(user); - return LocalResponseDTO.LocalLoginResponseDTO.builder() - .accessToken(accessToken) - .refreshToken(refreshToken) + return CommonResponseDTO.LoginResponseDTO.builder() + .tokens(tokens) .build(); } @@ -156,7 +126,7 @@ public void updateUserPassword(CommonRequestDTO.UpdatePasswordDTO request) { // 비밀번호 서식 틀렸을 경우 예외처리 if (!request.getNewPassword().matches("^(?=.*[A-Z])(?=.*[@$!%*?&]).{8,16}$") - || !request.getNewPasswordCheck().matches("^(?=.*[A-Z])(?=.*[@$!%*?&]).{8,16}$") ) { + || !request.getNewPasswordCheck().matches("^(?=.*[A-Z])(?=.*[@$!%*?&]).{8,16}$")) { throw new AuthException(AuthErrorCode.INVALID_PASSWORD); } @@ -170,50 +140,16 @@ public void updateUserPassword(CommonRequestDTO.UpdatePasswordDTO request) { } } - /** - * refresh Token으로 Access Token 재발급 - */ - public LocalResponseDTO.LocalLoginResponseDTO refreshToken(CommonRequestDTO.TokenRefreshDTO request) { - // 사용자 이메일 조회 - String userEmail = jwtProvider.getUserEmail(request.getRefreshToken()); - - // redis에서 refresh token 조회 - Optional findTokenOpt = redisService.getRefreshToken(userEmail); - - // refresh token 검증 - if (findTokenOpt.isEmpty() || !findTokenOpt.get().equals(request.getRefreshToken())) { - throw new AuthException(AuthErrorCode.INVALID_TOKEN); - } - - // 이메일로 사용자 정보 DB 조회 - User user = authRepository.findByUserEmail(userEmail).orElseThrow( + public void deleteUser() { + User user = authRepository.findById(jwtProvider.extractUserId()).orElseThrow( () -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); - // 유저 객체 생성 - CustomUserDetails userDetails = new CustomUserDetails( - user.getUserId(), - user.getUserEmail(), - null, - user.getNickName(), - user.getProvider(), - List.of(new SimpleGrantedAuthority(user.getRole().name())) - ); - - String accessToken = jwtProvider.createAccessToken(userDetails); - String refreshToken = jwtProvider.createRefreshToken(userDetails.getEmail()); + s3Service.deleteUserImgList(user); - // redis에서 사용된 refresh token삭제 - redisService.deleteRefreshToken(userEmail); + authRepository.delete(user); - // 새로운 refresh token 저장 - redisService.saveRefreshToken(userEmail, refreshToken); - - return LocalResponseDTO.LocalLoginResponseDTO.builder() - .accessToken(accessToken) - .refreshToken(refreshToken) - .build(); + redisService.deleteRefreshToken(user.getUserEmail()); } - } diff --git a/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java index 4911708..7377730 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java @@ -1,18 +1,17 @@ package haru.harudrawer.domain.auth.service; +import haru.harudrawer.domain.auth.controller.dto.request.SocialRequestDTO; +import haru.harudrawer.domain.auth.controller.dto.response.SocialResponseDTO; import haru.harudrawer.global.redis.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.*; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.MultiValueMap; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; -import haru.harudrawer.domain.auth.controller.dto.request.KakaoRequestDTO; import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; -import haru.harudrawer.domain.auth.controller.dto.response.KakaoResponseDTO; import haru.harudrawer.domain.auth.converter.AuthConverter; import haru.harudrawer.domain.auth.entity.Provider; import haru.harudrawer.domain.auth.entity.User; @@ -20,11 +19,7 @@ import haru.harudrawer.domain.auth.exception.AuthException; import haru.harudrawer.domain.auth.repository.AuthRepository; import haru.harudrawer.global.exception.CommonErrorCode; -import haru.harudrawer.global.s3.S3Service; -import haru.harudrawer.global.security.domain.CustomUserDetails; -import haru.harudrawer.global.security.jwt.JwtProvider; -import java.util.List; import java.util.Optional; @@ -32,17 +27,17 @@ @RequiredArgsConstructor @Slf4j @Transactional -public class KakaoAuthService { +public class KakaoAuthService{ private final RestTemplate restTemplate; private final AuthConverter authConverter; private final AuthRepository authRepository; - private final JwtProvider jwtProvider; - private final S3Service s3Service; private final RedisService redisService; + private final TokenService tokenService; + private final CommonAuthService commonAuthService; // 회원가입 - public void signup(KakaoRequestDTO.KakaoSignupDTO request) { + public void signup(SocialRequestDTO.SocialSignupDTO request) { //객체 변환후 저장 authRepository.save(authConverter.signupToKakaoUserEntity(request)); @@ -52,7 +47,7 @@ public void signup(KakaoRequestDTO.KakaoSignupDTO request) { public CommonResponseDTO.LoginResponseDTO login(String kakaoAccessToken) { // 사용자 정보 조회 - KakaoResponseDTO.KakaoUserInfoDTO userInfo = getKakaoUserInfo(kakaoAccessToken); + SocialResponseDTO.KakaoUserInfoDTO userInfo = getKakaoUserInfo(kakaoAccessToken); // 사용자 존재 유무 확인 Optional userOpt = authRepository.findByUserEmail(userInfo.getKakaoAccount().getKakaoEmail()); @@ -68,7 +63,7 @@ public CommonResponseDTO.LoginResponseDTO login(String kakaoAccessToken) { if (userOpt.isEmpty()) { return CommonResponseDTO.LoginResponseDTO.builder() .requiresSignup(true) - .email(userInfo.getKakaoAccount().getKakaoEmail()) + .userEmail(userInfo.getKakaoAccount().getKakaoEmail()) .tokens(null) .build(); } @@ -76,56 +71,27 @@ public CommonResponseDTO.LoginResponseDTO login(String kakaoAccessToken) { // 로그인 성공 User user = userOpt.get(); - CommonResponseDTO.TokenDTO tokens = createTokens(user); + CommonResponseDTO.TokenDTO tokens = tokenService.createTokens(user); // redis에 refresh token 저장 redisService.saveRefreshToken(user.getUserEmail(), tokens.getRefreshToken()); return CommonResponseDTO.LoginResponseDTO.builder() .requiresSignup(false) - .email(null) + .userEmail(null) .tokens(tokens) .build(); } - /** - * AccessToken 및 RefreshToken 생성 - */ - private CommonResponseDTO.TokenDTO createTokens(User user) { - - CustomUserDetails userDetails = CustomUserDetails.builder() - .userId(user.getUserId()) - .email(user.getUserEmail()) - .password(null) - .nickName(user.getNickName()) - .provider(user.getProvider()) - .authorities(List.of(new SimpleGrantedAuthority(user.getRole().name()))) - .build(); - - String accessToken = jwtProvider.createAccessToken(userDetails); - String refreshToken = jwtProvider.createRefreshToken(user.getUserEmail()); - - return CommonResponseDTO.TokenDTO.builder() - .accessToken(accessToken) - .refreshToken(refreshToken) - .build(); - } - /** * 카카오 회원 탈퇴 */ - public void delete(String accessToken) { + public void delete(String kakaoAccessToken) { // 카카오 연결 해제 - unlinkKakaoAccount(accessToken); - - User user = authRepository.findById(jwtProvider.extractUserId()).orElseThrow( - () -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); - - // s3 이미지 삭제 - s3Service.deleteUserImgList(user); + unlinkKakaoAccount(kakaoAccessToken); - // user 삭제 - authRepository.delete(user); + // 회원 삭제 + commonAuthService.deleteUser(); } /** @@ -146,12 +112,12 @@ private void unlinkKakaoAccount(String accessToken) { } // 카카오 사용자 정보 조회 - private KakaoResponseDTO.KakaoUserInfoDTO getKakaoUserInfo(String kakaoAccessToken) { + private SocialResponseDTO.KakaoUserInfoDTO getKakaoUserInfo(String kakaoAccessToken) { try { return restTemplate.postForEntity( "https://kapi.kakao.com/v2/user/me", createKakaoRequestEntity(kakaoAccessToken), - KakaoResponseDTO.KakaoUserInfoDTO.class + SocialResponseDTO.KakaoUserInfoDTO.class ).getBody(); } catch (HttpClientErrorException e) { log.error("Kakao 사용자 정보 조회 API 호출 실패: 상태 코드 {}, 응답 본문 {}", e.getStatusCode(), e.getResponseBodyAsString()); diff --git a/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java index 03181c2..fee90d6 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java @@ -40,7 +40,6 @@ public class LocalAuthService { private final JwtProvider jwtProvider; private final EmailRepository emailRepository; private final EmailService emailService; - private final S3Service s3Service; private final RedisService redisService; /** @@ -84,6 +83,7 @@ public CommonResponseDTO.LoginResponseDTO login(LocalRequestDTO.LoginRequestDTO // 인증 객체에서 사용자 정보 추출(Provider 추출 위해 작성) CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); + // 로컬로 가입한 유저가 아닐경우 에러 처리 if (!userDetails.getProvider().equals(Provider.LOCAL)) { throw new AuthException(AuthErrorCode.ALREADY_EXIST_SOCIAL_EMAIL); @@ -116,20 +116,6 @@ public CommonResponseDTO.LoginResponseDTO login(LocalRequestDTO.LoginRequestDTO } } - /** - * 회원 탈퇴 - */ - public void delete() { - - User user = authRepository.findByUserId(jwtProvider.extractUserId()) - .orElseThrow(() -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); - - // 탈퇴 회원이 저장한 사진 전체 삭제 - s3Service.deleteUserImgList(user); - - // 회원 탈퇴 처리 - authRepository.delete(user); - } /** * 아이디 찾기 diff --git a/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java b/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java new file mode 100644 index 0000000..e4cdeb5 --- /dev/null +++ b/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java @@ -0,0 +1,107 @@ +package haru.harudrawer.domain.auth.service; + +import haru.harudrawer.domain.auth.controller.dto.request.CommonRequestDTO; +import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; +import haru.harudrawer.domain.auth.controller.dto.response.LocalResponseDTO; +import haru.harudrawer.domain.auth.entity.Provider; +import haru.harudrawer.domain.auth.entity.User; +import haru.harudrawer.domain.auth.exception.AuthErrorCode; +import haru.harudrawer.domain.auth.exception.AuthException; +import haru.harudrawer.domain.auth.repository.AuthRepository; +import haru.harudrawer.global.redis.RedisService; +import haru.harudrawer.global.security.domain.CustomUserDetails; +import haru.harudrawer.global.security.jwt.JwtProvider; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class TokenService { + + private final JwtProvider jwtProvider; + private final RedisService redisService; + private final AuthRepository authRepository; + + public CommonResponseDTO.TokenDTO createTokens(User user) { + + CustomUserDetails userDetails = CustomUserDetails.builder() + .userId(user.getUserId()) + .email(user.getUserEmail()) + .provider(user.getProvider()) + .nickName(user.getNickName()) + .password(user.getProvider().equals(Provider.LOCAL) ? user.getPassword() : null) + .authorities(List.of(new SimpleGrantedAuthority(user.getRole().name()))) + .build(); + + String accessToken = jwtProvider.createAccessToken(userDetails); + String refreshToken = jwtProvider.createRefreshToken(user.getUserEmail()); + + redisService.saveRefreshToken(user.getUserEmail(), refreshToken); + + return CommonResponseDTO.TokenDTO.builder() + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + } + + public void deleteRefreshToken(String refreshToken) { + redisService.deleteRefreshToken(refreshToken); + } + + /** + * refresh Token으로 Access Token 재발급 + */ + public CommonResponseDTO.LoginResponseDTO refreshToken(CommonRequestDTO.TokenRefreshDTO request) { + // 사용자 이메일 조회 + String userEmail = jwtProvider.getUserEmail(request.getRefreshToken()); + + // redis에서 refresh token 조회 + Optional findTokenOpt = redisService.getRefreshToken(userEmail); + + // refresh token 검증 + if (findTokenOpt.isEmpty() || !findTokenOpt.get().equals(request.getRefreshToken())) { + throw new AuthException(AuthErrorCode.INVALID_TOKEN); + } + + // 이메일로 사용자 정보 DB 조회 + User user = authRepository.findByUserEmail(userEmail).orElseThrow( + () -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); + + // redis에서 만료된 RefreshToken 삭제 + deleteRefreshToken(request.getRefreshToken()); + + // 새 토큰 생성 + CommonResponseDTO.TokenDTO tokens = createTokens(user); + + return CommonResponseDTO.LoginResponseDTO.builder() + .tokens(tokens) + .build(); + } + + + /** + * 토큰 검증 + */ + public void validateToken(HttpServletRequest request) { + String token = resolveToken(request); + + if (!jwtProvider.validateToken(token)) { + throw new AuthException(AuthErrorCode.INVALID_TOKEN); + } + } + + + // Authorization 헤더에서 실제 JWT 토큰 문자열만 추출 + public String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } +} From 67bf72c7599ef4ccc7dbea082688f5fcd78ec3f2 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Tue, 11 Mar 2025 17:09:08 +0900 Subject: [PATCH 02/64] =?UTF-8?q?Refactor:=20=EC=95=A0=ED=94=8C=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=ED=83=88=ED=87=B4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=ED=86=A0=ED=81=B0=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20API=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AppleAuthController.java | 5 +- .../auth/controller/KakaoAuthController.java | 4 +- .../domain/auth/entity/TokenType.java | 5 ++ .../domain/auth/service/AppleAuthService.java | 56 +++++++++++++------ .../auth/service/CommonAuthService.java | 22 +++++++- .../domain/auth/service/KakaoAuthService.java | 8 +-- .../domain/auth/service/LocalAuthService.java | 2 +- .../domain/auth/service/TokenService.java | 24 ++++---- .../harudrawer/global/redis/RedisService.java | 20 ++++--- .../global/security/jwt/JwtProvider.java | 2 + src/main/resources/application.yml | 2 +- 11 files changed, 102 insertions(+), 48 deletions(-) create mode 100644 src/main/java/haru/harudrawer/domain/auth/entity/TokenType.java diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/AppleAuthController.java b/src/main/java/haru/harudrawer/domain/auth/controller/AppleAuthController.java index 9d59262..82cf710 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/AppleAuthController.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/AppleAuthController.java @@ -1,6 +1,7 @@ package haru.harudrawer.domain.auth.controller; import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -28,8 +29,8 @@ public ResponseEntity> login(@Re @DeleteMapping("/apple") @Operation(summary = "애플 회원 탈퇴", description = "애플 회원 탈퇴(토큰 회수)를 처리합니다. \n 응답 코드에 따른 자세한 결과는 PostMan API 명세서를 참고 부탁드립니다.") - public ResponseEntity> delete(@RequestParam String authorizationCode) throws Exception { - appleAuthService.revokeAppleToken(authorizationCode); + public ResponseEntity> delete(HttpServletRequest request) throws Exception { + appleAuthService.revokeAppleToken(request); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); } diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java b/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java index db18f5c..409059f 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java @@ -46,8 +46,8 @@ public ResponseEntity> signup(@RequestBody SocialReque @DeleteMapping("/kakao") @Operation(summary = "카카오 회원 탈퇴", description = "카카오 소셜 회원 탈퇴(연결 해제)를 처리합니다. kakaoAccessToken을 제공해야 합니다.") - public ResponseEntity> delete(@RequestParam String kakaoAccessToken) { - kakaoAuthService.delete(kakaoAccessToken); + public ResponseEntity> delete() { + kakaoAuthService.delete(); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); } diff --git a/src/main/java/haru/harudrawer/domain/auth/entity/TokenType.java b/src/main/java/haru/harudrawer/domain/auth/entity/TokenType.java new file mode 100644 index 0000000..f419a1e --- /dev/null +++ b/src/main/java/haru/harudrawer/domain/auth/entity/TokenType.java @@ -0,0 +1,5 @@ +package haru.harudrawer.domain.auth.entity; + +public enum TokenType { + ACCESS, REFRESH, SERVER +} diff --git a/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java index 070ce45..71ee983 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java @@ -8,14 +8,18 @@ import haru.harudrawer.domain.auth.controller.dto.response.SocialResponseDTO; import haru.harudrawer.domain.auth.converter.AuthConverter; import haru.harudrawer.domain.auth.entity.Provider; +import haru.harudrawer.domain.auth.entity.TokenType; import haru.harudrawer.domain.auth.entity.User; import haru.harudrawer.domain.auth.exception.AuthErrorCode; import haru.harudrawer.domain.auth.exception.AuthException; import haru.harudrawer.domain.auth.repository.AuthRepository; import haru.harudrawer.global.redis.RedisService; +import haru.harudrawer.global.security.jwt.JwtProvider; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; @@ -74,6 +78,7 @@ public class AppleAuthService { private final AuthConverter authConverter; private final TokenService tokenService; private final CommonAuthService commonAuthService; + private final JwtProvider jwtProvider; /** @@ -123,9 +128,9 @@ public CommonResponseDTO.LoginResponseDTO login(String authorizationCode) throws * 토큰 생성 및 로그인 응답 생성 */ private CommonResponseDTO.LoginResponseDTO createLoginResponse(User user) { + // 토큰 생성 후 Redis에 저장 CommonResponseDTO.TokenDTO tokens = tokenService.createTokens(user); - redisService.saveRefreshToken(user.getUserEmail(), tokens.getRefreshToken()); return CommonResponseDTO.LoginResponseDTO.builder() .requiresSignup(false) @@ -134,7 +139,6 @@ private CommonResponseDTO.LoginResponseDTO createLoginResponse(User user) { .build(); } - /* * Apple 요청 메소드 */ @@ -149,10 +153,17 @@ private SocialResponseDTO.AppleTokenInfoDTO requestAppleToken(String authorizati try{ // 애플 토큰 엔드포인트에 POST 요청 - return restTemplate.postForEntity( + SocialResponseDTO.AppleTokenInfoDTO response = restTemplate.postForEntity( "https://appleid.apple.com/auth/token", - createAppleRequestEntity(authorizationCode, clientSecret), + createAppleRequestEntity(Optional.of(authorizationCode), clientSecret, Optional.empty()), SocialResponseDTO.AppleTokenInfoDTO.class).getBody(); + + String userEmail = extractEmailFromIdToken(response.getIdToken()); + + // APPLE Refresh token -> redis에 저장 + redisService.saveToken(userEmail, response.getRefreshToken(), Provider.APPLE, TokenType.REFRESH); + + return response; } catch (HttpClientErrorException e){ // 에러 발생 시 로그 출력 및 예외 처리 log.error("Apple Request Token API 호출 실패: 상태 코드 {}, 응답 본문 {}", e.getStatusCode(), e.getResponseBodyAsString()); @@ -163,21 +174,21 @@ private SocialResponseDTO.AppleTokenInfoDTO requestAppleToken(String authorizati /** * 애플 연결 해제 (토큰 회수) */ - public void revokeAppleToken(String authorizationCode) throws Exception { + public void revokeAppleToken(HttpServletRequest request) throws Exception { String clientSecret = createClientSecret(); - // apple refreshToken 발급 - String refreshToken = requestAppleToken(authorizationCode).getRefreshToken(); + String userEmail = jwtProvider.getUserEmail(tokenService.resolveToken(request)); - // Apple 토큰 - try { - HttpEntity> appleRequestEntity = createAppleRequestEntity(authorizationCode, clientSecret); + Optional refreshToken = redisService.getToken(userEmail, Provider.APPLE, TokenType.REFRESH); - // token 헤더 추가 - appleRequestEntity.getHeaders().add("token", refreshToken); + if (refreshToken.isEmpty()) { + throw new AuthException(AuthErrorCode.INVALID_TOKEN); + } - // user 삭제 - commonAuthService.deleteUser(); + // Apple 토큰 + try { + HttpEntity> appleRequestEntity = + createAppleRequestEntity(Optional.empty(), clientSecret, refreshToken); // revoke 요청 restTemplate.postForEntity( @@ -186,6 +197,11 @@ public void revokeAppleToken(String authorizationCode) throws Exception { String.class ); + // user 삭제 + commonAuthService.deleteUser(); + + log.info("apple 요청 성공"); + } catch (HttpClientErrorException e) { log.error("Apple revoke API 호출 실패: 상태 코드 {}, 응답 본문 {}", e.getStatusCode(), e.getResponseBodyAsString()); throw new AuthException(AuthErrorCode.APPLE_AUTH_FAILED); @@ -195,17 +211,23 @@ public void revokeAppleToken(String authorizationCode) throws Exception { /** * 요청 파라미터 준비 (application/x-www-form-urlencoded) */ - private HttpEntity> createAppleRequestEntity(String authorizationCode, String clientSecret) { + private HttpEntity> createAppleRequestEntity(Optional authorizationCode, String clientSecret, Optional refreshToken) { MultiValueMap params = new LinkedMultiValueMap<>(); params.add("client_id", clientId); params.add("client_secret", clientSecret); - params.add("code", authorizationCode); params.add("grant_type", "authorization_code"); + authorizationCode.ifPresent(code -> params.add("code", code)); + + refreshToken.ifPresent(token -> { + params.add("token", token); + params.add("token_type_hint", "refresh_token"); + log.info(token); + }); + // 요청 헤더 설정 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - HttpEntity> requestEntity = new HttpEntity<>(params, headers); return requestEntity; } diff --git a/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java index cbe65da..a44b9dc 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java @@ -3,6 +3,8 @@ import haru.harudrawer.domain.auth.controller.dto.request.CommonRequestDTO; import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; import haru.harudrawer.domain.auth.controller.dto.response.LocalResponseDTO; +import haru.harudrawer.domain.auth.entity.Provider; +import haru.harudrawer.domain.auth.entity.TokenType; import haru.harudrawer.domain.auth.entity.User; import haru.harudrawer.domain.auth.exception.AuthErrorCode; import haru.harudrawer.domain.auth.exception.AuthException; @@ -49,7 +51,7 @@ public void logout(HttpServletRequest request) { throw new AuthException(AuthErrorCode.ALREADY_LOGOUT_USER); } - redisService.deleteRefreshToken(jwtProvider.getUserEmail(token)); + redisService.deleteRefreshToken(jwtProvider.getUserEmail(token), Provider.LOCAL, TokenType.SERVER); } @@ -148,7 +150,23 @@ public void deleteUser() { authRepository.delete(user); - redisService.deleteRefreshToken(user.getUserEmail()); + Provider provider = user.getProvider(); + String userEmail = user.getUserEmail(); + + // 소셜 타입인 경우 로컬, 소셜 타입의 Refresh Token 모두 삭제 + if (provider.equals(Provider.LOCAL)) { + redisService.deleteRefreshToken(userEmail, provider, TokenType.SERVER); + } + else if (provider.equals(Provider.APPLE)) { + redisService.deleteRefreshToken(userEmail, provider, TokenType.SERVER); + redisService.deleteRefreshToken(userEmail, provider, TokenType.REFRESH); + } + else if (provider.equals(Provider.KAKAO)) { + redisService.deleteRefreshToken(userEmail, provider, TokenType.SERVER); + redisService.deleteRefreshToken(userEmail, provider, TokenType.REFRESH); + } + + } diff --git a/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java index 7377730..3ec7ffd 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java @@ -2,6 +2,7 @@ import haru.harudrawer.domain.auth.controller.dto.request.SocialRequestDTO; import haru.harudrawer.domain.auth.controller.dto.response.SocialResponseDTO; +import haru.harudrawer.domain.auth.entity.TokenType; import haru.harudrawer.global.redis.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -73,9 +74,6 @@ public CommonResponseDTO.LoginResponseDTO login(String kakaoAccessToken) { CommonResponseDTO.TokenDTO tokens = tokenService.createTokens(user); - // redis에 refresh token 저장 - redisService.saveRefreshToken(user.getUserEmail(), tokens.getRefreshToken()); - return CommonResponseDTO.LoginResponseDTO.builder() .requiresSignup(false) .userEmail(null) @@ -86,9 +84,9 @@ public CommonResponseDTO.LoginResponseDTO login(String kakaoAccessToken) { /** * 카카오 회원 탈퇴 */ - public void delete(String kakaoAccessToken) { + public void delete() { // 카카오 연결 해제 - unlinkKakaoAccount(kakaoAccessToken); +// unlinkKakaoAccount(kakaoAccessToken); // 회원 삭제 commonAuthService.deleteUser(); diff --git a/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java index fee90d6..f1c3a27 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java @@ -94,7 +94,7 @@ public CommonResponseDTO.LoginResponseDTO login(LocalRequestDTO.LoginRequestDTO String refreshToken = jwtProvider.createRefreshToken(request.getUserEmail()); // redis에 refresh token 저장 - redisService.saveRefreshToken(request.getUserEmail(), refreshToken); + redisService.saveToken(request.getUserEmail(), refreshToken, userDetails.getProvider(), TokenType.SERVER); return CommonResponseDTO.LoginResponseDTO.builder() .tokens(CommonResponseDTO.TokenDTO.builder() diff --git a/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java b/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java index e4cdeb5..f265a16 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java @@ -4,6 +4,7 @@ import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; import haru.harudrawer.domain.auth.controller.dto.response.LocalResponseDTO; import haru.harudrawer.domain.auth.entity.Provider; +import haru.harudrawer.domain.auth.entity.TokenType; import haru.harudrawer.domain.auth.entity.User; import haru.harudrawer.domain.auth.exception.AuthErrorCode; import haru.harudrawer.domain.auth.exception.AuthException; @@ -41,7 +42,7 @@ public CommonResponseDTO.TokenDTO createTokens(User user) { String accessToken = jwtProvider.createAccessToken(userDetails); String refreshToken = jwtProvider.createRefreshToken(user.getUserEmail()); - redisService.saveRefreshToken(user.getUserEmail(), refreshToken); + redisService.saveToken(user.getUserEmail(), refreshToken, user.getProvider(), TokenType.SERVER); return CommonResponseDTO.TokenDTO.builder() .accessToken(accessToken) @@ -49,31 +50,34 @@ public CommonResponseDTO.TokenDTO createTokens(User user) { .build(); } - public void deleteRefreshToken(String refreshToken) { - redisService.deleteRefreshToken(refreshToken); + /** + * 서버 자체 Refresh Token 삭제 + */ + public void deleteRefreshToken(String refreshToken, User user) { + redisService.deleteRefreshToken(refreshToken, user.getProvider(), TokenType.SERVER); } /** - * refresh Token으로 Access Token 재발급 + * 서버 자체 refresh Token으로 Access Token 재발급 */ public CommonResponseDTO.LoginResponseDTO refreshToken(CommonRequestDTO.TokenRefreshDTO request) { // 사용자 이메일 조회 String userEmail = jwtProvider.getUserEmail(request.getRefreshToken()); + // 이메일로 사용자 정보 DB 조회 + User user = authRepository.findByUserEmail(userEmail).orElseThrow( + () -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); + // redis에서 refresh token 조회 - Optional findTokenOpt = redisService.getRefreshToken(userEmail); + Optional findTokenOpt = redisService.getToken(userEmail, user.getProvider(), TokenType.SERVER); // refresh token 검증 if (findTokenOpt.isEmpty() || !findTokenOpt.get().equals(request.getRefreshToken())) { throw new AuthException(AuthErrorCode.INVALID_TOKEN); } - // 이메일로 사용자 정보 DB 조회 - User user = authRepository.findByUserEmail(userEmail).orElseThrow( - () -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); - // redis에서 만료된 RefreshToken 삭제 - deleteRefreshToken(request.getRefreshToken()); + deleteRefreshToken(request.getRefreshToken(), user); // 새 토큰 생성 CommonResponseDTO.TokenDTO tokens = createTokens(user); diff --git a/src/main/java/haru/harudrawer/global/redis/RedisService.java b/src/main/java/haru/harudrawer/global/redis/RedisService.java index b4adc24..01b877a 100644 --- a/src/main/java/haru/harudrawer/global/redis/RedisService.java +++ b/src/main/java/haru/harudrawer/global/redis/RedisService.java @@ -1,5 +1,7 @@ package haru.harudrawer.global.redis; +import haru.harudrawer.domain.auth.entity.Provider; +import haru.harudrawer.domain.auth.entity.TokenType; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; @@ -22,18 +24,20 @@ public class RedisService { /** * Refresh Token 저장 (기본 만료 시간 적용) */ - public void saveRefreshToken(String userEmail, String refreshToken) { - String key = "refresh:" + userEmail; // Redis Key + public void saveToken(String userEmail, String token, Provider provider, TokenType tokenType) { + String key = (tokenType == TokenType.SERVER ? "SERVER_REFRESH:" : provider.name() + "_" + tokenType.name() + ":") + userEmail; + ValueOperations values = redisTemplate.opsForValue(); - values.set(key, refreshToken, refreshTokenValidity, TimeUnit.SECONDS); + values.set(key, token, refreshTokenValidity, TimeUnit.SECONDS); } /** - * Refresh Token 조회 (Optional 반환) + * Refresh Token 조회 (Optional 반환) */ @Transactional(readOnly = true) - public Optional getRefreshToken(String userEmail) { - String key = "refresh:" + userEmail; + public Optional getToken(String userEmail, Provider provider, TokenType tokenType) { + String key = (tokenType == TokenType.SERVER ? "SERVER_REFRESH:" : provider.name() + "_" + tokenType.name() + ":") + userEmail; + ValueOperations values = redisTemplate.opsForValue(); String token = (String) values.get(key); return Optional.ofNullable(token); @@ -42,8 +46,8 @@ public Optional getRefreshToken(String userEmail) { /** * Refresh Token 삭제 (로그아웃 시) */ - public void deleteRefreshToken(String userEmail) { - String key = "refresh:" + userEmail; + public void deleteRefreshToken(String userEmail, Provider provider, TokenType tokenType) { + String key = (tokenType == TokenType.SERVER ? "SERVER_REFRESH:" : provider.name() + "_" + tokenType.name() + ":") + userEmail; redisTemplate.delete(key); } diff --git a/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java b/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java index 7c2ac67..8edd6e9 100644 --- a/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java +++ b/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java @@ -94,6 +94,8 @@ public Long extractUserId() { // 토큰에서 클레임 파싱 private Claims parseClaims(String token) { + validateToken(token); + return Jwts.parser() .verifyWith(extractSecretKey()) .build() diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4f6daa5..25a39b2 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -12,7 +12,7 @@ spring: jpa: show-sql: true hibernate: - ddl-auto: create + ddl-auto: update properties: hibernate: dialect: org.hibernate.dialect.MySQLDialect From 4cc260af2a8220e049e68021385f4ba984958d10 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Tue, 11 Mar 2025 17:58:04 +0900 Subject: [PATCH 03/64] =?UTF-8?q?Refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/controller/CommonAuthController.java | 4 ++-- .../domain/auth/controller/KakaoAuthController.java | 2 +- .../auth/controller/dto/request/CommonRequestDTO.java | 3 --- .../harudrawer/domain/auth/service/TokenService.java | 10 +++++----- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/CommonAuthController.java b/src/main/java/haru/harudrawer/domain/auth/controller/CommonAuthController.java index b16adc5..e7dd307 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/CommonAuthController.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/CommonAuthController.java @@ -73,8 +73,8 @@ public ResponseEntity> getUserInfo @PostMapping("/reissue") @Operation(summary = "Access Token 재발급", description = "Access Token 재발급을 처리합니다. \n 응답 코드에 따른 자세한 결과는 PostMan API 명세서를 참고 부탁드립니다.") - public ResponseEntity> refreshAccessToken(@RequestBody CommonRequestDTO.TokenRefreshDTO request) { - CommonResponseDTO.LoginResponseDTO token = tokenService.refreshToken(request); + public ResponseEntity> refreshAccessToken(@RequestParam String refreshToken ) { + CommonResponseDTO.LoginResponseDTO token = tokenService.refreshToken(refreshToken); return ResponseEntity.ok(ApiResponse.of(token)); diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java b/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java index 409059f..833a8d9 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java @@ -45,7 +45,7 @@ public ResponseEntity> signup(@RequestBody SocialReque } @DeleteMapping("/kakao") - @Operation(summary = "카카오 회원 탈퇴", description = "카카오 소셜 회원 탈퇴(연결 해제)를 처리합니다. kakaoAccessToken을 제공해야 합니다.") + @Operation(summary = "카카오 회원 탈퇴", description = "카카오 소셜 회원 탈퇴(연결 해제)를 처리합니다.") public ResponseEntity> delete() { kakaoAuthService.delete(); diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/CommonRequestDTO.java b/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/CommonRequestDTO.java index 1460868..f7aef28 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/CommonRequestDTO.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/CommonRequestDTO.java @@ -45,9 +45,6 @@ public static class UpdatePasswordDTO { @Builder public static class TokenRefreshDTO { - @NotBlank(message = "refreshToken은 필수 입력 항목입니다.") - private String accessToken; - @NotBlank(message = "refreshToken은 필수 입력 항목입니다.") private String refreshToken; } diff --git a/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java b/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java index f265a16..53ec8d6 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java @@ -60,9 +60,9 @@ public void deleteRefreshToken(String refreshToken, User user) { /** * 서버 자체 refresh Token으로 Access Token 재발급 */ - public CommonResponseDTO.LoginResponseDTO refreshToken(CommonRequestDTO.TokenRefreshDTO request) { + public CommonResponseDTO.LoginResponseDTO refreshToken(String refreshToken) { // 사용자 이메일 조회 - String userEmail = jwtProvider.getUserEmail(request.getRefreshToken()); + String userEmail = jwtProvider.getUserEmail(refreshToken); // 이메일로 사용자 정보 DB 조회 User user = authRepository.findByUserEmail(userEmail).orElseThrow( @@ -72,12 +72,12 @@ public CommonResponseDTO.LoginResponseDTO refreshToken(CommonRequestDTO.TokenRef Optional findTokenOpt = redisService.getToken(userEmail, user.getProvider(), TokenType.SERVER); // refresh token 검증 - if (findTokenOpt.isEmpty() || !findTokenOpt.get().equals(request.getRefreshToken())) { + if (findTokenOpt.isEmpty() || !findTokenOpt.get().equals(refreshToken)) { throw new AuthException(AuthErrorCode.INVALID_TOKEN); } - // redis에서 만료된 RefreshToken 삭제 - deleteRefreshToken(request.getRefreshToken(), user); + // redis에서 RefreshToken 삭제 + deleteRefreshToken(refreshToken, user); // 새 토큰 생성 CommonResponseDTO.TokenDTO tokens = createTokens(user); From ddb142451e84fc5456c37519cbb99ea8e95e77b3 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Tue, 11 Mar 2025 20:17:55 +0900 Subject: [PATCH 04/64] =?UTF-8?q?Fix:=20jwt=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EC=B2=98=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/jwt/JwtAuthenticationFilter.java | 75 ++++++++++++++----- .../global/security/jwt/JwtProvider.java | 1 + 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/main/java/haru/harudrawer/global/security/jwt/JwtAuthenticationFilter.java b/src/main/java/haru/harudrawer/global/security/jwt/JwtAuthenticationFilter.java index d70785f..40761da 100644 --- a/src/main/java/haru/harudrawer/global/security/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/haru/harudrawer/global/security/jwt/JwtAuthenticationFilter.java @@ -1,11 +1,16 @@ package haru.harudrawer.global.security.jwt; +import com.fasterxml.jackson.databind.ObjectMapper; +import haru.harudrawer.domain.auth.exception.AuthException; +import haru.harudrawer.global.exception.BaseErrorCode; +import haru.harudrawer.global.response.ErrorResponse; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; @@ -23,37 +28,47 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtProvider jwtProvider; private final CustomUserDetailsService userDetailsService; + private final ObjectMapper objectMapper = new ObjectMapper(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - // 1. HTTP 헤더에서 "Authorization" 추출 - String token = resolveToken(request); + try { + // HTTP 헤더에서 "Authorization" 추출 + String token = resolveToken(request); - // 2. 토큰이 존재하고, 유효한지 검사 - if (token != null && jwtProvider.validateToken(token)) { - // 3. 토큰에서 username 추출 - String userEmail = jwtProvider.getUserEmail(token); + // 토큰이 존재하고, 유효한지 검사 + if (token != null && jwtProvider.validateToken(token)) { + // 토큰에서 username 추출 + String userEmail = jwtProvider.getUserEmail(token); - // 4. DB에서 유저 정보 가져오기 (UserDetailsService) - UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail); + // DB에서 유저 정보 가져오기 (UserDetailsService) + UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail); - // 5. 인증 객체(UsernamePasswordAuthenticationToken) 생성 - UsernamePasswordAuthenticationToken auth = - new UsernamePasswordAuthenticationToken( - userDetails, - null, - userDetails.getAuthorities() - ); + // 인증 객체(UsernamePasswordAuthenticationToken) 생성 + UsernamePasswordAuthenticationToken auth = + new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); - // 6. SecurityContext에 저장 - SecurityContextHolder.getContext().setAuthentication(auth); + // SecurityContext에 저장 + SecurityContextHolder.getContext().setAuthentication(auth); + } + // 다음 필터로 진행 + filterChain.doFilter(request, response); + } catch (AuthException e) { + // 필터 내에서 AuthException 발생 시 여기서 커스텀 응답 + handleAuthException(response, e); + } catch (Exception e) { + // 기타 예외는 500 처리 + handleOtherException(response, e); } - // 다음 필터로 진행 - filterChain.doFilter(request, response); + } // Authorization 헤더에서 실제 JWT 토큰 문자열만 추출 @@ -64,4 +79,26 @@ private String resolveToken(HttpServletRequest request) { } return null; } + + private void handleAuthException(HttpServletResponse response, AuthException e) throws IOException { + BaseErrorCode errorCode = e.getErrorCode(); + + // 여기서 HttpStatus와 메시지를 가져옴 + HttpStatus status = errorCode.getHttpStatus(); + String message = errorCode.getMessage(); + + ErrorResponse errorResponse = ErrorResponse.of(errorCode.getCode(), message); + + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(status.value()); + response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); + } + + private void handleOtherException(HttpServletResponse response, Exception e) throws IOException { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + + ErrorResponse errorResponse = ErrorResponse.of("500", e.getMessage()); + response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); + } } diff --git a/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java b/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java index 8edd6e9..cdc79b7 100644 --- a/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java +++ b/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java @@ -1,5 +1,6 @@ package haru.harudrawer.global.security.jwt; +import haru.harudrawer.global.exception.CustomException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.JwtException; From 1f89a16b214d45bd7ecabf6e8b103e7b911cc6b1 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 16 Mar 2025 13:41:29 +0900 Subject: [PATCH 05/64] =?UTF-8?q?Fix:=20security=20admin=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SecurityConfig.java | 38 +++++++++++++------ .../security/jwt/JwtAuthenticationFilter.java | 2 +- .../global/security/jwt/JwtProvider.java | 3 -- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/main/java/haru/harudrawer/global/config/SecurityConfig.java b/src/main/java/haru/harudrawer/global/config/SecurityConfig.java index 58405fe..dc89644 100644 --- a/src/main/java/haru/harudrawer/global/config/SecurityConfig.java +++ b/src/main/java/haru/harudrawer/global/config/SecurityConfig.java @@ -34,22 +34,15 @@ public class SecurityConfig { private final JwtProvider jwtProvider; private final CustomUserDetailsService userDetailsService; - /** - * permitAll 권한을 가진 엔드포인트에 적용되는 Security FilterChain - * @param http - * @return - * @throws Exception - */ + @Bean - @Order(1) - public SecurityFilterChain securityFilterChainPermitAll(HttpSecurity http) throws Exception { + @Order(3) + public SecurityFilterChain securityFilterChainAdmin(HttpSecurity http) throws Exception { configureCommonSecuritySettings(http); - http.securityMatchers(matchers -> matchers.requestMatchers(requestPermitAll())) + http.securityMatchers(matchers -> matchers.requestMatchers("/**")) .authorizeHttpRequests(auth -> auth - .anyRequest() - .permitAll()); - + .anyRequest().hasRole("ADMIN")); return http.build(); } @@ -74,6 +67,27 @@ public SecurityFilterChain securityFilterChainAuthorized(HttpSecurity http) thro return http.build(); } + + /** + * permitAll 권한을 가진 엔드포인트에 적용되는 Security FilterChain + * + * @param http + * @return + * @throws Exception + */ + @Bean + @Order(1) + public SecurityFilterChain securityFilterChainPermitAll(HttpSecurity http) throws Exception { + configureCommonSecuritySettings(http); + + http.securityMatchers(matchers -> matchers.requestMatchers(requestPermitAll())) + .authorizeHttpRequests(auth -> auth + .anyRequest() + .permitAll()); + + return http.build(); + } + // 인증 및 인가가 필요한 엔드포인트에 적용되는 RequestMatcher private RequestMatcher[] requestHasRoleUser() { List requestMatchers = List.of( diff --git a/src/main/java/haru/harudrawer/global/security/jwt/JwtAuthenticationFilter.java b/src/main/java/haru/harudrawer/global/security/jwt/JwtAuthenticationFilter.java index 40761da..3d75738 100644 --- a/src/main/java/haru/harudrawer/global/security/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/haru/harudrawer/global/security/jwt/JwtAuthenticationFilter.java @@ -68,7 +68,6 @@ protected void doFilterInternal(HttpServletRequest request, // 기타 예외는 500 처리 handleOtherException(response, e); } - } // Authorization 헤더에서 실제 JWT 토큰 문자열만 추출 @@ -102,3 +101,4 @@ private void handleOtherException(HttpServletResponse response, Exception e) thr response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); } } + diff --git a/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java b/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java index cdc79b7..e856d55 100644 --- a/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java +++ b/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java @@ -1,6 +1,5 @@ package haru.harudrawer.global.security.jwt; -import haru.harudrawer.global.exception.CustomException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.JwtException; @@ -18,8 +17,6 @@ import javax.crypto.SecretKey; import java.time.Instant; import java.util.Date; -import java.util.HashSet; -import java.util.Set; @Component @Slf4j From 434199b65c6b8dd15b49419b1632284a7897b7b0 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 16 Mar 2025 13:53:00 +0900 Subject: [PATCH 06/64] =?UTF-8?q?Fix:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=88=98=EC=A0=95=20DTO=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/CommonRequestDTO.java | 3 -- .../auth/service/CommonAuthService.java | 39 +++++++++---------- .../global/config/SecurityConfig.java | 8 +++- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/CommonRequestDTO.java b/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/CommonRequestDTO.java index f7aef28..ff23b74 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/CommonRequestDTO.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/CommonRequestDTO.java @@ -30,9 +30,6 @@ public static class UpdateNickNameDTO { @Builder public static class UpdatePasswordDTO { - @NotBlank(message = "기존 비밀번호는 필수 입력 항목입니다.") - private String currentPassword; - @NotBlank(message = "변경 비밀번호는 필수 입력 항목입니다.") private String newPassword; diff --git a/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java index a44b9dc..ecb7370 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java @@ -118,28 +118,25 @@ public void updateUserPassword(CommonRequestDTO.UpdatePasswordDTO request) { User user = authRepository.findByUserId(jwtProvider.extractUserId()) .orElseThrow(() -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); - //비밀번호 일치할 경우 - if (passwordEncoder.matches(request.getCurrentPassword(), user.getPassword())) { - - // 변경 비밀번호, 변경 비밀번호 확인 서로 다를경우 - if (!request.getNewPassword().equals(request.getNewPasswordCheck())) { - throw new AuthException(AuthErrorCode.PASSWORD_MISMATCH); - } - - // 비밀번호 서식 틀렸을 경우 예외처리 - if (!request.getNewPassword().matches("^(?=.*[A-Z])(?=.*[@$!%*?&]).{8,16}$") - || !request.getNewPasswordCheck().matches("^(?=.*[A-Z])(?=.*[@$!%*?&]).{8,16}$")) { - throw new AuthException(AuthErrorCode.INVALID_PASSWORD); - } - - // 변경 전 비밀번호와 같을 경우 예외 - if (passwordEncoder.matches(request.getNewPassword(), user.getPassword())) { - throw new AuthException(AuthErrorCode.DUPLICATE_PASSWORD); - } - - // 비밀번호 업데이트 - user.updatePassword(passwordEncoder.encode(request.getNewPassword())); + // 변경 비밀번호, 변경 비밀번호 확인 서로 다를경우 + if (!request.getNewPassword().equals(request.getNewPasswordCheck())) { + throw new AuthException(AuthErrorCode.PASSWORD_MISMATCH); } + + // 비밀번호 서식 틀렸을 경우 예외처리 + if (!request.getNewPassword().matches("^(?=.*[A-Z])(?=.*[@$!%*?&]).{8,16}$") + || !request.getNewPasswordCheck().matches("^(?=.*[A-Z])(?=.*[@$!%*?&]).{8,16}$")) { + throw new AuthException(AuthErrorCode.INVALID_PASSWORD); + } + + // 변경 전 비밀번호와 같을 경우 예외 + if (passwordEncoder.matches(request.getNewPassword(), user.getPassword())) { + throw new AuthException(AuthErrorCode.DUPLICATE_PASSWORD); + } + + // 비밀번호 업데이트 + user.updatePassword(passwordEncoder.encode(request.getNewPassword())); + } public void deleteUser() { diff --git a/src/main/java/haru/harudrawer/global/config/SecurityConfig.java b/src/main/java/haru/harudrawer/global/config/SecurityConfig.java index dc89644..68ba17b 100644 --- a/src/main/java/haru/harudrawer/global/config/SecurityConfig.java +++ b/src/main/java/haru/harudrawer/global/config/SecurityConfig.java @@ -98,7 +98,8 @@ private RequestMatcher[] requestHasRoleUser() { antMatcher(HttpMethod.DELETE, "/api/v1/auth"), antMatcher("/api/v1/auth/kakao"), antMatcher("/api/v1/auth/apple"), - antMatcher("/api/v1/auth/local") + antMatcher("/api/v1/auth/local"), + antMatcher("api/v1/auth/check-email/recovery") ); @@ -113,7 +114,10 @@ private RequestMatcher[] requestPermitAll() { antMatcher("/v3/api-docs/**"), antMatcher("/api/v1/auth/login/**"), antMatcher("/api/v1/auth/signup/**"), - antMatcher("/api/v1/auth/reissue") + antMatcher("/api/v1/auth/reissue"), + antMatcher("/api/v1/auth/send-verification"), + antMatcher("/api/v1/auth/verification-code"), + antMatcher("api/v1/auth/check-email/signup") ); return requestMatchers.toArray(RequestMatcher[]::new); From 048e58f13a7f01fff4728102c9de1ecd740aa799 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Tue, 18 Mar 2025 15:26:47 +0900 Subject: [PATCH 07/64] =?UTF-8?q?Fix:=20Security=20config=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SecurityConfig.java | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/main/java/haru/harudrawer/global/config/SecurityConfig.java b/src/main/java/haru/harudrawer/global/config/SecurityConfig.java index 99fb487..05831e3 100644 --- a/src/main/java/haru/harudrawer/global/config/SecurityConfig.java +++ b/src/main/java/haru/harudrawer/global/config/SecurityConfig.java @@ -34,18 +34,6 @@ public class SecurityConfig { private final JwtProvider jwtProvider; private final CustomUserDetailsService userDetailsService; - - @Bean - @Order(3) - public SecurityFilterChain securityFilterChainAdmin(HttpSecurity http) throws Exception { - configureCommonSecuritySettings(http); - - http.securityMatchers(matchers -> matchers.requestMatchers("/api/v1/**")) - .authorizeHttpRequests(auth -> auth - .anyRequest().hasRole("ADMIN")); - return http.build(); - } - @Bean @Order(2) public SecurityFilterChain securityFilterChainAuthorized(HttpSecurity http) throws Exception { @@ -99,8 +87,7 @@ private RequestMatcher[] requestHasRoleUser() { antMatcher("/api/v1/auth/kakao"), antMatcher("/api/v1/auth/apple"), antMatcher("/api/v1/auth/local"), - antMatcher("api/v1/auth/check-email/recovery") - + antMatcher("/api/v1/auth/logout") ); return requestMatchers.toArray(RequestMatcher[]::new); @@ -117,7 +104,10 @@ private RequestMatcher[] requestPermitAll() { antMatcher("/api/v1/auth/reissue"), antMatcher("/api/v1/auth/send-verification"), antMatcher("/api/v1/auth/verification-code"), - antMatcher("api/v1/auth/check-email/signup") + antMatcher("/api/v1/auth/check-email/recovery"), + antMatcher("/api/v1/auth/check-email/signup"), + antMatcher("/api/v1/auth/local/reset-password"), + antMatcher("/api/v1/auth/local/find-email") ); return requestMatchers.toArray(RequestMatcher[]::new); From 01baf672f8af2c9c7a41f4a89bc7fecadbc00c24 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Wed, 19 Mar 2025 17:21:53 +0900 Subject: [PATCH 08/64] =?UTF-8?q?Refactor:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=EC=A0=84=ED=99=94=EB=B2=88=ED=98=B8=20=EC=95=94=ED=98=B8?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../harudrawer/domain/auth/entity/User.java | 10 +++++----- .../auth/repository/AuthRepository.java | 2 +- .../auth/service/CommonAuthService.java | 1 - .../domain/auth/service/LocalAuthService.java | 10 +++++++--- .../security/service/EncryptService.java | 20 +++++++++++++++++++ 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/main/java/haru/harudrawer/domain/auth/entity/User.java b/src/main/java/haru/harudrawer/domain/auth/entity/User.java index 9678716..0684fc2 100644 --- a/src/main/java/haru/harudrawer/domain/auth/entity/User.java +++ b/src/main/java/haru/harudrawer/domain/auth/entity/User.java @@ -31,12 +31,13 @@ public class User extends BaseEntity { @Column(name = "nick_name", nullable = false, length = 20) private String nickName; - @Column(name = "user_name", length = 10) - private String userName; - - @Column(name = "phone_number", length = 25) + @Lob + @Column(name = "phone_number", length = 25, columnDefinition = "TEXT") private String phoneNumber; + @Column(name = "phone_hash", nullable = false, unique = true) + private String phoneHash; + @Column(name = "password", length = 100) private String password; @@ -44,7 +45,6 @@ public class User extends BaseEntity { @Enumerated(EnumType.STRING) private Provider provider; - @Column(name = "role") @Enumerated(EnumType.STRING) private Role role; diff --git a/src/main/java/haru/harudrawer/domain/auth/repository/AuthRepository.java b/src/main/java/haru/harudrawer/domain/auth/repository/AuthRepository.java index b404688..2fb352a 100644 --- a/src/main/java/haru/harudrawer/domain/auth/repository/AuthRepository.java +++ b/src/main/java/haru/harudrawer/domain/auth/repository/AuthRepository.java @@ -14,7 +14,7 @@ public interface AuthRepository extends JpaRepository { Optional findByUserId(Long userId); - Optional findByPhoneNumberAndNickName(String phoneNumber, String nickName); + Optional findByPhoneHashAndNickName(String phoneHash, String nickName); Boolean existsByUserEmail(String userEmail); } diff --git a/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java index 8c1fa17..3c89b1a 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java @@ -72,7 +72,6 @@ public CommonResponseDTO.GetUserInfoDTO getUserInfo(HttpServletRequest request) return CommonResponseDTO.GetUserInfoDTO.builder() .nickName(findUser.getNickName()) .userEmail(findUser.getUserEmail()) - .userName(findUser.getUserName()) .phoneNumber(findUser.getPhoneNumber()) .provider(findUser.getProvider()) .build(); diff --git a/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java index f1c3a27..a1fb76e 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java @@ -1,6 +1,7 @@ package haru.harudrawer.domain.auth.service; import haru.harudrawer.global.redis.RedisService; +import haru.harudrawer.global.security.service.EncryptService; import jakarta.mail.MessagingException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -41,6 +42,7 @@ public class LocalAuthService { private final EmailRepository emailRepository; private final EmailService emailService; private final RedisService redisService; + private final EncryptService encryptService; /** * 로컬 회원 가입 @@ -58,12 +60,12 @@ public void signUp(LocalRequestDTO.SignUpRequestDTO request) { authRepository.save(User.builder() .userEmail(request.getUserEmail()) .password(passwordEncoder.encode(request.getPassword())) - .phoneNumber(request.getPhoneNumber()) + .phoneNumber(encryptService.encrypt(request.getPhoneNumber())) + .phoneHash(encryptService.sha256(request.getPhoneNumber())) .nickName(request.getNickName()) .role(Role.USER) .provider(Provider.LOCAL) .build()); - } /** @@ -121,7 +123,9 @@ public CommonResponseDTO.LoginResponseDTO login(LocalRequestDTO.LoginRequestDTO * 아이디 찾기 */ public String findUserEmail(LocalRequestDTO.FindEmailDTO request) { - User user = authRepository.findByPhoneNumberAndNickName(request.getPhoneNumber(), request.getNickName()) + String hash = encryptService.sha256(request.getPhoneNumber()); + + User user = authRepository.findByPhoneHashAndNickName(hash, request.getNickName()) .orElseThrow(() -> new AuthException(AuthErrorCode.USER_NOT_FOUND) ); diff --git a/src/main/java/haru/harudrawer/global/security/service/EncryptService.java b/src/main/java/haru/harudrawer/global/security/service/EncryptService.java index 030f00e..3bb096f 100644 --- a/src/main/java/haru/harudrawer/global/security/service/EncryptService.java +++ b/src/main/java/haru/harudrawer/global/security/service/EncryptService.java @@ -12,6 +12,8 @@ import javax.crypto.spec.SecretKeySpec; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Base64; @@ -124,4 +126,22 @@ public String decrypt(String cipherText) { throw new CustomException(CommonErrorCode.INTERNAL_SERVER_ERROR); } } + + public String sha256(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + + // 결과를 헥스 문자열로 변환 + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if(hex.length() == 1) hexString.append('0'); + hexString.append(hex); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 algorithm not available", e); + } + } } From 702fa196e8036376b3135f396c6c9ddf1e4e68ab Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Thu, 20 Mar 2025 15:55:08 +0900 Subject: [PATCH 09/64] =?UTF-8?q?Fix:=20User=20=EC=97=94=ED=8B=B0=ED=8B=B0?= =?UTF-8?q?=20PhoneHash=20=EC=86=8D=EC=84=B1=20=EC=98=B5=EC=85=98=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/haru/harudrawer/domain/auth/entity/User.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/haru/harudrawer/domain/auth/entity/User.java b/src/main/java/haru/harudrawer/domain/auth/entity/User.java index 0684fc2..a7a3bc9 100644 --- a/src/main/java/haru/harudrawer/domain/auth/entity/User.java +++ b/src/main/java/haru/harudrawer/domain/auth/entity/User.java @@ -35,7 +35,7 @@ public class User extends BaseEntity { @Column(name = "phone_number", length = 25, columnDefinition = "TEXT") private String phoneNumber; - @Column(name = "phone_hash", nullable = false, unique = true) + @Column(name = "phone_hash", unique = true) private String phoneHash; @Column(name = "password", length = 100) From 93e413afa5926905dfea7d8ef0a10fb19b3c5163 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Thu, 20 Mar 2025 22:14:58 +0900 Subject: [PATCH 10/64] =?UTF-8?q?Fix:=20=EC=A0=84=ED=99=94=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B0=98=ED=99=98=EA=B0=92=20=EB=B3=B5=ED=98=B8?= =?UTF-8?q?=ED=99=94=20=EC=A7=84=ED=96=89=20=ED=9B=84=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/CommonAuthService.java | 13 +++---------- .../global/config/GeometryFactoryConfig.java | 18 ------------------ 2 files changed, 3 insertions(+), 28 deletions(-) delete mode 100644 src/main/java/haru/harudrawer/global/config/GeometryFactoryConfig.java diff --git a/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java index 3c89b1a..d007315 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java @@ -2,30 +2,23 @@ import haru.harudrawer.domain.auth.controller.dto.request.CommonRequestDTO; import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; -import haru.harudrawer.domain.auth.controller.dto.response.LocalResponseDTO; import haru.harudrawer.domain.auth.entity.Provider; import haru.harudrawer.domain.auth.entity.TokenType; import haru.harudrawer.domain.auth.entity.User; import haru.harudrawer.domain.auth.exception.AuthErrorCode; import haru.harudrawer.domain.auth.exception.AuthException; import haru.harudrawer.domain.auth.repository.AuthRepository; -import haru.harudrawer.domain.diary.repository.DiaryRepository; import haru.harudrawer.global.redis.RedisService; import haru.harudrawer.global.s3.S3Service; -import haru.harudrawer.global.security.domain.CustomUserDetails; import haru.harudrawer.global.security.jwt.JwtProvider; +import haru.harudrawer.global.security.service.EncryptService; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - @Service @RequiredArgsConstructor @Transactional @@ -38,7 +31,7 @@ public class CommonAuthService { private final RedisService redisService; private final TokenService tokenService; private final S3Service s3Service; - + private final EncryptService encryptService; /** * 사용자 로그 아웃 @@ -72,7 +65,7 @@ public CommonResponseDTO.GetUserInfoDTO getUserInfo(HttpServletRequest request) return CommonResponseDTO.GetUserInfoDTO.builder() .nickName(findUser.getNickName()) .userEmail(findUser.getUserEmail()) - .phoneNumber(findUser.getPhoneNumber()) + .phoneNumber(encryptService.decrypt(findUser.getPhoneNumber())) .provider(findUser.getProvider()) .build(); } diff --git a/src/main/java/haru/harudrawer/global/config/GeometryFactoryConfig.java b/src/main/java/haru/harudrawer/global/config/GeometryFactoryConfig.java deleted file mode 100644 index aab838f..0000000 --- a/src/main/java/haru/harudrawer/global/config/GeometryFactoryConfig.java +++ /dev/null @@ -1,18 +0,0 @@ -package haru.harudrawer.global.config; - -import org.locationtech.jts.geom.GeometryFactory; -import org.locationtech.jts.geom.PrecisionModel; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class GeometryFactoryConfig { - - /** - * SRID를 인스턴스 생성시마다 초기화 해주어야 하기에 Config 파일로 관리 - */ - @Bean - public GeometryFactory GeometryFactoryConfig() { - return new GeometryFactory(new PrecisionModel(), 4326); - } -} From f223ae2c2f8fdddd93374c9b9a7bb222132e8229 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Fri, 21 Mar 2025 13:03:09 +0900 Subject: [PATCH 11/64] =?UTF-8?q?Fix:=20user=20entity=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/haru/harudrawer/domain/auth/entity/User.java | 6 +++--- src/main/resources/application.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/haru/harudrawer/domain/auth/entity/User.java b/src/main/java/haru/harudrawer/domain/auth/entity/User.java index a7a3bc9..4d5117e 100644 --- a/src/main/java/haru/harudrawer/domain/auth/entity/User.java +++ b/src/main/java/haru/harudrawer/domain/auth/entity/User.java @@ -31,11 +31,11 @@ public class User extends BaseEntity { @Column(name = "nick_name", nullable = false, length = 20) private String nickName; - @Lob - @Column(name = "phone_number", length = 25, columnDefinition = "TEXT") + @Column(name = "phone_number", length = 25) private String phoneNumber; - @Column(name = "phone_hash", unique = true) + @Lob + @Column(name = "phone_hash", unique = true, columnDefinition = "TEXT") private String phoneHash; @Column(name = "password", length = 100) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d674279..ee10319 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -12,7 +12,7 @@ spring: jpa: show-sql: true hibernate: - ddl-auto: update + ddl-auto: create properties: hibernate: dialect: org.hibernate.dialect.MySQLDialect From e10bb87dfeb314ac7fbef04132e93de779419644 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Fri, 21 Mar 2025 13:09:15 +0900 Subject: [PATCH 12/64] =?UTF-8?q?Fix:=20user=20entity=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/haru/harudrawer/domain/auth/entity/User.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/haru/harudrawer/domain/auth/entity/User.java b/src/main/java/haru/harudrawer/domain/auth/entity/User.java index 4d5117e..b7fca91 100644 --- a/src/main/java/haru/harudrawer/domain/auth/entity/User.java +++ b/src/main/java/haru/harudrawer/domain/auth/entity/User.java @@ -31,11 +31,11 @@ public class User extends BaseEntity { @Column(name = "nick_name", nullable = false, length = 20) private String nickName; - @Column(name = "phone_number", length = 25) + @Lob + @Column(name = "phone_number", columnDefinition = "TEXT") private String phoneNumber; - @Lob - @Column(name = "phone_hash", unique = true, columnDefinition = "TEXT") + @Column(name = "phone_hash", unique = true, length = 100) private String phoneHash; @Column(name = "password", length = 100) From d6d4b9335b1b9cbf9d0153e46e4fe689f0ec1301 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Fri, 21 Mar 2025 13:11:30 +0900 Subject: [PATCH 13/64] =?UTF-8?q?Fix:=20application.yml=20=EC=98=B5?= =?UTF-8?q?=EC=85=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ee10319..d674279 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -12,7 +12,7 @@ spring: jpa: show-sql: true hibernate: - ddl-auto: create + ddl-auto: update properties: hibernate: dialect: org.hibernate.dialect.MySQLDialect From f34d46baf2cf41534319be5bf8648d459f53f3a8 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Mon, 24 Mar 2025 19:41:43 +0900 Subject: [PATCH 14/64] =?UTF-8?q?Fix:=20=EC=A0=84=ED=99=94=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B3=B5=ED=98=B8=ED=99=94=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../harudrawer/domain/auth/entity/User.java | 2 +- .../global/config/GeometryFactoryConfig.java | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 src/main/java/haru/harudrawer/global/config/GeometryFactoryConfig.java diff --git a/src/main/java/haru/harudrawer/domain/auth/entity/User.java b/src/main/java/haru/harudrawer/domain/auth/entity/User.java index b7fca91..1f427bf 100644 --- a/src/main/java/haru/harudrawer/domain/auth/entity/User.java +++ b/src/main/java/haru/harudrawer/domain/auth/entity/User.java @@ -35,7 +35,7 @@ public class User extends BaseEntity { @Column(name = "phone_number", columnDefinition = "TEXT") private String phoneNumber; - @Column(name = "phone_hash", unique = true, length = 100) + @Column(name = "phone_hash", length = 100) private String phoneHash; @Column(name = "password", length = 100) diff --git a/src/main/java/haru/harudrawer/global/config/GeometryFactoryConfig.java b/src/main/java/haru/harudrawer/global/config/GeometryFactoryConfig.java new file mode 100644 index 0000000..e322b52 --- /dev/null +++ b/src/main/java/haru/harudrawer/global/config/GeometryFactoryConfig.java @@ -0,0 +1,18 @@ +package haru.harudrawer.global.config; + +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.PrecisionModel; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class GeometryFactoryConfig { + + /** + * SRID를 인스턴스 생성시마다 초기화 해주어야 하기에 Config 파일로 관리 + */ + @Bean + public GeometryFactory GeometryFactoryConfig() { + return new GeometryFactory(new PrecisionModel(), 4326); + } +} \ No newline at end of file From cf85c196d21e04bfcd239a7bfde44ccac8e61707 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Tue, 25 Mar 2025 14:00:46 +0900 Subject: [PATCH 15/64] =?UTF-8?q?Fix:=20=ED=86=A0=ED=81=B0=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=20API=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/haru/harudrawer/domain/auth/service/TokenService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java b/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java index 53ec8d6..4cff3f9 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/TokenService.java @@ -82,6 +82,9 @@ public CommonResponseDTO.LoginResponseDTO refreshToken(String refreshToken) { // 새 토큰 생성 CommonResponseDTO.TokenDTO tokens = createTokens(user); + // 새로 발급받은 토큰 저장 + redisService.saveToken(userEmail,tokens.getRefreshToken(), user.getProvider(), TokenType.SERVER); + return CommonResponseDTO.LoginResponseDTO.builder() .tokens(tokens) .build(); From f6fa007b459f9cbddb3dca40ac5aa7b18fe8333e Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Wed, 26 Mar 2025 14:34:17 +0900 Subject: [PATCH 16/64] =?UTF-8?q?Refactor:=20=EB=A1=9C=EC=BB=AC=20Auth=20A?= =?UTF-8?q?PI=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 10 ++ src/main/resources/application-test.yml | 71 +++++++++ src/main/resources/application.yml | 2 +- .../auth/service/LocalAuthServiceTest.java | 146 ++++++++++++++++++ 4 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/application-test.yml create mode 100644 src/test/java/haru/harudrawer/domain/auth/service/LocalAuthServiceTest.java diff --git a/build.gradle b/build.gradle index 3bca684..aeb3e62 100644 --- a/build.gradle +++ b/build.gradle @@ -67,6 +67,16 @@ dependencies { //email implementation 'org.springframework.boot:spring-boot-starter-mail' + // JUnit 5 (Jupiter) + testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' + + // Mockito + testImplementation 'org.mockito:mockito-core:4.2.0' + testImplementation 'org.mockito:mockito-junit-jupiter:4.2.0' + + // H2 Database (테스트 환경용 인메모리 DB) + implementation 'com.h2database:h2' + compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 0000000..fd40ab3 --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,71 @@ +spring: + application: + name: harudrawer + web: + resources: + add-mappings: false + datasource: + url: jdbc:h2:tcp://localhost/~/jpashop;DB_CLOSE_DELAY=-1 + driver-class-name: org.h2.Driver + username: sa + password: + jpa: + show-sql: true + hibernate: + ddl-auto: create-drop + mail: + host: smtp.gmail.com + port: 587 + username: ${SMTP_ADDRESS} + password: ${SMTP_PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true + redis: + host: ${REDIS_HOST} + port: ${REDIS_PORT} + +jwt: + secret: ${JWT_SECRET} + access-token-expiration: ${JWT_ACCESS_EXPIRATION} + refresh-token-expiration: ${JWT_REFRESH_EXPIRATION} +kakao: + rest: + api: + key: ${KAKAO_REST_API_KEY} + url: ${KAKAO_STORE_API_URL} + redirect_url: ${KAKAO_REDIRECT_URL} + +aws: + s3: + bucket: ${S3_BUCKET} + region: + static: ${S3_REGION} + credentials: + access-key: ${S3_ACCESS_KEY} + secret-key: ${S3_SECRET_KEY} + +apple: + key-id: ${APPLE_KEY_ID} + client-id: ${APPLE_CLIENT_ID} + private-key-url: ${APPLE_PRIVATE_KEY_URL} + public-key-url: ${APPLE_PUBLIC_KEY_URL} + team-id: ${APPLE_TEAM_ID} + +logging: + level: + org: + springframework: DEBUG +app: + encryption: + key: ${ENCRYPTION_KEY} +springdoc: + default-consumes-media-type: application/json;charset=UTF-8 + default-produces-media-type: application/json;charset=UTF-8 + swagger-ui: + path: index.html # Swagger UI 경로 => localhost:8000/demo-ui.html + tags-sorter: alpha # alpha: 알파벳 순 태그 정렬, method: HTTP Method 순 정렬* + operations-sorter: alpha # alpha: 알파벳 순 태그 정렬, method: HTTP Method 순 정렬* \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d674279..de42c26 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: application: - name: what2eat + name: harudrawer web: resources: add-mappings: false diff --git a/src/test/java/haru/harudrawer/domain/auth/service/LocalAuthServiceTest.java b/src/test/java/haru/harudrawer/domain/auth/service/LocalAuthServiceTest.java new file mode 100644 index 0000000..d047826 --- /dev/null +++ b/src/test/java/haru/harudrawer/domain/auth/service/LocalAuthServiceTest.java @@ -0,0 +1,146 @@ +package haru.harudrawer.domain.auth.service; + +import haru.harudrawer.domain.auth.controller.dto.request.LocalRequestDTO; +import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; +import haru.harudrawer.domain.auth.entity.EmailVerificationCode; +import haru.harudrawer.domain.auth.entity.Provider; +import haru.harudrawer.domain.auth.entity.TokenType; +import haru.harudrawer.domain.auth.entity.User; +import haru.harudrawer.domain.auth.repository.AuthRepository; +import haru.harudrawer.domain.auth.repository.EmailRepository; +import haru.harudrawer.global.redis.RedisService; +import haru.harudrawer.global.security.domain.CustomUserDetails; +import haru.harudrawer.global.security.jwt.JwtProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +class LocalAuthServiceTest { + + @Mock + private AuthRepository authRepository; + + @Mock + private EmailRepository emailRepository; + + @Mock + private AuthenticationManager authenticationManager; + @Mock + private RedisService redisService; + + @Mock + private LocalAuthService localAuthService; + + @Mock + private JwtProvider jwtProvider; + + static String testEmail = "test@test.com"; + static String testPassword = "TestPassword!"; + static String testNickname = "Tester"; + static String testPhoneNumber = "01000000000"; + + @BeforeEach + public void setup() { + MockitoAnnotations.openMocks(this); + } + + @BeforeEach + public void setupEmailVerification() { + String testEmail = "test@test.com"; + + emailRepository.findByUserEmail(testEmail).ifPresent(emailRepository::delete); + + EmailVerificationCode evc = EmailVerificationCode.builder() + .userEmail(testEmail) + .verificationCode("123456") + .emailStatus(true) + .expiryDate(LocalDateTime.now().plusMinutes(10)) + .build(); + + emailRepository.save(evc); + } + + + @Test + @DisplayName("회원가입 테스트") + public void signupTest() { + //Given + LocalRequestDTO.SignUpRequestDTO tester = LocalRequestDTO.SignUpRequestDTO.builder() + .userEmail(testEmail) + .password(testPassword) + .nickName(testNickname) + .phoneNumber(testPhoneNumber) + .build(); + + //When + assertDoesNotThrow(() -> localAuthService.signUp(tester)); + + //Then + Optional createdUser = authRepository.findByUserEmail(testEmail); + assertTrue(createdUser.isPresent(), "회원가입 후 사용자가 DB에 저장되어야 합니다."); + + Optional evcAfter = emailRepository.findByUserEmail(testEmail); + assertTrue(evcAfter.isEmpty(), "회원가입 후 이메일 인증 정보는 삭제되어야 합니다."); + } + + @Test + @DisplayName("로그인 테스트") + public void loginTest() throws Exception { + //Given + + LocalRequestDTO.LoginRequestDTO loginRequestDTO = LocalRequestDTO.LoginRequestDTO.builder() + .userEmail(testEmail) + .password(testPassword).build(); + + CustomUserDetails userDetails = CustomUserDetails.builder() + .userId(1L) + .email(testEmail) + .password(testPassword) + .provider(Provider.LOCAL) + .nickName("손혁") + .authorities(Collections.emptyList()) + .build(); + + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class))) + .thenReturn(authentication); + + String accessToken = "dummyAccessToken"; + String refreshToken = "dummyRefreshToken"; + when(jwtProvider.createAccessToken(userDetails)).thenReturn(accessToken); + when(jwtProvider.createRefreshToken(testEmail)).thenReturn(refreshToken); + + //When + CommonResponseDTO.LoginResponseDTO response = localAuthService.login(loginRequestDTO); + + + //Then + assertNull(response); + assertNotNull(response.getTokens()); + + assertEquals(accessToken, response.getTokens().getAccessToken()); + assertEquals(refreshToken, response.getTokens().getRefreshToken()); + + verify(redisService).saveToken(testEmail, refreshToken, Provider.LOCAL, TokenType.SERVER); + } +} \ No newline at end of file From 24f2fffe251ea595bd287953856c4ff9c309e8b7 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 20:13:29 +0900 Subject: [PATCH 17/64] =?UTF-8?q?Refactor:=20=EB=A1=9C=EC=BB=AC=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../haru/harudrawer/global/s3/S3Service.java | 7 ++- src/main/resources/application-test.yml | 12 ++-- .../auth/service/LocalAuthServiceTest.java | 59 ++++++++++--------- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/main/java/haru/harudrawer/global/s3/S3Service.java b/src/main/java/haru/harudrawer/global/s3/S3Service.java index 9988cbb..d346d2b 100644 --- a/src/main/java/haru/harudrawer/global/s3/S3Service.java +++ b/src/main/java/haru/harudrawer/global/s3/S3Service.java @@ -52,7 +52,9 @@ public List uploadFiles(List files, String preFilePath) { s3Client.putObject( PutObjectRequest.builder() .bucket(bucket) // S3 버킷 이름 - .key(key) // S3 파일 경로(Key) + .key(key) + .contentType(file.getContentType()) + .contentDisposition("inline")// S3 파일 경로(Key) .build(), RequestBody.fromBytes(file.getBytes()) // 파일 데이터를 바이트 배열로 변환 ); @@ -119,7 +121,6 @@ public void deleteUserImgList(User user) { deleteFiles(imgList); log.info("탈퇴한 유저의 이미지 삭제 완료"); } - } /** @@ -137,6 +138,6 @@ private String getUploadFileUrl(String key) { } public String extractKey(String imgUrl) { - return imgUrl.replace(awsUrlPrefix, ""); + return imgUrl.replace(cloudFrontUrl, ""); } } diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index fd40ab3..5d4e4cc 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -5,14 +5,15 @@ spring: resources: add-mappings: false datasource: - url: jdbc:h2:tcp://localhost/~/jpashop;DB_CLOSE_DELAY=-1 - driver-class-name: org.h2.Driver - username: sa - password: + url: ${DB_URL} + driver-class-name: ${DB_DRIVER} + username: ${DB_USER} + password: ${DB_PASSWORD} jpa: show-sql: true hibernate: - ddl-auto: create-drop + ddl-auto: update + mail: host: smtp.gmail.com port: 587 @@ -44,6 +45,7 @@ aws: bucket: ${S3_BUCKET} region: static: ${S3_REGION} + cloud-front: ${S3_CLOUD_FRONT_URL} credentials: access-key: ${S3_ACCESS_KEY} secret-key: ${S3_SECRET_KEY} diff --git a/src/test/java/haru/harudrawer/domain/auth/service/LocalAuthServiceTest.java b/src/test/java/haru/harudrawer/domain/auth/service/LocalAuthServiceTest.java index d047826..4a86958 100644 --- a/src/test/java/haru/harudrawer/domain/auth/service/LocalAuthServiceTest.java +++ b/src/test/java/haru/harudrawer/domain/auth/service/LocalAuthServiceTest.java @@ -11,18 +11,24 @@ import haru.harudrawer.global.redis.RedisService; import haru.harudrawer.global.security.domain.CustomUserDetails; import haru.harudrawer.global.security.jwt.JwtProvider; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.test.annotation.Commit; import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; +import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -37,27 +43,25 @@ @ActiveProfiles("test") class LocalAuthServiceTest { - @Mock + @Autowired private AuthRepository authRepository; - @Mock + @Autowired private EmailRepository emailRepository; - @Mock - private AuthenticationManager authenticationManager; - @Mock - private RedisService redisService; - - @Mock + @Autowired private LocalAuthService localAuthService; - @Mock + @MockBean + private RedisService redisService; + + @MockBean private JwtProvider jwtProvider; - static String testEmail = "test@test.com"; - static String testPassword = "TestPassword!"; + static String testEmail = "test@testing.com"; + static String testPassword = "Test0001!"; static String testNickname = "Tester"; - static String testPhoneNumber = "01000000000"; + static String testPhoneNumber = "01000030003"; @BeforeEach public void setup() { @@ -66,7 +70,6 @@ public void setup() { @BeforeEach public void setupEmailVerification() { - String testEmail = "test@test.com"; emailRepository.findByUserEmail(testEmail).ifPresent(emailRepository::delete); @@ -82,6 +85,7 @@ public void setupEmailVerification() { @Test + @Commit @DisplayName("회원가입 테스트") public void signupTest() { //Given @@ -108,39 +112,36 @@ public void signupTest() { public void loginTest() throws Exception { //Given + // 로그인 객체 생성 LocalRequestDTO.LoginRequestDTO loginRequestDTO = LocalRequestDTO.LoginRequestDTO.builder() .userEmail(testEmail) .password(testPassword).build(); - CustomUserDetails userDetails = CustomUserDetails.builder() - .userId(1L) - .email(testEmail) - .password(testPassword) - .provider(Provider.LOCAL) - .nickName("손혁") - .authorities(Collections.emptyList()) - .build(); - - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class))) - .thenReturn(authentication); + // 토큰 생성 및 검증 String accessToken = "dummyAccessToken"; String refreshToken = "dummyRefreshToken"; - when(jwtProvider.createAccessToken(userDetails)).thenReturn(accessToken); - when(jwtProvider.createRefreshToken(testEmail)).thenReturn(refreshToken); + + // 메소드를 실행하면 설정한 결과를 반환하도록 설정 + when(jwtProvider.createAccessToken(any(CustomUserDetails.class))).thenReturn(accessToken); + when(jwtProvider.createRefreshToken(anyString())).thenReturn(refreshToken); //When + + // 로그인 진행 CommonResponseDTO.LoginResponseDTO response = localAuthService.login(loginRequestDTO); + // 사용자 조회 + Optional userOpt= authRepository.findByUserEmail(testEmail); //Then - assertNull(response); assertNotNull(response.getTokens()); - assertEquals(accessToken, response.getTokens().getAccessToken()); assertEquals(refreshToken, response.getTokens().getRefreshToken()); + assertTrue(userOpt.isPresent(), "사용자를 찾을 수 없습니다."); + assertEquals(userOpt.get().getNickName(), testNickname); + verify(redisService).saveToken(testEmail, refreshToken, Provider.LOCAL, TokenType.SERVER); } } \ No newline at end of file From fd66ac069b0e9966e589b33010362f4cdeb60e2e Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 20:30:10 +0900 Subject: [PATCH 18/64] =?UTF-8?q?Fix:=20S3Service=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/haru/harudrawer/global/s3/S3Service.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/haru/harudrawer/global/s3/S3Service.java b/src/main/java/haru/harudrawer/global/s3/S3Service.java index d346d2b..f52f4d7 100644 --- a/src/main/java/haru/harudrawer/global/s3/S3Service.java +++ b/src/main/java/haru/harudrawer/global/s3/S3Service.java @@ -30,18 +30,9 @@ public class S3Service { @Value("${aws.s3.bucket}") private String bucket; - @Value("${aws.s3.region.static}") - private String region; - @Value("${aws.s3.cloud-front}") private String cloudFrontUrl; - private String awsUrlPrefix; - - @PostConstruct - public void init() { - this.awsUrlPrefix = String.format("https://%s.s3.%s.amazonaws.com/", bucket, region); - } public List uploadFiles(List files, String preFilePath) { return files.stream() From a13d3ec8de331eb531ec96b5263cbc4b0f5a41d3 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 20:34:42 +0900 Subject: [PATCH 19/64] =?UTF-8?q?Fix:=20test.yml=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 5d4e4cc..12ea561 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -5,14 +5,14 @@ spring: resources: add-mappings: false datasource: - url: ${DB_URL} + url: ${TEST_DB_URL} driver-class-name: ${DB_DRIVER} username: ${DB_USER} password: ${DB_PASSWORD} jpa: show-sql: true hibernate: - ddl-auto: update + ddl-auto: create mail: host: smtp.gmail.com From 25c26df9f21babef229689b6cfb917049e64e542 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 21:06:22 +0900 Subject: [PATCH 20/64] =?UTF-8?q?Fix:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 2 +- .../domain/auth/service/LocalAuthServiceTest.java | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 95e4437..12ea561 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -12,7 +12,7 @@ spring: jpa: show-sql: true hibernate: - ddl-auto: update + ddl-auto: create mail: host: smtp.gmail.com diff --git a/src/test/java/haru/harudrawer/domain/auth/service/LocalAuthServiceTest.java b/src/test/java/haru/harudrawer/domain/auth/service/LocalAuthServiceTest.java index 4a86958..a550fd6 100644 --- a/src/test/java/haru/harudrawer/domain/auth/service/LocalAuthServiceTest.java +++ b/src/test/java/haru/harudrawer/domain/auth/service/LocalAuthServiceTest.java @@ -85,7 +85,6 @@ public void setupEmailVerification() { @Test - @Commit @DisplayName("회원가입 테스트") public void signupTest() { //Given @@ -112,12 +111,19 @@ public void signupTest() { public void loginTest() throws Exception { //Given + // 회원가입 객체 생성 + LocalRequestDTO.SignUpRequestDTO tester = LocalRequestDTO.SignUpRequestDTO.builder() + .userEmail(testEmail) + .password(testPassword) + .nickName(testNickname) + .phoneNumber(testPhoneNumber) + .build(); + // 로그인 객체 생성 LocalRequestDTO.LoginRequestDTO loginRequestDTO = LocalRequestDTO.LoginRequestDTO.builder() .userEmail(testEmail) .password(testPassword).build(); - // 토큰 생성 및 검증 String accessToken = "dummyAccessToken"; String refreshToken = "dummyRefreshToken"; @@ -128,6 +134,9 @@ public void loginTest() throws Exception { //When + // 회원가입 진행 + localAuthService.signUp(tester); + // 로그인 진행 CommonResponseDTO.LoginResponseDTO response = localAuthService.login(loginRequestDTO); From 31139691a92cf502b02dbf9ca07078fa214b62d9 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 21:49:12 +0900 Subject: [PATCH 21/64] =?UTF-8?q?Fix:=20workflow=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 175d010..2d26b72 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -20,6 +20,20 @@ jobs: permissions: contents: read + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: "yes" + MYSQL_DATABASE: test + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping --silent" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + steps: - uses: actions/checkout@v4 - name: Set up JDK 21 From 76650d2f31ffeb897f0156bf52bf59488a1e2f60 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 21:58:42 +0900 Subject: [PATCH 22/64] =?UTF-8?q?Fix:=20test.yml=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 12ea561..9c25575 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -6,9 +6,9 @@ spring: add-mappings: false datasource: url: ${TEST_DB_URL} - driver-class-name: ${DB_DRIVER} - username: ${DB_USER} - password: ${DB_PASSWORD} + driver-class-name: com.mysql.cj.jdbc.Driver + username: root + password: jpa: show-sql: true hibernate: From b1223dd1e2adcfd256d6d649bde543bdc244cec6 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:03:35 +0900 Subject: [PATCH 23/64] =?UTF-8?q?Fix:=20test.yml=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 9c25575..f796d66 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -6,9 +6,9 @@ spring: add-mappings: false datasource: url: ${TEST_DB_URL} - driver-class-name: com.mysql.cj.jdbc.Driver - username: root - password: + driver-class-name: ${DB_DRIVER} + username: ${DB_USER} + password: ${DB_PASSWORD:""} jpa: show-sql: true hibernate: From 739e9eeea4dc825dbc1df07e972b1d5744d94df1 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:22:49 +0900 Subject: [PATCH 24/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 2d26b72..311ea77 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -24,16 +24,28 @@ jobs: mysql: image: mysql:8.0 env: - MYSQL_ALLOW_EMPTY_PASSWORD: "yes" + MYSQL_PASSWORD: ${{secrets.DB_PASSWORD}} + MYSQL_USER: ${{secrets.DB_USER}} MYSQL_DATABASE: test ports: - 3306:3306 options: >- - --health-cmd="mysqladmin ping --silent" + --health-cmd="healthcheck.sh + --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3 + redis: + image: redis:7.2 + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: - uses: actions/checkout@v4 - name: Set up JDK 21 From 7a5d0865a403fc7dcee4c24120782e71d24dea50 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:24:19 +0900 Subject: [PATCH 25/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 311ea77..da6095f 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -24,7 +24,7 @@ jobs: mysql: image: mysql:8.0 env: - MYSQL_PASSWORD: ${{secrets.DB_PASSWORD}} + MYSQL_ROOT_PASSWORD: ${{secrets.DB_PASSWORD}} MYSQL_USER: ${{secrets.DB_USER}} MYSQL_DATABASE: test ports: From ba0401170c84ea2dddc4be982a6eaa64327cad5c Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:26:28 +0900 Subject: [PATCH 26/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index da6095f..cf7b8cc 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -25,7 +25,6 @@ jobs: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: ${{secrets.DB_PASSWORD}} - MYSQL_USER: ${{secrets.DB_USER}} MYSQL_DATABASE: test ports: - 3306:3306 From ff505060b28aaf6f051c46b31909694eb99fd7d2 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:29:15 +0900 Subject: [PATCH 27/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index f796d66..f175346 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -8,7 +8,7 @@ spring: url: ${TEST_DB_URL} driver-class-name: ${DB_DRIVER} username: ${DB_USER} - password: ${DB_PASSWORD:""} + password: ${DB_PASSWORD:"0000"} jpa: show-sql: true hibernate: From 5986891b9b241c4a861b286cb06a3143c65fc589 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:31:02 +0900 Subject: [PATCH 28/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index cf7b8cc..3d92dbd 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -24,7 +24,7 @@ jobs: mysql: image: mysql:8.0 env: - MYSQL_ROOT_PASSWORD: ${{secrets.DB_PASSWORD}} + MYSQL_ROOT_PASSWORD: "0000" MYSQL_DATABASE: test ports: - 3306:3306 From 34e0d6e003bbfe2f8a0f45abde83cd3c6c662c6e Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:32:37 +0900 Subject: [PATCH 29/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- src/main/resources/application-test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 3d92dbd..3113f01 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -24,7 +24,7 @@ jobs: mysql: image: mysql:8.0 env: - MYSQL_ROOT_PASSWORD: "0000" + MYSQL_ALLOW_EMPTY_PASSWORD : "yes" MYSQL_DATABASE: test ports: - 3306:3306 diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index f175346..061c1b1 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -7,7 +7,7 @@ spring: datasource: url: ${TEST_DB_URL} driver-class-name: ${DB_DRIVER} - username: ${DB_USER} + username: ${DB_USER:"root"} password: ${DB_PASSWORD:"0000"} jpa: show-sql: true From c387f0c27632f5d3c99d773a30ab806990cc6b75 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:32:46 +0900 Subject: [PATCH 30/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 061c1b1..ce4fa13 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -8,7 +8,7 @@ spring: url: ${TEST_DB_URL} driver-class-name: ${DB_DRIVER} username: ${DB_USER:"root"} - password: ${DB_PASSWORD:"0000"} + password: ${DB_PASSWORD:""} jpa: show-sql: true hibernate: From 6aee4cf519ddfb236a7195f18fbae075283dbd4d Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:34:27 +0900 Subject: [PATCH 31/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 3113f01..1b3b575 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -29,8 +29,7 @@ jobs: ports: - 3306:3306 options: >- - --health-cmd="healthcheck.sh - --connect --innodb_initialized" + --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3 From d35680e3a10a2425a877b2a55af84761c979469b Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:36:30 +0900 Subject: [PATCH 32/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 1b3b575..5b9b08d 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -28,7 +28,7 @@ jobs: MYSQL_DATABASE: test ports: - 3306:3306 - options: >- + options: > --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s From 90ffc1c5f26ea5b526d3274353820731bdc33238 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:37:10 +0900 Subject: [PATCH 33/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 5b9b08d..1a7c6f6 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -24,7 +24,7 @@ jobs: mysql: image: mysql:8.0 env: - MYSQL_ALLOW_EMPTY_PASSWORD : "yes" + MYSQL_ALLOW_EMPTY_PASSWORD: "yes" MYSQL_DATABASE: test ports: - 3306:3306 From 6597b70d04402254c30cbeda12371659a46cf490 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:40:00 +0900 Subject: [PATCH 34/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 1a7c6f6..47ee390 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -28,7 +28,7 @@ jobs: MYSQL_DATABASE: test ports: - 3306:3306 - options: > + options: >- --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s From f4846185f9d0fbeb656aa84f0b2453dd974ebd7b Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:42:18 +0900 Subject: [PATCH 35/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- src/main/resources/application-test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 47ee390..975405a 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -24,7 +24,7 @@ jobs: mysql: image: mysql:8.0 env: - MYSQL_ALLOW_EMPTY_PASSWORD: "yes" + MYSQL_ROOT_PASSWORD: "0000" MYSQL_DATABASE: test ports: - 3306:3306 diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index ce4fa13..061c1b1 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -8,7 +8,7 @@ spring: url: ${TEST_DB_URL} driver-class-name: ${DB_DRIVER} username: ${DB_USER:"root"} - password: ${DB_PASSWORD:""} + password: ${DB_PASSWORD:"0000"} jpa: show-sql: true hibernate: From b76544e1a53db9d47d0d40aee2143f1e0ecb3167 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:44:24 +0900 Subject: [PATCH 36/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 975405a..5034229 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -24,7 +24,7 @@ jobs: mysql: image: mysql:8.0 env: - MYSQL_ROOT_PASSWORD: "0000" + MYSQL_ROOT_PASSWORD: 0000 MYSQL_DATABASE: test ports: - 3306:3306 From e3decec3311f4aa1acf6038b55e6f5a7bba8d573 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 22:45:35 +0900 Subject: [PATCH 37/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 5034229..84368a3 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -29,7 +29,7 @@ jobs: ports: - 3306:3306 options: >- - --health-cmd="healthcheck.sh --connect --innodb_initialized" + --health-cmd="mysqladmin ping --silent" --health-interval=10s --health-timeout=5s --health-retries=3 From f348c99a0e9627828a050dd0d27aa619ec945992 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 23:04:10 +0900 Subject: [PATCH 38/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 061c1b1..c245f67 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -5,9 +5,9 @@ spring: resources: add-mappings: false datasource: - url: ${TEST_DB_URL} - driver-class-name: ${DB_DRIVER} - username: ${DB_USER:"root"} + url: ${TEST_DB_URL:jdbc:mysql://localhost:3306/test} + driver-class-name: ${DB_DRIVER:com.mysql.cj.jdbc.Driver} + username: ${DB_USER:root} password: ${DB_PASSWORD:"0000"} jpa: show-sql: true From 387a2ed16fe7999105cb3d28c2b7d387d40b9254 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 23:14:02 +0900 Subject: [PATCH 39/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 84368a3..8ccbce2 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -61,7 +61,7 @@ jobs: run: chmod +x gradlew - name: Build with Gradle Wrapper - run: ./gradlew build + run: ./gradlew build --scan # artifact(jar 파일) 업로드 - name: Upload JAR artifact From 4ce8dd226563f2b2fe8e6545803cd459aed45483 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 23:23:13 +0900 Subject: [PATCH 40/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index c245f67..4674b05 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -8,7 +8,7 @@ spring: url: ${TEST_DB_URL:jdbc:mysql://localhost:3306/test} driver-class-name: ${DB_DRIVER:com.mysql.cj.jdbc.Driver} username: ${DB_USER:root} - password: ${DB_PASSWORD:"0000"} + password: ${DB_PASSWORD:0000} jpa: show-sql: true hibernate: From 6ae8a286d4c4957dfa25af3957f31cd9685c6aa5 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 23:25:20 +0900 Subject: [PATCH 41/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 4674b05..bcb9f3e 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -5,7 +5,7 @@ spring: resources: add-mappings: false datasource: - url: ${TEST_DB_URL:jdbc:mysql://localhost:3306/test} + url: ${TEST_DB_URL:jdbc:mysql://mysql:3306/test} driver-class-name: ${DB_DRIVER:com.mysql.cj.jdbc.Driver} username: ${DB_USER:root} password: ${DB_PASSWORD:0000} From c91bdf296c3bdb4cc88e9f5ad0c5c395855694ab Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sat, 29 Mar 2025 23:28:49 +0900 Subject: [PATCH 42/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 8ccbce2..910175d 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -19,7 +19,11 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - + env: + TEST_DB_URL: jdbc:mysql://mysql:3306/test + DB_USER: root + DB_PASSWORD: 0000 + DB_DRIVER: com.mysql.cj.jdbc.Driver services: mysql: image: mysql:8.0 From 789ba4d4fb30738537194c82e76b9780d2471ffe Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 11:49:58 +0900 Subject: [PATCH 43/64] =?UTF-8?q?Fix:=20gradle.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 12 +++++++----- src/main/resources/application-test.yml | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 910175d..5644822 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -20,16 +20,18 @@ jobs: permissions: contents: read env: - TEST_DB_URL: jdbc:mysql://mysql:3306/test - DB_USER: root - DB_PASSWORD: 0000 - DB_DRIVER: com.mysql.cj.jdbc.Driver + TEST_DB_URL: ${{secrets.TEST_DB_URL}} + DB_USER: ${{secrets.DB_USER}} + DB_PASSWORD: ${{secrets.DB_PASSWORD}} + DB_DRIVER: ${{secrets.DB_DRIVER}} services: mysql: image: mysql:8.0 env: - MYSQL_ROOT_PASSWORD: 0000 + MYSQL_ROOT_PASSWORD: ${{secrets.DB_ROOT_PASSWORD}} MYSQL_DATABASE: test + MYSQL_USER: ${{secrets.DB_USER}} + MYSQL_PASSWORD: ${{secrets.DB_PASSWORD}} ports: - 3306:3306 options: >- diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index bcb9f3e..94dffc4 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -7,7 +7,7 @@ spring: datasource: url: ${TEST_DB_URL:jdbc:mysql://mysql:3306/test} driver-class-name: ${DB_DRIVER:com.mysql.cj.jdbc.Driver} - username: ${DB_USER:root} + username: ${DB_USER:son} password: ${DB_PASSWORD:0000} jpa: show-sql: true From 30a9b1e360a74077ae63eb090a04b3cb3ac62b60 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 11:58:43 +0900 Subject: [PATCH 44/64] =?UTF-8?q?Fix:=20gradle.yml=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 5644822..1b24a0b 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -58,6 +58,15 @@ jobs: java-version: '21' distribution: 'temurin' + - name: Debug environment + run: | + echo "TEST_DB_URL=$TEST_DB_URL" + echo "DB_USER=$DB_USER" + echo "DB_PASSWORD=$DB_PASSWORD" + + - name: Test network + run: ping -c 1 mysql + # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md - name: Setup Gradle @@ -67,7 +76,7 @@ jobs: run: chmod +x gradlew - name: Build with Gradle Wrapper - run: ./gradlew build --scan + run: ./gradlew build -Dspring.profiles.active=test # artifact(jar 파일) 업로드 - name: Upload JAR artifact From 3110963279de19043678dc3ba6acad50d4ede112 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 12:02:29 +0900 Subject: [PATCH 45/64] =?UTF-8?q?Fix:=20gradle.yml=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 1b24a0b..6c9a3ab 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -65,7 +65,7 @@ jobs: echo "DB_PASSWORD=$DB_PASSWORD" - name: Test network - run: ping -c 1 mysql + run: nc -z mysql 3306 # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md From b9d6f958756a672cd84f7647de9ffc12fccbd7e3 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 12:04:37 +0900 Subject: [PATCH 46/64] =?UTF-8?q?Fix:=20gradle.yml=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 6c9a3ab..9bf3557 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -64,7 +64,19 @@ jobs: echo "DB_USER=$DB_USER" echo "DB_PASSWORD=$DB_PASSWORD" - - name: Test network + - name: Wait for MySQL DNS resolution + run: | + for i in {1..30}; do + if nslookup mysql; then + echo "mysql hostname resolved" + break + else + echo "Waiting for DNS resolution... ($i)" + sleep 2 + fi + done + + - name: Test network with netcat run: nc -z mysql 3306 # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. From b9d8b98a8def8e369dca313334276771029e88c5 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 12:08:36 +0900 Subject: [PATCH 47/64] =?UTF-8?q?Fix:=20gradle.yml=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 7 ++++--- src/main/resources/application-test.yml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 9bf3557..653da91 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -35,6 +35,7 @@ jobs: ports: - 3306:3306 options: >- + --network-alias mydb --health-cmd="mysqladmin ping --silent" --health-interval=10s --health-timeout=5s @@ -67,8 +68,8 @@ jobs: - name: Wait for MySQL DNS resolution run: | for i in {1..30}; do - if nslookup mysql; then - echo "mysql hostname resolved" + if nslookup mydb; then + echo "mydb hostname resolved" break else echo "Waiting for DNS resolution... ($i)" @@ -77,7 +78,7 @@ jobs: done - name: Test network with netcat - run: nc -z mysql 3306 + run: nc -z mydb 3306 # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 94dffc4..09e3319 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -5,7 +5,7 @@ spring: resources: add-mappings: false datasource: - url: ${TEST_DB_URL:jdbc:mysql://mysql:3306/test} + url: ${TEST_DB_URL:jdbc:mysql://mydb:3306/test} driver-class-name: ${DB_DRIVER:com.mysql.cj.jdbc.Driver} username: ${DB_USER:son} password: ${DB_PASSWORD:0000} From 576db16ca2e69be05e389b0fb0cb70c9dd934318 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 12:18:19 +0900 Subject: [PATCH 48/64] =?UTF-8?q?Fix:=20gradle.yml=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 24 +----------------------- src/main/resources/application-test.yml | 8 ++++---- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 653da91..9e6fc52 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -35,7 +35,6 @@ jobs: ports: - 3306:3306 options: >- - --network-alias mydb --health-cmd="mysqladmin ping --silent" --health-interval=10s --health-timeout=5s @@ -59,27 +58,6 @@ jobs: java-version: '21' distribution: 'temurin' - - name: Debug environment - run: | - echo "TEST_DB_URL=$TEST_DB_URL" - echo "DB_USER=$DB_USER" - echo "DB_PASSWORD=$DB_PASSWORD" - - - name: Wait for MySQL DNS resolution - run: | - for i in {1..30}; do - if nslookup mydb; then - echo "mydb hostname resolved" - break - else - echo "Waiting for DNS resolution... ($i)" - sleep 2 - fi - done - - - name: Test network with netcat - run: nc -z mydb 3306 - # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md - name: Setup Gradle @@ -89,7 +67,7 @@ jobs: run: chmod +x gradlew - name: Build with Gradle Wrapper - run: ./gradlew build -Dspring.profiles.active=test + run: ./gradlew build -i # artifact(jar 파일) 업로드 - name: Upload JAR artifact diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 09e3319..12ea561 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -5,10 +5,10 @@ spring: resources: add-mappings: false datasource: - url: ${TEST_DB_URL:jdbc:mysql://mydb:3306/test} - driver-class-name: ${DB_DRIVER:com.mysql.cj.jdbc.Driver} - username: ${DB_USER:son} - password: ${DB_PASSWORD:0000} + url: ${TEST_DB_URL} + driver-class-name: ${DB_DRIVER} + username: ${DB_USER} + password: ${DB_PASSWORD} jpa: show-sql: true hibernate: From d9d83a847880b092f619438a7a912b213d6ead71 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 12:25:52 +0900 Subject: [PATCH 49/64] =?UTF-8?q?Fix:=20gradle.yml=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 10 +++------- src/main/resources/application-test.yml | 8 ++++---- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 9e6fc52..2698abc 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -19,19 +19,15 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - env: - TEST_DB_URL: ${{secrets.TEST_DB_URL}} - DB_USER: ${{secrets.DB_USER}} - DB_PASSWORD: ${{secrets.DB_PASSWORD}} - DB_DRIVER: ${{secrets.DB_DRIVER}} + services: mysql: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: ${{secrets.DB_ROOT_PASSWORD}} MYSQL_DATABASE: test - MYSQL_USER: ${{secrets.DB_USER}} - MYSQL_PASSWORD: ${{secrets.DB_PASSWORD}} + MYSQL_USER: son + MYSQL_PASSWORD: 0000 ports: - 3306:3306 options: >- diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 12ea561..7c15d14 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -5,10 +5,10 @@ spring: resources: add-mappings: false datasource: - url: ${TEST_DB_URL} - driver-class-name: ${DB_DRIVER} - username: ${DB_USER} - password: ${DB_PASSWORD} + url: jdbc:mysql://mysql:3306/test + driver-class-name: com.mysql.cj.jdbc.Driver + username: son + password: 0000 jpa: show-sql: true hibernate: From e66cc0b1491aaeba79100a780c00847d93701145 Mon Sep 17 00:00:00 2001 From: Son Hyeok <87377014+SonHyeok@users.noreply.github.com> Date: Sun, 30 Mar 2025 12:28:22 +0900 Subject: [PATCH 50/64] Fix: Update gradle.yml --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 2698abc..72bc185 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -63,7 +63,7 @@ jobs: run: chmod +x gradlew - name: Build with Gradle Wrapper - run: ./gradlew build -i + run: ./gradlew build -Dspring.profiles.active=test # artifact(jar 파일) 업로드 - name: Upload JAR artifact From 81c591fa4637fca41dfb0ed2a9fd60a1faf5b83e Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 12:32:12 +0900 Subject: [PATCH 51/64] =?UTF-8?q?Fix:=20application-test.yml=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- src/main/resources/application-test.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 2698abc..72bc185 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -63,7 +63,7 @@ jobs: run: chmod +x gradlew - name: Build with Gradle Wrapper - run: ./gradlew build -i + run: ./gradlew build -Dspring.profiles.active=test # artifact(jar 파일) 업로드 - name: Upload JAR artifact diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 7c15d14..326ff2c 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -26,8 +26,8 @@ spring: starttls: enable: true redis: - host: ${REDIS_HOST} - port: ${REDIS_PORT} + host: ${REDIS_HOST:host.docker.internal} + port: ${REDIS_PORT:6379} jwt: secret: ${JWT_SECRET} From a37ba974378c1d68da64b29835c310573d34fa21 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 12:36:14 +0900 Subject: [PATCH 52/64] =?UTF-8?q?Fix:=20application-test.yml=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 326ff2c..240b325 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -5,7 +5,7 @@ spring: resources: add-mappings: false datasource: - url: jdbc:mysql://mysql:3306/test + url: jdbc:mysql://localhost:3306/test driver-class-name: com.mysql.cj.jdbc.Driver username: son password: 0000 From 8ee810daf83e4dca10015c389ed8e284b2549949 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 12:40:12 +0900 Subject: [PATCH 53/64] =?UTF-8?q?Fix:=20application-test.yml=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 6 +++++- src/main/resources/application-test.yml | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 72bc185..983bc87 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -19,7 +19,11 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - + env: + TEST_DB_URL: ${{secrets.TEST_DB_URL}} + DB_DRIVER: ${{secrets.DB_DRIVER}} + DB_USER: ${{secrets.DB_USER}} + DB_PASSWORD: ${{secrets.DB_PASSWORD}} services: mysql: image: mysql:8.0 diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 240b325..df7d1f8 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -5,10 +5,10 @@ spring: resources: add-mappings: false datasource: - url: jdbc:mysql://localhost:3306/test - driver-class-name: com.mysql.cj.jdbc.Driver - username: son - password: 0000 + url: ${TEST_DB_URL:jdbc:mysql://localhost:3306/test} + driver-class-name: ${DB_DRIVER:com.mysql.cj.jdbc.Driver} + username: ${DB_USER:root} + password: ${DB_PASSWORD:0000} jpa: show-sql: true hibernate: From 525e245af4da872bfe016732f4c8177714c8e466 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 13:10:58 +0900 Subject: [PATCH 54/64] =?UTF-8?q?Fix:=20gradle.yml=20MySQL=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 983bc87..b4b967e 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -20,25 +20,11 @@ jobs: permissions: contents: read env: - TEST_DB_URL: ${{secrets.TEST_DB_URL}} + TEST_DB_URL: ${{secrets.DB_URL}} DB_DRIVER: ${{secrets.DB_DRIVER}} DB_USER: ${{secrets.DB_USER}} DB_PASSWORD: ${{secrets.DB_PASSWORD}} services: - mysql: - image: mysql:8.0 - env: - MYSQL_ROOT_PASSWORD: ${{secrets.DB_ROOT_PASSWORD}} - MYSQL_DATABASE: test - MYSQL_USER: son - MYSQL_PASSWORD: 0000 - ports: - - 3306:3306 - options: >- - --health-cmd="mysqladmin ping --silent" - --health-interval=10s - --health-timeout=5s - --health-retries=3 redis: image: redis:7.2 @@ -60,6 +46,14 @@ jobs: # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md + - name: setup MySQL + uses: mirromutth/mysql-action@v1.1 + with: + host port: 3306 + container port: 3306 + mysql database: 'test' + mysql root password: ${{secrets.DB_ROOT_PASSWORD}} + - name: Setup Gradle uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 From e17a8bbff979f9b732ec5a9643640449c5f611fe Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 13:11:51 +0900 Subject: [PATCH 55/64] =?UTF-8?q?Fix:=20gradle.yml=20MySQL=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index df7d1f8..41d1a50 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -5,7 +5,7 @@ spring: resources: add-mappings: false datasource: - url: ${TEST_DB_URL:jdbc:mysql://localhost:3306/test} + url: ${DB_URL:jdbc:mysql://localhost:3306/test} driver-class-name: ${DB_DRIVER:com.mysql.cj.jdbc.Driver} username: ${DB_USER:root} password: ${DB_PASSWORD:0000} From e3efe1fd749f271debda4614f6c5e45f728b7f30 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 13:13:57 +0900 Subject: [PATCH 56/64] =?UTF-8?q?Fix:=20gradle.yml=20MySQL=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index b4b967e..5c1c914 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -23,7 +23,7 @@ jobs: TEST_DB_URL: ${{secrets.DB_URL}} DB_DRIVER: ${{secrets.DB_DRIVER}} DB_USER: ${{secrets.DB_USER}} - DB_PASSWORD: ${{secrets.DB_PASSWORD}} + DB_PASSWORD: ${{secrets.DB_ROOT_PASSWORD}} services: redis: From ef8b4e183a44fe6d8ce737f0f494a2acfbb6045f Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 13:16:02 +0900 Subject: [PATCH 57/64] =?UTF-8?q?Fix:=20gradle.yml=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85=20=EC=98=B5=EC=85=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 5c1c914..d5845b2 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -61,7 +61,7 @@ jobs: run: chmod +x gradlew - name: Build with Gradle Wrapper - run: ./gradlew build -Dspring.profiles.active=test + run: ./gradlew build -Dspring.profiles.active=test -i # artifact(jar 파일) 업로드 - name: Upload JAR artifact From 92f7dfbd0391d0eac1e33fc1761e9997a8374963 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 13:16:27 +0900 Subject: [PATCH 58/64] =?UTF-8?q?Fix:=20gradle.yml=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85=20=EC=98=B5=EC=85=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index d5845b2..6a6f13a 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -20,7 +20,7 @@ jobs: permissions: contents: read env: - TEST_DB_URL: ${{secrets.DB_URL}} + DB_URL: ${{secrets.DB_URL}} DB_DRIVER: ${{secrets.DB_DRIVER}} DB_USER: ${{secrets.DB_USER}} DB_PASSWORD: ${{secrets.DB_ROOT_PASSWORD}} From ffdcdd1ccf9801fe6a628d7d887958352935d899 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 13:25:14 +0900 Subject: [PATCH 59/64] =?UTF-8?q?Fix:=20gradle.yml=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85=20=EC=98=B5=EC=85=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 ++ src/main/resources/application-test.yml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 6a6f13a..6f5b852 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -24,6 +24,8 @@ jobs: DB_DRIVER: ${{secrets.DB_DRIVER}} DB_USER: ${{secrets.DB_USER}} DB_PASSWORD: ${{secrets.DB_ROOT_PASSWORD}} + REDIS_HOST: ${{secrets.REDIS_HOST}} + REDIS_PORT: 6379 services: redis: diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 41d1a50..29bfc51 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -26,8 +26,8 @@ spring: starttls: enable: true redis: - host: ${REDIS_HOST:host.docker.internal} - port: ${REDIS_PORT:6379} + host: ${REDIS_HOST} + port: ${REDIS_PORT} jwt: secret: ${JWT_SECRET} From 8eb95d483fa42725055aeab744706053efa71b1e Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Sun, 30 Mar 2025 13:40:26 +0900 Subject: [PATCH 60/64] =?UTF-8?q?Fix:=20gradle.yml=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 23 +++++++++++++++++++++++ src/main/resources/application-test.yml | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 6f5b852..d2336a5 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -26,6 +26,29 @@ jobs: DB_PASSWORD: ${{secrets.DB_ROOT_PASSWORD}} REDIS_HOST: ${{secrets.REDIS_HOST}} REDIS_PORT: 6379 + APPLE_CLIENT_ID: ${{secrets.APPLE_CLIENT_ID}} + APPLE_KEY_ID: ${{secrets.APPLE_KEY_ID}} + APPLE_PRIVATE_KEY_URL: ${{secrets.APPLE_PRIVATE_KEY_URL}} + APPLE_PUBLIC_KEY_URL: ${{secrets.APPLE_PUBLIC_KEY_URL}} + APPLE_TEAM_ID: ${{secrets.APPLE_TEAM_ID}} + EC2_HOST: ${{secrets.EC2_HOST}} + EC2_SSH_KEY: ${{secrets.EC2_SSH_KEY}} + EC2_USER: ${{secrets.EC2_USER}} + ENCRYPTION_KEY: ${{secrets.ENCRYPTION_KEY}} + JWT_ACCESS_EXPIRATION: ${{secrets.JWT_ACCESS_EXPIRATION}} + JWT_REFRESH_EXPIRATION: ${{secrets.JWT_REFRESH_EXPIRATION}} + JWT_SECRET: ${{secrets.JWT_SECRET}} + KAKAO_REDIRECT_URL: ${{secrets.KAKAO_REDIRECT_URL}} + KAKAO_REST_API_KEY: ${{secrets.KAKAO_REST_API_KEY}} + KAKAO_STORE_API_URL: ${{secrets.KAKAO_STORE_API_URL}} + S3_ACCESS_KEY: ${{secrets.S3_ACCESS_KEY}} + S3_BUCKET: ${{secrets.S3_BUCKET}} + S3_CLOUD_FRONT_URL: ${{secrets.S3_CLOUD_FRONT_URL}} + S3_REGION: ${{secrets.S3_REGION}} + S3_SECRET_KEY: ${{secrets.S3_SECRET_KEY}} + SMTP_ADDRESS: ${{secrets.SMTP_ADDRESS}} + SMTP_PASSWORD: ${{secrets.SMTP_PASSWORD}} + TEST_DB_URL: ${{secrets.TEST_DB_URL}} services: redis: diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 29bfc51..4674b05 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -5,7 +5,7 @@ spring: resources: add-mappings: false datasource: - url: ${DB_URL:jdbc:mysql://localhost:3306/test} + url: ${TEST_DB_URL:jdbc:mysql://localhost:3306/test} driver-class-name: ${DB_DRIVER:com.mysql.cj.jdbc.Driver} username: ${DB_USER:root} password: ${DB_PASSWORD:0000} From 8736e69596e42a8992469d540a293e157607aeea Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Tue, 1 Apr 2025 14:17:48 +0900 Subject: [PATCH 61/64] =?UTF-8?q?Refactor:=20=EC=B9=B4=EC=B9=B4=EC=98=A4?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/KakaoAuthController.java | 7 +--- .../dto/response/CommonResponseDTO.java | 3 -- .../dto/response/SocialResponseDTO.java | 12 ++++++ .../domain/auth/converter/AuthConverter.java | 7 ++-- .../domain/auth/service/AppleAuthService.java | 1 - .../domain/auth/service/KakaoAuthService.java | 41 ++++++++----------- 6 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java b/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java index 833a8d9..5472cad 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java @@ -28,18 +28,13 @@ public class KakaoAuthController { public ResponseEntity> login(@RequestParam String kakaoAccessToken) { CommonResponseDTO.LoginResponseDTO loginResult = kakaoAuthService.login(kakaoAccessToken); - if (loginResult.isRequiresSignup()) { - return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT) - .body(ApiResponse.of(ResponseCode.NEED_SIGNUP,loginResult)); - } - return ResponseEntity.ok(ApiResponse.of(loginResult)); } @PostMapping("/signup/kakao") @Operation(summary = "카카오 회원가입", description = "카카오 소셜 회원가입을 처리합니다. 이메일, 닉네임을 제공해야 합니다.") public ResponseEntity> signup(@RequestBody SocialRequestDTO.SocialSignupDTO request) { - kakaoAuthService.signup(request); +// kakaoAuthService.signup(request); return ResponseEntity.status(HttpStatus.CREATED) .body(ApiResponse.of(ResponseCode.CREATED)); } diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/CommonResponseDTO.java b/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/CommonResponseDTO.java index 5ed2803..9f6379a 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/CommonResponseDTO.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/CommonResponseDTO.java @@ -29,9 +29,6 @@ public static class GetUserInfoDTO { @JsonInclude(JsonInclude.Include.NON_NULL) public static class LoginResponseDTO { - @JsonInclude(JsonInclude.Include.NON_DEFAULT) - private boolean requiresSignup; - private String userEmail; private TokenDTO tokens; diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/SocialResponseDTO.java b/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/SocialResponseDTO.java index efba1da..71041ac 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/SocialResponseDTO.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/dto/response/SocialResponseDTO.java @@ -87,6 +87,8 @@ public static class KakaoUserInfoDTO { //사용자 이메일 정보 @JsonProperty("kakao_account") private SocialResponseDTO.KakaoAccount kakaoAccount; + + } @Getter @@ -95,6 +97,9 @@ public static class KakaoAccount { @JsonProperty("email") private String kakaoEmail; + + @JsonProperty("profile") + private Profile profile; } @@ -105,4 +110,11 @@ public static class Partner { @JsonProperty("uuid") private String uuid; } + + @Getter + @Builder + public static class Profile { + @JsonProperty("nickname") + private String kakaoNickName; + } } diff --git a/src/main/java/haru/harudrawer/domain/auth/converter/AuthConverter.java b/src/main/java/haru/harudrawer/domain/auth/converter/AuthConverter.java index 846884f..2206e33 100644 --- a/src/main/java/haru/harudrawer/domain/auth/converter/AuthConverter.java +++ b/src/main/java/haru/harudrawer/domain/auth/converter/AuthConverter.java @@ -1,6 +1,7 @@ package haru.harudrawer.domain.auth.converter; import haru.harudrawer.domain.auth.controller.dto.request.SocialRequestDTO; +import haru.harudrawer.domain.auth.controller.dto.response.SocialResponseDTO; import org.springframework.stereotype.Component; import haru.harudrawer.domain.auth.entity.Provider; import haru.harudrawer.domain.auth.entity.Role; @@ -9,10 +10,10 @@ @Component public class AuthConverter { - public User signupToKakaoUserEntity(SocialRequestDTO.SocialSignupDTO request) { + public User signupToKakaoUserEntity(SocialResponseDTO.KakaoUserInfoDTO request) { return User.builder() - .nickName(request.getNickName()) - .userEmail(request.getUserEmail()) + .nickName(request.getKakaoAccount().getProfile().getKakaoNickName()) + .userEmail(request.getKakaoAccount().getKakaoEmail()) .role(Role.USER) .provider(Provider.KAKAO) .build(); diff --git a/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java index 71ee983..ca8efa8 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java @@ -133,7 +133,6 @@ private CommonResponseDTO.LoginResponseDTO createLoginResponse(User user) { CommonResponseDTO.TokenDTO tokens = tokenService.createTokens(user); return CommonResponseDTO.LoginResponseDTO.builder() - .requiresSignup(false) .userEmail(user.getUserEmail()) .tokens(tokens) .build(); diff --git a/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java index 3ec7ffd..22529ce 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java @@ -1,25 +1,26 @@ package haru.harudrawer.domain.auth.service; import haru.harudrawer.domain.auth.controller.dto.request.SocialRequestDTO; +import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; import haru.harudrawer.domain.auth.controller.dto.response.SocialResponseDTO; -import haru.harudrawer.domain.auth.entity.TokenType; +import haru.harudrawer.domain.auth.converter.AuthConverter; +import haru.harudrawer.domain.auth.entity.Provider; +import haru.harudrawer.domain.auth.entity.Role; +import haru.harudrawer.domain.auth.entity.User; +import haru.harudrawer.domain.auth.exception.AuthErrorCode; +import haru.harudrawer.domain.auth.exception.AuthException; +import haru.harudrawer.domain.auth.repository.AuthRepository; +import haru.harudrawer.global.exception.CommonErrorCode; import haru.harudrawer.global.redis.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.*; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.MultiValueMap; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; -import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; -import haru.harudrawer.domain.auth.converter.AuthConverter; -import haru.harudrawer.domain.auth.entity.Provider; -import haru.harudrawer.domain.auth.entity.User; -import haru.harudrawer.domain.auth.exception.AuthErrorCode; -import haru.harudrawer.domain.auth.exception.AuthException; -import haru.harudrawer.domain.auth.repository.AuthRepository; -import haru.harudrawer.global.exception.CommonErrorCode; import java.util.Optional; @@ -33,14 +34,13 @@ public class KakaoAuthService{ private final RestTemplate restTemplate; private final AuthConverter authConverter; private final AuthRepository authRepository; - private final RedisService redisService; private final TokenService tokenService; private final CommonAuthService commonAuthService; // 회원가입 - public void signup(SocialRequestDTO.SocialSignupDTO request) { + public User signup(SocialResponseDTO.KakaoUserInfoDTO request) { //객체 변환후 저장 - authRepository.save(authConverter.signupToKakaoUserEntity(request)); + return authRepository.save(authConverter.signupToKakaoUserEntity(request)); } @@ -60,22 +60,15 @@ public CommonResponseDTO.LoginResponseDTO login(String kakaoAccessToken) { throw new AuthException(AuthErrorCode.DUPLICATE_USER_EMAIL); } - // 사용자 존재하지 않을경우 회원 가입으로 리다이렉트 처리 - if (userOpt.isEmpty()) { - return CommonResponseDTO.LoginResponseDTO.builder() - .requiresSignup(true) - .userEmail(userInfo.getKakaoAccount().getKakaoEmail()) - .tokens(null) - .build(); - } + // 사용자가 존재하지 않을 경우 회원가입 처리 + User user; - // 로그인 성공 - User user = userOpt.get(); + user = userOpt.orElseGet(() -> signup(userInfo)); + // 로그인 성공 CommonResponseDTO.TokenDTO tokens = tokenService.createTokens(user); return CommonResponseDTO.LoginResponseDTO.builder() - .requiresSignup(false) .userEmail(null) .tokens(tokens) .build(); From 4fc6add864096f03e5f0b5695db847279f7aa9ae Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Tue, 1 Apr 2025 16:24:37 +0900 Subject: [PATCH 62/64] =?UTF-8?q?Refactor:=20Social=20Auth=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EC=BD=94=EB=93=9C=20Template=20Method=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AppleAuthController.java | 3 +- .../auth/controller/KakaoAuthController.java | 12 +-- .../dto/request/SocialRequestDTO.java | 2 +- .../domain/auth/converter/AuthConverter.java | 36 ++++----- .../social/AbstractSocialAuthService.java | 77 ++++++++++++++++++ .../{ => social}/AppleAuthService.java | 80 +++++-------------- .../{ => social}/KakaoAuthService.java | 58 +++++--------- .../service/social/SocialAuthService.java | 9 +++ 8 files changed, 146 insertions(+), 131 deletions(-) create mode 100644 src/main/java/haru/harudrawer/domain/auth/service/social/AbstractSocialAuthService.java rename src/main/java/haru/harudrawer/domain/auth/service/{ => social}/AppleAuthService.java (82%) rename src/main/java/haru/harudrawer/domain/auth/service/{ => social}/KakaoAuthService.java (67%) create mode 100644 src/main/java/haru/harudrawer/domain/auth/service/social/SocialAuthService.java diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/AppleAuthController.java b/src/main/java/haru/harudrawer/domain/auth/controller/AppleAuthController.java index 82cf710..9e5e7a1 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/AppleAuthController.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/AppleAuthController.java @@ -4,11 +4,10 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; -import haru.harudrawer.domain.auth.service.AppleAuthService; +import haru.harudrawer.domain.auth.service.social.AppleAuthService; import haru.harudrawer.global.response.ApiResponse; import haru.harudrawer.global.response.ResponseCode; diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java b/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java index 5472cad..2af0106 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java @@ -10,7 +10,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; -import haru.harudrawer.domain.auth.service.KakaoAuthService; +import haru.harudrawer.domain.auth.service.social.KakaoAuthService; import haru.harudrawer.global.response.ApiResponse; import haru.harudrawer.global.response.ResponseCode; @@ -25,20 +25,12 @@ public class KakaoAuthController { // 카카오 로그인 후 토큰과 사용자 정보 반환받음 @PostMapping("/login/kakao") @Operation(summary = "카카오 소셜 로그인", description = "카카오 소셜 로그인을 처리합니다. kakaoAccessToken을 제공해야 합니다.") - public ResponseEntity> login(@RequestParam String kakaoAccessToken) { + public ResponseEntity> login(@RequestParam String kakaoAccessToken) throws Exception { CommonResponseDTO.LoginResponseDTO loginResult = kakaoAuthService.login(kakaoAccessToken); return ResponseEntity.ok(ApiResponse.of(loginResult)); } - @PostMapping("/signup/kakao") - @Operation(summary = "카카오 회원가입", description = "카카오 소셜 회원가입을 처리합니다. 이메일, 닉네임을 제공해야 합니다.") - public ResponseEntity> signup(@RequestBody SocialRequestDTO.SocialSignupDTO request) { -// kakaoAuthService.signup(request); - return ResponseEntity.status(HttpStatus.CREATED) - .body(ApiResponse.of(ResponseCode.CREATED)); - } - @DeleteMapping("/kakao") @Operation(summary = "카카오 회원 탈퇴", description = "카카오 소셜 회원 탈퇴(연결 해제)를 처리합니다.") public ResponseEntity> delete() { diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/SocialRequestDTO.java b/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/SocialRequestDTO.java index e932e78..1f85be4 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/SocialRequestDTO.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/dto/request/SocialRequestDTO.java @@ -10,7 +10,7 @@ public class SocialRequestDTO { @Builder @Getter - public static class SocialSignupDTO { + public static class SocialUserInfoDTO { @NotBlank(message = "이메일은 필수 입력 항목입니다.") @Email(message = "이메일 형식이 올바르지 않습니다.") diff --git a/src/main/java/haru/harudrawer/domain/auth/converter/AuthConverter.java b/src/main/java/haru/harudrawer/domain/auth/converter/AuthConverter.java index 2206e33..6849df8 100644 --- a/src/main/java/haru/harudrawer/domain/auth/converter/AuthConverter.java +++ b/src/main/java/haru/harudrawer/domain/auth/converter/AuthConverter.java @@ -1,31 +1,29 @@ package haru.harudrawer.domain.auth.converter; import haru.harudrawer.domain.auth.controller.dto.request.SocialRequestDTO; -import haru.harudrawer.domain.auth.controller.dto.response.SocialResponseDTO; -import org.springframework.stereotype.Component; import haru.harudrawer.domain.auth.entity.Provider; import haru.harudrawer.domain.auth.entity.Role; import haru.harudrawer.domain.auth.entity.User; +import org.springframework.stereotype.Component; @Component public class AuthConverter { - public User signupToKakaoUserEntity(SocialResponseDTO.KakaoUserInfoDTO request) { - return User.builder() - .nickName(request.getKakaoAccount().getProfile().getKakaoNickName()) - .userEmail(request.getKakaoAccount().getKakaoEmail()) - .role(Role.USER) - .provider(Provider.KAKAO) - .build(); + public User userEmailToSocialUserEntity(SocialRequestDTO.SocialUserInfoDTO userInfo, Provider provider) { + if (provider.equals(Provider.APPLE)) { + return User.builder() + .nickName("이름을 변경해주세요.") + .userEmail(userInfo.getUserEmail()) + .role(Role.USER) + .provider(Provider.APPLE) + .build(); + } else if (provider.equals(Provider.KAKAO)) { + return User.builder() + .nickName(userInfo.getNickName()) + .userEmail(userInfo.getUserEmail()) + .role(Role.USER) + .provider(Provider.KAKAO) + .build();} + return null; } - - public User userEmailToAppleUserEntity(String userEmail) { - return User.builder() - .nickName("이름을 변경해주세요") - .userEmail(userEmail) - .role(Role.USER) - .provider(Provider.APPLE) - .build(); - } - } diff --git a/src/main/java/haru/harudrawer/domain/auth/service/social/AbstractSocialAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/social/AbstractSocialAuthService.java new file mode 100644 index 0000000..fac5be6 --- /dev/null +++ b/src/main/java/haru/harudrawer/domain/auth/service/social/AbstractSocialAuthService.java @@ -0,0 +1,77 @@ +package haru.harudrawer.domain.auth.service.social; + +import haru.harudrawer.domain.auth.controller.dto.request.SocialRequestDTO; +import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; +import haru.harudrawer.domain.auth.converter.AuthConverter; +import haru.harudrawer.domain.auth.entity.Provider; +import haru.harudrawer.domain.auth.entity.User; +import haru.harudrawer.domain.auth.exception.AuthErrorCode; +import haru.harudrawer.domain.auth.exception.AuthException; +import haru.harudrawer.domain.auth.repository.AuthRepository; +import haru.harudrawer.domain.auth.service.CommonAuthService; +import haru.harudrawer.domain.auth.service.TokenService; +import haru.harudrawer.global.redis.RedisService; +import haru.harudrawer.global.security.jwt.JwtProvider; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.client.RestTemplate; + +import java.util.Optional; + +@RequiredArgsConstructor +public abstract class AbstractSocialAuthService implements SocialAuthService{ + + protected final AuthRepository authRepository; + protected final TokenService tokenService; + protected final CommonAuthService commonAuthService; + protected final AuthConverter authConverter; + protected final RedisService redisService; + protected final RestTemplate restTemplate; + protected final JwtProvider jwtProvider; + + @Override + public CommonResponseDTO.LoginResponseDTO login(String tokenOrCode) throws Exception { + // Provider 기준으로 사용자 정보 조회 + SocialRequestDTO.SocialUserInfoDTO userInfo = getSocialUserInfo(tokenOrCode); + + // 사용자 존재 여부 확인 + Optional userOpt = authRepository.findByUserEmail(userInfo.getUserEmail()); + + // 사용자가 존재할 경우 + if (userOpt.isPresent()) { + User existingUser = userOpt.get(); + + // 다른 Provider로 가입된 경우 예외 처리 + if (!existingUser.getProvider().equals(getProvider())) { + throw new AuthException(AuthErrorCode.DUPLICATE_USER_EMAIL); + } + + // 존재하지 사용자일 경우 기존 응답 반환 + return createLoginResponse(existingUser); + } + + // 사용자가 존재하지 않을 경우 회원가입 처리 + User newUser = signup(userInfo); + return createLoginResponse(newUser); + } + + protected abstract SocialRequestDTO.SocialUserInfoDTO getSocialUserInfo(String tokenOrCode) throws Exception; + + protected abstract Provider getProvider(); + + protected CommonResponseDTO.LoginResponseDTO createLoginResponse(User user) { + CommonResponseDTO.TokenDTO tokens = tokenService.createTokens(user); + + return CommonResponseDTO.LoginResponseDTO.builder() + .userEmail(user.getUserEmail()) + .tokens(tokens) + .build(); + } + + protected User signup(SocialRequestDTO.SocialUserInfoDTO userInfo) { + User user = authConverter.userEmailToSocialUserEntity(userInfo, getProvider()); + authRepository.save(user); + return user; + } + +} diff --git a/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/social/AppleAuthService.java similarity index 82% rename from src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java rename to src/main/java/haru/harudrawer/domain/auth/service/social/AppleAuthService.java index ca8efa8..cc5c35d 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/AppleAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/social/AppleAuthService.java @@ -1,24 +1,24 @@ -package haru.harudrawer.domain.auth.service; +package haru.harudrawer.domain.auth.service.social; import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jwt.SignedJWT; -import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; +import haru.harudrawer.domain.auth.controller.dto.request.SocialRequestDTO; import haru.harudrawer.domain.auth.controller.dto.response.SocialResponseDTO; import haru.harudrawer.domain.auth.converter.AuthConverter; import haru.harudrawer.domain.auth.entity.Provider; import haru.harudrawer.domain.auth.entity.TokenType; -import haru.harudrawer.domain.auth.entity.User; import haru.harudrawer.domain.auth.exception.AuthErrorCode; import haru.harudrawer.domain.auth.exception.AuthException; import haru.harudrawer.domain.auth.repository.AuthRepository; +import haru.harudrawer.domain.auth.service.CommonAuthService; +import haru.harudrawer.domain.auth.service.TokenService; import haru.harudrawer.global.redis.RedisService; import haru.harudrawer.global.security.jwt.JwtProvider; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; -import jakarta.annotation.Nullable; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -52,10 +52,8 @@ import java.util.Optional; @Service -@RequiredArgsConstructor @Slf4j - -public class AppleAuthService { +public class AppleAuthService extends AbstractSocialAuthService{ @Value("${apple.key-id}") private String keyId; @@ -72,72 +70,34 @@ public class AppleAuthService { @Value("${apple.private-key-url}") private String privateKeyFileUrl; - private final AuthRepository authRepository; - private final RestTemplate restTemplate; - private final RedisService redisService; - private final AuthConverter authConverter; - private final TokenService tokenService; - private final CommonAuthService commonAuthService; - private final JwtProvider jwtProvider; - - - /** - * 회원 가입 - */ - public User signup(String userEmail) { - User newUser = authConverter.userEmailToAppleUserEntity(userEmail); - - authRepository.save(newUser); - - return newUser; + public AppleAuthService(AuthRepository authRepository, TokenService tokenService, CommonAuthService commonAuthService, AuthConverter authConverter, RedisService redisService, RestTemplate restTemplate, JwtProvider jwtProvider) { + super(authRepository, tokenService, commonAuthService, authConverter, redisService, restTemplate, jwtProvider); } - /** - * 애플 로그인 - */ - public CommonResponseDTO.LoginResponseDTO login(String authorizationCode) throws Exception { + + @Override + protected SocialRequestDTO.SocialUserInfoDTO getSocialUserInfo(String tokenOrCode) throws Exception { // 토큰 조회 - SocialResponseDTO.AppleTokenInfoDTO loginResponse = requestAppleToken(authorizationCode); + SocialResponseDTO.AppleTokenInfoDTO loginResponse = requestAppleToken(tokenOrCode); // idToken에서 사용자 이메일 조회 String userEmail = extractEmailFromIdToken(loginResponse.getIdToken()); - // 사용자 존재 여부 확인 - Optional userOpt = authRepository.findByUserEmail(userEmail); - - if (userOpt.isPresent()) { - User existingUser = userOpt.get(); + // apple에서 발급받은 refresh 토큰 저장 + redisService.saveToken(userEmail, loginResponse.getRefreshToken(), Provider.APPLE, TokenType.REFRESH); - // 로컬 또는 카카오로 가입된 사용자라면 예외 발생 - if (existingUser.getProvider() == Provider.LOCAL || existingUser.getProvider() == Provider.KAKAO) { - throw new AuthException(AuthErrorCode.DUPLICATE_USER_EMAIL); - } - - // 기존 애플 계정 사용자라면 로그인 진행 - return createLoginResponse(existingUser); - } - - // 사용자가 존재하지 않으면 회원가입 후 로그인 진행 - User newUser = signup(userEmail); - - return createLoginResponse(newUser); + return SocialRequestDTO.SocialUserInfoDTO.builder() + .userEmail(userEmail) + .build(); } - /** - * 토큰 생성 및 로그인 응답 생성 - */ - private CommonResponseDTO.LoginResponseDTO createLoginResponse(User user) { - - // 토큰 생성 후 Redis에 저장 - CommonResponseDTO.TokenDTO tokens = tokenService.createTokens(user); - - return CommonResponseDTO.LoginResponseDTO.builder() - .userEmail(user.getUserEmail()) - .tokens(tokens) - .build(); + @Override + protected Provider getProvider() { + return Provider.APPLE; } + /* * Apple 요청 메소드 */ diff --git a/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/social/KakaoAuthService.java similarity index 67% rename from src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java rename to src/main/java/haru/harudrawer/domain/auth/service/social/KakaoAuthService.java index 22529ce..e7dd96f 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/KakaoAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/social/KakaoAuthService.java @@ -1,4 +1,4 @@ -package haru.harudrawer.domain.auth.service; +package haru.harudrawer.domain.auth.service.social; import haru.harudrawer.domain.auth.controller.dto.request.SocialRequestDTO; import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; @@ -10,8 +10,12 @@ import haru.harudrawer.domain.auth.exception.AuthErrorCode; import haru.harudrawer.domain.auth.exception.AuthException; import haru.harudrawer.domain.auth.repository.AuthRepository; +import haru.harudrawer.domain.auth.service.CommonAuthService; +import haru.harudrawer.domain.auth.service.TokenService; import haru.harudrawer.global.exception.CommonErrorCode; import haru.harudrawer.global.redis.RedisService; +import haru.harudrawer.global.security.jwt.JwtProvider; +import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpEntity; @@ -26,54 +30,30 @@ @Service -@RequiredArgsConstructor @Slf4j @Transactional -public class KakaoAuthService{ - - private final RestTemplate restTemplate; - private final AuthConverter authConverter; - private final AuthRepository authRepository; - private final TokenService tokenService; - private final CommonAuthService commonAuthService; - - // 회원가입 - public User signup(SocialResponseDTO.KakaoUserInfoDTO request) { - //객체 변환후 저장 - return authRepository.save(authConverter.signupToKakaoUserEntity(request)); +public class KakaoAuthService extends AbstractSocialAuthService{ + public KakaoAuthService(AuthRepository authRepository, TokenService tokenService, CommonAuthService commonAuthService, AuthConverter authConverter, RedisService redisService, RestTemplate restTemplate, JwtProvider jwtProvider) { + super(authRepository, tokenService, commonAuthService, authConverter, redisService, restTemplate, jwtProvider); } - // 토큰으로 사용자 정보 조회 - public CommonResponseDTO.LoginResponseDTO login(String kakaoAccessToken) { - + @Override + protected SocialRequestDTO.SocialUserInfoDTO getSocialUserInfo(String tokenOrCode) throws Exception { // 사용자 정보 조회 - SocialResponseDTO.KakaoUserInfoDTO userInfo = getKakaoUserInfo(kakaoAccessToken); - - // 사용자 존재 유무 확인 - Optional userOpt = authRepository.findByUserEmail(userInfo.getKakaoAccount().getKakaoEmail()); - - // 사용자가 존재하지만 로컬 or 애플로 가입된 회원인지 확인 - if (userOpt.isPresent() && - (userOpt.get().getProvider().equals(Provider.LOCAL) || - userOpt.get().getProvider().equals(Provider.APPLE))) { - throw new AuthException(AuthErrorCode.DUPLICATE_USER_EMAIL); - } + SocialResponseDTO.KakaoUserInfoDTO userInfo = getKakaoUserInfo(tokenOrCode); - // 사용자가 존재하지 않을 경우 회원가입 처리 - User user; - - user = userOpt.orElseGet(() -> signup(userInfo)); - - // 로그인 성공 - CommonResponseDTO.TokenDTO tokens = tokenService.createTokens(user); - - return CommonResponseDTO.LoginResponseDTO.builder() - .userEmail(null) - .tokens(tokens) + return SocialRequestDTO.SocialUserInfoDTO.builder() + .userEmail(userInfo.getKakaoAccount().getKakaoEmail()) + .nickName(userInfo.getKakaoAccount().getProfile().getKakaoNickName()) .build(); } + @Override + protected Provider getProvider() { + return Provider.KAKAO; + } + /** * 카카오 회원 탈퇴 */ diff --git a/src/main/java/haru/harudrawer/domain/auth/service/social/SocialAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/social/SocialAuthService.java new file mode 100644 index 0000000..24440b2 --- /dev/null +++ b/src/main/java/haru/harudrawer/domain/auth/service/social/SocialAuthService.java @@ -0,0 +1,9 @@ +package haru.harudrawer.domain.auth.service.social; + +import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; + +public interface SocialAuthService { + CommonResponseDTO.LoginResponseDTO login(String tokenOrCode) throws Exception; + + +} From e976f1d359776e56ef71c461652f90214cab4f87 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Thu, 3 Apr 2025 17:44:18 +0900 Subject: [PATCH 63/64] =?UTF-8?q?Refactor:=20Auth=20=EA=B4=80=EB=A0=A8=20S?= =?UTF-8?q?ervice=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AppleAuthController.java | 28 ++-- .../auth/controller/KakaoAuthController.java | 26 ++-- .../auth/controller/LocalAuthController.java | 9 +- .../auth/service/CommonAuthService.java | 2 +- .../domain/auth/service/EmailService.java | 125 ++++++++++++------ .../domain/auth/service/LocalAuthService.java | 68 +--------- .../social/AbstractSocialAuthService.java | 2 +- .../auth/service/social/AppleAuthService.java | 25 ++-- .../auth/service/social/KakaoAuthService.java | 13 +- .../service/social/SocialAuthService.java | 2 +- .../global/security/jwt/JwtProvider.java | 9 ++ .../templates/email-verification.html | 33 +++++ 12 files changed, 186 insertions(+), 156 deletions(-) create mode 100644 src/main/resources/templates/email-verification.html diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/AppleAuthController.java b/src/main/java/haru/harudrawer/domain/auth/controller/AppleAuthController.java index 9e5e7a1..7429ac6 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/AppleAuthController.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/AppleAuthController.java @@ -1,35 +1,41 @@ package haru.harudrawer.domain.auth.controller; +import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; +import haru.harudrawer.domain.auth.service.social.SocialAuthService; +import haru.harudrawer.global.response.ApiResponse; +import haru.harudrawer.global.response.ResponseCode; import io.swagger.v3.oas.annotations.Operation; -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; -import haru.harudrawer.domain.auth.service.social.AppleAuthService; -import haru.harudrawer.global.response.ApiResponse; -import haru.harudrawer.global.response.ResponseCode; @RestController -@RequiredArgsConstructor @Slf4j @RequestMapping("/api/v1/auth") public class AppleAuthController { - private final AppleAuthService appleAuthService; + + private final SocialAuthService socialAuthService; + + + public AppleAuthController(@Qualifier("appleAuthService") SocialAuthService socialAuthService) { + this.socialAuthService = socialAuthService; + } + @PostMapping("/login/apple") @Operation(summary = "애플 로그인", description = "애플 소셜 로그인을 처리합니다. \n 응답 코드에 따른 자세한 결과는 PostMan API 명세서를 참고 부탁드립니다.") public ResponseEntity> login(@RequestParam String authorizationCode) throws Exception { - CommonResponseDTO.LoginResponseDTO result = appleAuthService.login(authorizationCode); + CommonResponseDTO.LoginResponseDTO result = socialAuthService.login(authorizationCode); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS, result)); } @DeleteMapping("/apple") @Operation(summary = "애플 회원 탈퇴", description = "애플 회원 탈퇴(토큰 회수)를 처리합니다. \n 응답 코드에 따른 자세한 결과는 PostMan API 명세서를 참고 부탁드립니다.") - public ResponseEntity> delete(HttpServletRequest request) throws Exception { - appleAuthService.revokeAppleToken(request); + public ResponseEntity> delete() throws Exception { + socialAuthService.delete(); + return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); } diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java b/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java index 2af0106..0638997 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/KakaoAuthController.java @@ -1,40 +1,42 @@ package haru.harudrawer.domain.auth.controller; -import haru.harudrawer.domain.auth.controller.dto.request.SocialRequestDTO; +import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; +import haru.harudrawer.domain.auth.service.social.SocialAuthService; +import haru.harudrawer.global.response.ApiResponse; +import haru.harudrawer.global.response.ResponseCode; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; -import haru.harudrawer.domain.auth.service.social.KakaoAuthService; -import haru.harudrawer.global.response.ApiResponse; -import haru.harudrawer.global.response.ResponseCode; @RestController @Slf4j -@RequiredArgsConstructor @RequestMapping("/api/v1/auth") @Tag(name = "카카오 소셜 로그인 컨트롤러", description = " 카카오 소셜 로그인 API를 처리합니다. 응답 코드에 따른 자세한 결과는 PostMan API 명세서를 참고 부탁드립니다.") public class KakaoAuthController { - private final KakaoAuthService kakaoAuthService; + + private final SocialAuthService socialAuthService; + + public KakaoAuthController(@Qualifier("kakaoAuthService") SocialAuthService socialAuthService) { + this.socialAuthService = socialAuthService; + } // 카카오 로그인 후 토큰과 사용자 정보 반환받음 @PostMapping("/login/kakao") @Operation(summary = "카카오 소셜 로그인", description = "카카오 소셜 로그인을 처리합니다. kakaoAccessToken을 제공해야 합니다.") public ResponseEntity> login(@RequestParam String kakaoAccessToken) throws Exception { - CommonResponseDTO.LoginResponseDTO loginResult = kakaoAuthService.login(kakaoAccessToken); + CommonResponseDTO.LoginResponseDTO loginResult = socialAuthService.login(kakaoAccessToken); return ResponseEntity.ok(ApiResponse.of(loginResult)); } @DeleteMapping("/kakao") @Operation(summary = "카카오 회원 탈퇴", description = "카카오 소셜 회원 탈퇴(연결 해제)를 처리합니다.") - public ResponseEntity> delete() { - kakaoAuthService.delete(); + public ResponseEntity> delete() throws Exception { + socialAuthService.delete(); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); } diff --git a/src/main/java/haru/harudrawer/domain/auth/controller/LocalAuthController.java b/src/main/java/haru/harudrawer/domain/auth/controller/LocalAuthController.java index d465749..6f2a46c 100644 --- a/src/main/java/haru/harudrawer/domain/auth/controller/LocalAuthController.java +++ b/src/main/java/haru/harudrawer/domain/auth/controller/LocalAuthController.java @@ -3,6 +3,7 @@ import haru.harudrawer.domain.auth.controller.dto.request.LocalRequestDTO; import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; import haru.harudrawer.domain.auth.service.CommonAuthService; +import haru.harudrawer.domain.auth.service.EmailService; import haru.harudrawer.domain.auth.service.LocalAuthService; import haru.harudrawer.global.response.ApiResponse; import haru.harudrawer.global.response.ResponseCode; @@ -17,6 +18,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.io.IOException; import java.util.Map; @RestController @@ -28,6 +30,7 @@ public class LocalAuthController { private final LocalAuthService localAuthService; private final CommonAuthService commonAuthService; + private final EmailService emailService; @PostMapping("/signup/local") @Operation(summary = "로컬 회원가입", description = "로컬 회원가입을 처리합니다. 이메일, 비밀번호, 닉네임을 제공해야 합니다. \n 응답코드에 따른 결과값은 포스트맨 API 명세서를 참고 부탁드립니다.") @@ -72,8 +75,8 @@ public ResponseEntity> checkEmailForRecovery(@RequestP @PostMapping("/send-verification") @Operation(summary = "인증 번호 이메일 전송", description = "이메일로 인증 번호 전송을 처리합니다. 이메일을 제공해야 합니다. \n 응답코드에 따른 결과값은 포스트맨 API 명세서를 참고 부탁드립니다.") - public ResponseEntity> sendEmail(@RequestParam @Email String userEmail) throws MessagingException { - localAuthService.sendEmail(userEmail); + public ResponseEntity> sendEmail(@RequestParam @Email String userEmail) throws MessagingException, IOException { + emailService.sendAndSaveEmail(userEmail); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); } @@ -81,7 +84,7 @@ public ResponseEntity> sendEmail(@RequestParam @Email @PostMapping("/verification-code") @Operation(summary = "인증번호 검증", description = "클라이언트로부터 전달받은 인증 번호 검증을 처리합니다. 이메일을 제공해야 합니다. \n 응답코드에 따른 결과값은 포스트맨 API 명세서를 참고 부탁드립니다.") public ResponseEntity> verifyCode(@RequestBody LocalRequestDTO.VerifyCodeDTO request) { - localAuthService.verifyCode(request); + emailService.verifyCode(request); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); } diff --git a/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java index d007315..0678d9a 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/CommonAuthService.java @@ -57,7 +57,7 @@ public CommonResponseDTO.GetUserInfoDTO getUserInfo(HttpServletRequest request) tokenService.validateToken(request); // 토큰을 통해 사용자 이메일 조회 - String userEmail = jwtProvider.getUserEmail(tokenService.resolveToken(request)); + String userEmail = jwtProvider.extractUserEmail(); User findUser = authRepository.findByUserEmail(userEmail).orElseThrow( () -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); diff --git a/src/main/java/haru/harudrawer/domain/auth/service/EmailService.java b/src/main/java/haru/harudrawer/domain/auth/service/EmailService.java index f756936..648a0b9 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/EmailService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/EmailService.java @@ -1,17 +1,29 @@ package haru.harudrawer.domain.auth.service; +import haru.harudrawer.domain.auth.controller.dto.request.LocalRequestDTO; +import haru.harudrawer.domain.auth.entity.EmailVerificationCode; +import haru.harudrawer.domain.auth.exception.AuthErrorCode; +import haru.harudrawer.domain.auth.exception.AuthException; +import haru.harudrawer.domain.auth.repository.EmailRepository; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.security.SecureRandom; +import java.time.LocalDateTime; +import java.util.Optional; @Service @RequiredArgsConstructor +@Transactional public class EmailService { private final JavaMailSender mailSender; @@ -19,8 +31,15 @@ public class EmailService { @Value("${spring.mail.username}") private String fromEmail; - // 인증 코드 메일 전송 - public String sendVerificationEmail(String toEmail) throws MessagingException { + @Value("classpath:templates/email-verification.html") + private Resource emailTemplate; + + private final EmailRepository emailRepository; + + /** + * 인증 코드 메일 전송 + */ + public String sendVerificationEmail(String toEmail) throws MessagingException, IOException { MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, "UTF-8"); @@ -29,7 +48,7 @@ public String sendVerificationEmail(String toEmail) throws MessagingException { helper.setFrom(fromEmail); // 발신 이메일 // HTML 템플릿에서 인증 코드를 치환 - String htmlContent = loadHtmlTemplate(); + String htmlContent = new String(emailTemplate.getInputStream().readAllBytes(), StandardCharsets.UTF_8); String token = String.valueOf(generateVerificationCode()); helper.setText(htmlContent.replace("${verificationCode}", token), true); @@ -39,6 +58,70 @@ public String sendVerificationEmail(String toEmail) throws MessagingException { return token; } + /** + * 인증번호 이메일 전송 + */ + public void sendAndSaveEmail(String userEmail) throws MessagingException, IOException { + + Optional existingVerificationCode = emailRepository.findByUserEmail(userEmail); + + // 이메일 전송 후 인증번호 반환 + String code = sendVerificationEmail(userEmail); + + if (existingVerificationCode.isPresent()) { + EmailVerificationCode emailVerificationCode = existingVerificationCode.get(); + + emailVerificationCode.updateCode(code); + } else { + // 이메일 정보 저장 + emailRepository.save(EmailVerificationCode.builder() + .userEmail(userEmail) + .emailStatus(false) + .verificationCode(code) + .expiryDate(LocalDateTime.now().plusMinutes(10)) + .build()); + } + } + + /** + * 이메일 인증 상태 조회 및 삭제 + */ + public void checkEmailVerification(String userEmail) { + // 인증번호 엔티티에서 이메일과 인증 상태로 조회 + EmailVerificationCode byUserEmail = emailRepository.findByUserEmailAndEmailStatus(userEmail, true) + .orElseThrow(() -> new AuthException(AuthErrorCode.NEED_VERIFICATION)); + + // 인증 상태가 false인 경우 + if (!byUserEmail.isEmailStatus()) { + throw new AuthException(AuthErrorCode.NEED_VERIFICATION); + } + + // 인증된 객체 삭제 + emailRepository.delete(byUserEmail); + } + + + /** + * 인증번호 검증 + */ + public void verifyCode(LocalRequestDTO.VerifyCodeDTO request) { + // 인증 토큰 검증 + if (Boolean.FALSE.equals(emailRepository.existsByVerificationCode(request.getCode()))) { + throw new AuthException(AuthErrorCode.INVALID_CERTIFICATION_CODE); + } + + // 인증 번호와 이메일로 저장된 정보 찾기 + EmailVerificationCode findCode = emailRepository.findByUserEmailAndVerificationCodeAndEmailStatus(request.getUserEmail(), request.getCode(), false) + .orElseThrow(() -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); + + // 인증 코드 시간 만료된 경우 + if (findCode.getExpiryDate().isBefore(LocalDateTime.now())) { + throw new AuthException(AuthErrorCode.VERIFICATION_TOKEN_EXPIRED); + } + + // 인증 상태 변경 + findCode.changeStatus(); + } // 인증 코드 생성 private int generateVerificationCode() { @@ -46,40 +129,4 @@ private int generateVerificationCode() { return 100000 + secureRandom.nextInt(900000); } - private String loadHtmlTemplate() { - - return "\n" + - "\n" + - "\n" + - " \n" + - " 이메일 인증 코드\n" + - " \n" + - "\n" + - "\n" + - "
\n" + - "
\n" + - "

이메일 인증

\n" + - "
\n" + - "
\n" + - "

아래 인증 코드를 복사하여 회원가입/로그인 화면에 입력해 주세요.

\n" + - "
\n" + - " ${verificationCode}\n" + - "
\n" + - "

이 코드는 10분 동안 유효합니다.

\n" + - "
\n" + - "
\n" + - "

© 2025 YourCompany. All rights reserved.

\n" + - "
\n" + - "
\n" + - "\n" + - ""; - } } diff --git a/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java index a1fb76e..0cc50ee 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/LocalAuthService.java @@ -49,7 +49,7 @@ public class LocalAuthService { */ public void signUp(LocalRequestDTO.SignUpRequestDTO request) { - checkEmailVerification(request.getUserEmail()); + emailService.checkEmailVerification(request.getUserEmail()); // 비밀번호 형식 확인 if (!isValidPassword(request.getPassword())) { @@ -154,7 +154,7 @@ public String maskEmail(User user) { public void resetPassword(LocalRequestDTO.ResetPasswordDTO request) { // 이메일 인증 상태 검사 - checkEmailVerification(request.getUserEmail()); + emailService.checkEmailVerification(request.getUserEmail()); // 변경 비밀번호와 변경 확인 비밀번호가 다를 경우 if (!request.getNewPassword().equals(request.getNewPasswordCheck())) { @@ -175,70 +175,6 @@ public void resetPassword(LocalRequestDTO.ResetPasswordDTO request) { } - /** - * 이메일 인증 상태 조회 및 삭제 - */ - private void checkEmailVerification(String userEmail) { - // 인증번호 엔티티에서 이메일과 인증 상태로 조회 - EmailVerificationCode byUserEmail = emailRepository.findByUserEmailAndEmailStatus(userEmail, true) - .orElseThrow(() -> new AuthException(AuthErrorCode.NEED_VERIFICATION)); - - // 인증 상태가 false인 경우 - if (!byUserEmail.isEmailStatus()) { - throw new AuthException(AuthErrorCode.NEED_VERIFICATION); - } - - // 인증된 객체 삭제 - emailRepository.delete(byUserEmail); - } - - - /** - * 인증번호 이메일 전송 - */ - public void sendEmail(String userEmail) throws MessagingException { - - Optional existingVerificationCode = emailRepository.findByUserEmail(userEmail); - - // 이메일 전송 후 인증번호 반환 - String code = emailService.sendVerificationEmail(userEmail); - - if (existingVerificationCode.isPresent()) { - EmailVerificationCode emailVerificationCode = existingVerificationCode.get(); - - emailVerificationCode.updateCode(code); - } else { - // 이메일 정보 저장 - emailRepository.save(EmailVerificationCode.builder() - .userEmail(userEmail) - .emailStatus(false) - .verificationCode(code) - .expiryDate(LocalDateTime.now().plusMinutes(10)) - .build()); - } - } - - /** - * 인증번호 검증 - */ - public void verifyCode(LocalRequestDTO.VerifyCodeDTO request) { - // 인증 토큰 검증 - if (Boolean.FALSE.equals(emailRepository.existsByVerificationCode(request.getCode()))) { - throw new AuthException(AuthErrorCode.INVALID_CERTIFICATION_CODE); - } - - // 인증 번호와 이메일로 저장된 정보 찾기 - EmailVerificationCode findCode = emailRepository.findByUserEmailAndVerificationCodeAndEmailStatus(request.getUserEmail(), request.getCode(), false) - .orElseThrow(() -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); - - // 인증 코드 시간 만료된 경우 - if (findCode.getExpiryDate().isBefore(LocalDateTime.now())) { - throw new AuthException(AuthErrorCode.VERIFICATION_TOKEN_EXPIRED); - } - - // 인증 상태 변경 - findCode.changeStatus(); - } /** * 검증 메서드 diff --git a/src/main/java/haru/harudrawer/domain/auth/service/social/AbstractSocialAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/social/AbstractSocialAuthService.java index fac5be6..2b4aa11 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/social/AbstractSocialAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/social/AbstractSocialAuthService.java @@ -30,7 +30,7 @@ public abstract class AbstractSocialAuthService implements SocialAuthService{ protected final JwtProvider jwtProvider; @Override - public CommonResponseDTO.LoginResponseDTO login(String tokenOrCode) throws Exception { + public final CommonResponseDTO.LoginResponseDTO login(String tokenOrCode) throws Exception { // Provider 기준으로 사용자 정보 조회 SocialRequestDTO.SocialUserInfoDTO userInfo = getSocialUserInfo(tokenOrCode); diff --git a/src/main/java/haru/harudrawer/domain/auth/service/social/AppleAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/social/AppleAuthService.java index cc5c35d..0c00580 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/social/AppleAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/social/AppleAuthService.java @@ -26,6 +26,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -51,7 +52,7 @@ import java.util.List; import java.util.Optional; -@Service +@Service("appleAuthService") @Slf4j public class AppleAuthService extends AbstractSocialAuthService{ @@ -97,7 +98,7 @@ protected Provider getProvider() { return Provider.APPLE; } - +//DEVNOTEEVOL24 /* * Apple 요청 메소드 */ @@ -117,11 +118,6 @@ private SocialResponseDTO.AppleTokenInfoDTO requestAppleToken(String authorizati createAppleRequestEntity(Optional.of(authorizationCode), clientSecret, Optional.empty()), SocialResponseDTO.AppleTokenInfoDTO.class).getBody(); - String userEmail = extractEmailFromIdToken(response.getIdToken()); - - // APPLE Refresh token -> redis에 저장 - redisService.saveToken(userEmail, response.getRefreshToken(), Provider.APPLE, TokenType.REFRESH); - return response; } catch (HttpClientErrorException e){ // 에러 발생 시 로그 출력 및 예외 처리 @@ -130,13 +126,21 @@ private SocialResponseDTO.AppleTokenInfoDTO requestAppleToken(String authorizati } } + public void delete() throws Exception { + + revokeAppleToken(); + + // user 삭제 + commonAuthService.deleteUser(); + } + /** * 애플 연결 해제 (토큰 회수) */ - public void revokeAppleToken(HttpServletRequest request) throws Exception { + public void revokeAppleToken() throws Exception { String clientSecret = createClientSecret(); - String userEmail = jwtProvider.getUserEmail(tokenService.resolveToken(request)); + String userEmail = jwtProvider.extractUserEmail(); Optional refreshToken = redisService.getToken(userEmail, Provider.APPLE, TokenType.REFRESH); @@ -156,9 +160,6 @@ public void revokeAppleToken(HttpServletRequest request) throws Exception { String.class ); - // user 삭제 - commonAuthService.deleteUser(); - log.info("apple 요청 성공"); } catch (HttpClientErrorException e) { diff --git a/src/main/java/haru/harudrawer/domain/auth/service/social/KakaoAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/social/KakaoAuthService.java index e7dd96f..3b01855 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/social/KakaoAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/social/KakaoAuthService.java @@ -1,13 +1,9 @@ package haru.harudrawer.domain.auth.service.social; import haru.harudrawer.domain.auth.controller.dto.request.SocialRequestDTO; -import haru.harudrawer.domain.auth.controller.dto.response.CommonResponseDTO; import haru.harudrawer.domain.auth.controller.dto.response.SocialResponseDTO; import haru.harudrawer.domain.auth.converter.AuthConverter; import haru.harudrawer.domain.auth.entity.Provider; -import haru.harudrawer.domain.auth.entity.Role; -import haru.harudrawer.domain.auth.entity.User; -import haru.harudrawer.domain.auth.exception.AuthErrorCode; import haru.harudrawer.domain.auth.exception.AuthException; import haru.harudrawer.domain.auth.repository.AuthRepository; import haru.harudrawer.domain.auth.service.CommonAuthService; @@ -15,9 +11,8 @@ import haru.harudrawer.global.exception.CommonErrorCode; import haru.harudrawer.global.redis.RedisService; import haru.harudrawer.global.security.jwt.JwtProvider; -import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; @@ -26,10 +21,8 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; -import java.util.Optional; - -@Service +@Service("kakaoAuthService") @Slf4j @Transactional public class KakaoAuthService extends AbstractSocialAuthService{ @@ -57,7 +50,7 @@ protected Provider getProvider() { /** * 카카오 회원 탈퇴 */ - public void delete() { + public void delete() throws Exception{ // 카카오 연결 해제 // unlinkKakaoAccount(kakaoAccessToken); diff --git a/src/main/java/haru/harudrawer/domain/auth/service/social/SocialAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/social/SocialAuthService.java index 24440b2..c29b1e7 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/social/SocialAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/social/SocialAuthService.java @@ -5,5 +5,5 @@ public interface SocialAuthService { CommonResponseDTO.LoginResponseDTO login(String tokenOrCode) throws Exception; - + void delete() throws Exception; } diff --git a/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java b/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java index 870059b..98a9fd7 100644 --- a/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java +++ b/src/main/java/haru/harudrawer/global/security/jwt/JwtProvider.java @@ -39,6 +39,7 @@ public String createAccessToken(CustomUserDetails userDetails) { return Jwts.builder() .subject(userDetails.getUsername()) + .claim("userEmail",userDetails.getEmail()) .claim("userId", userDetails.getUserId()) .claim("provider",userDetails.getProvider()) .issuedAt(Date.from(now)) @@ -81,6 +82,14 @@ public String getUserEmail(String token) { return parseClaims(token).getSubject(); } + public String extractUserEmail() { + CustomUserDetails userDetails = (CustomUserDetails) SecurityContextHolder + .getContext() + .getAuthentication() + .getPrincipal(); + return userDetails.getUsername(); // 또는 userDetails.getEmail() 등, DTO에 따라 다름 + } + // Spring Security Context에서 userId 추출 public Long extractUserId() { CustomUserDetails userDetails = (CustomUserDetails) SecurityContextHolder diff --git a/src/main/resources/templates/email-verification.html b/src/main/resources/templates/email-verification.html new file mode 100644 index 0000000..ff6bbdb --- /dev/null +++ b/src/main/resources/templates/email-verification.html @@ -0,0 +1,33 @@ + + + + + 이메일 인증 코드 + + + + + + From e9f0d25e5bdfb228c072b831ca9f4bc22c2080c5 Mon Sep 17 00:00:00 2001 From: Son Hyeok Date: Thu, 3 Apr 2025 18:12:34 +0900 Subject: [PATCH 64/64] =?UTF-8?q?Fix:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/social/AbstractSocialAuthService.java | 2 +- .../domain/auth/service/social/AppleAuthService.java | 3 +-- .../domain/auth/service/social/KakaoAuthService.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/haru/harudrawer/domain/auth/service/social/AbstractSocialAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/social/AbstractSocialAuthService.java index 2b4aa11..fac5be6 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/social/AbstractSocialAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/social/AbstractSocialAuthService.java @@ -30,7 +30,7 @@ public abstract class AbstractSocialAuthService implements SocialAuthService{ protected final JwtProvider jwtProvider; @Override - public final CommonResponseDTO.LoginResponseDTO login(String tokenOrCode) throws Exception { + public CommonResponseDTO.LoginResponseDTO login(String tokenOrCode) throws Exception { // Provider 기준으로 사용자 정보 조회 SocialRequestDTO.SocialUserInfoDTO userInfo = getSocialUserInfo(tokenOrCode); diff --git a/src/main/java/haru/harudrawer/domain/auth/service/social/AppleAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/social/AppleAuthService.java index 0c00580..d45a2c4 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/social/AppleAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/social/AppleAuthService.java @@ -52,7 +52,7 @@ import java.util.List; import java.util.Optional; -@Service("appleAuthService") +@Service @Slf4j public class AppleAuthService extends AbstractSocialAuthService{ @@ -98,7 +98,6 @@ protected Provider getProvider() { return Provider.APPLE; } -//DEVNOTEEVOL24 /* * Apple 요청 메소드 */ diff --git a/src/main/java/haru/harudrawer/domain/auth/service/social/KakaoAuthService.java b/src/main/java/haru/harudrawer/domain/auth/service/social/KakaoAuthService.java index 3b01855..825bb8e 100644 --- a/src/main/java/haru/harudrawer/domain/auth/service/social/KakaoAuthService.java +++ b/src/main/java/haru/harudrawer/domain/auth/service/social/KakaoAuthService.java @@ -22,7 +22,7 @@ import org.springframework.web.client.RestTemplate; -@Service("kakaoAuthService") +@Service @Slf4j @Transactional public class KakaoAuthService extends AbstractSocialAuthService{