From e1eee4b6fb3d655e966f5fad307c0dbc66aa45ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=A9=E1=84=8B=E1=85=B2=E1=84=8E=E1=85=A1?= =?UTF-8?q?=E1=86=AB?= Date: Thu, 9 Jan 2025 02:28:58 +0900 Subject: [PATCH 1/9] =?UTF-8?q?:fire:=20ApiResponse=EC=9D=98=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=8A=94=20=EB=B3=B5=EC=9E=A1=ED=95=B4=20=EB=B3=B4?= =?UTF-8?q?=EC=9E=84=20=ED=8C=8C=EC=9D=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EmailVerificationCheckResponseDto.java | 14 -------------- .../dto/response/EmailVerificationResponseDto.java | 14 -------------- 2 files changed, 28 deletions(-) delete mode 100644 src/main/java/com/mycom/socket/auth/dto/response/EmailVerificationCheckResponseDto.java delete mode 100644 src/main/java/com/mycom/socket/auth/dto/response/EmailVerificationResponseDto.java diff --git a/src/main/java/com/mycom/socket/auth/dto/response/EmailVerificationCheckResponseDto.java b/src/main/java/com/mycom/socket/auth/dto/response/EmailVerificationCheckResponseDto.java deleted file mode 100644 index fcd6f59..0000000 --- a/src/main/java/com/mycom/socket/auth/dto/response/EmailVerificationCheckResponseDto.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.mycom.socket.auth.dto.response; - -import com.mycom.socket.global.dto.ApiResponse; - -public record EmailVerificationCheckResponseDto(ApiResponse apiResponse) { - - public static EmailVerificationCheckResponseDto createSuccessResponse() { - return new EmailVerificationCheckResponseDto(ApiResponse.success("이메일 인증 성공", true)); - } - - public static EmailVerificationCheckResponseDto createFailureResponse(String errorMessage) { - return new EmailVerificationCheckResponseDto(ApiResponse.error(errorMessage)); - } -} \ No newline at end of file diff --git a/src/main/java/com/mycom/socket/auth/dto/response/EmailVerificationResponseDto.java b/src/main/java/com/mycom/socket/auth/dto/response/EmailVerificationResponseDto.java deleted file mode 100644 index 4e78e02..0000000 --- a/src/main/java/com/mycom/socket/auth/dto/response/EmailVerificationResponseDto.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.mycom.socket.auth.dto.response; - -import com.mycom.socket.global.dto.ApiResponse; - -public record EmailVerificationResponseDto(ApiResponse apiResponse) { - - public static EmailVerificationResponseDto createSuccessResponse() { - return new EmailVerificationResponseDto(ApiResponse.success("이메일 전송 성공")); - } - - public static EmailVerificationResponseDto createFailureResponse(String errorMessage) { - return new EmailVerificationResponseDto(ApiResponse.error(errorMessage)); - } -} \ No newline at end of file From a56d475c3cdcd9330e7c083af8b3714e5e0467f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=A9=E1=84=8B=E1=85=B2=E1=84=8E=E1=85=A1?= =?UTF-8?q?=E1=86=AB?= Date: Thu, 9 Jan 2025 02:29:56 +0900 Subject: [PATCH 2/9] =?UTF-8?q?:recycle:=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EB=AA=85=EC=97=90=EC=84=9C=20Dto=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{EmailRequestDto.java => EmailRequest.java} | 2 +- ...RequestDto.java => EmailVerificationRequest.java} | 2 +- .../{LoginRequestDto.java => LoginRequest.java} | 2 +- ...{RegisterRequestDto.java => RegisterRequest.java} | 2 +- .../auth/dto/response/EmailVerificationResponse.java | 10 ++++++++++ .../socket/auth/dto/response/LoginResponse.java | 10 ++++++++++ .../socket/auth/dto/response/LoginResponseDto.java | 10 ---------- .../socket/auth/dto/response/RegisterResponse.java | 12 ++++++++++++ 8 files changed, 36 insertions(+), 14 deletions(-) rename src/main/java/com/mycom/socket/auth/dto/request/{EmailRequestDto.java => EmailRequest.java} (90%) rename src/main/java/com/mycom/socket/auth/dto/request/{EmailVerificationRequestDto.java => EmailVerificationRequest.java} (92%) rename src/main/java/com/mycom/socket/auth/dto/request/{LoginRequestDto.java => LoginRequest.java} (92%) rename src/main/java/com/mycom/socket/auth/dto/request/{RegisterRequestDto.java => RegisterRequest.java} (95%) create mode 100644 src/main/java/com/mycom/socket/auth/dto/response/EmailVerificationResponse.java create mode 100644 src/main/java/com/mycom/socket/auth/dto/response/LoginResponse.java delete mode 100644 src/main/java/com/mycom/socket/auth/dto/response/LoginResponseDto.java create mode 100644 src/main/java/com/mycom/socket/auth/dto/response/RegisterResponse.java diff --git a/src/main/java/com/mycom/socket/auth/dto/request/EmailRequestDto.java b/src/main/java/com/mycom/socket/auth/dto/request/EmailRequest.java similarity index 90% rename from src/main/java/com/mycom/socket/auth/dto/request/EmailRequestDto.java rename to src/main/java/com/mycom/socket/auth/dto/request/EmailRequest.java index abf4411..1754adf 100644 --- a/src/main/java/com/mycom/socket/auth/dto/request/EmailRequestDto.java +++ b/src/main/java/com/mycom/socket/auth/dto/request/EmailRequest.java @@ -3,7 +3,7 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotEmpty; -public record EmailRequestDto( +public record EmailRequest( @NotEmpty(message = "이메일 주소를 입력해주세요.") @Email(message = "유효하지 않은 이메일 형식입니다.") String email diff --git a/src/main/java/com/mycom/socket/auth/dto/request/EmailVerificationRequestDto.java b/src/main/java/com/mycom/socket/auth/dto/request/EmailVerificationRequest.java similarity index 92% rename from src/main/java/com/mycom/socket/auth/dto/request/EmailVerificationRequestDto.java rename to src/main/java/com/mycom/socket/auth/dto/request/EmailVerificationRequest.java index fbb3e32..674f234 100644 --- a/src/main/java/com/mycom/socket/auth/dto/request/EmailVerificationRequestDto.java +++ b/src/main/java/com/mycom/socket/auth/dto/request/EmailVerificationRequest.java @@ -4,7 +4,7 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Pattern; -public record EmailVerificationRequestDto( +public record EmailVerificationRequest( @NotEmpty(message = "이메일 주소를 입력해주세요.") @Email(message = "유효하지 않은 이메일 형식입니다.") String email, diff --git a/src/main/java/com/mycom/socket/auth/dto/request/LoginRequestDto.java b/src/main/java/com/mycom/socket/auth/dto/request/LoginRequest.java similarity index 92% rename from src/main/java/com/mycom/socket/auth/dto/request/LoginRequestDto.java rename to src/main/java/com/mycom/socket/auth/dto/request/LoginRequest.java index 18a035d..4c9d6ca 100644 --- a/src/main/java/com/mycom/socket/auth/dto/request/LoginRequestDto.java +++ b/src/main/java/com/mycom/socket/auth/dto/request/LoginRequest.java @@ -3,7 +3,7 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; -public record LoginRequestDto( +public record LoginRequest( @NotBlank(message = "이메일은 필수입니다") @Email(message = "올바른 이메일 형식이 아닙니다") String email, diff --git a/src/main/java/com/mycom/socket/auth/dto/request/RegisterRequestDto.java b/src/main/java/com/mycom/socket/auth/dto/request/RegisterRequest.java similarity index 95% rename from src/main/java/com/mycom/socket/auth/dto/request/RegisterRequestDto.java rename to src/main/java/com/mycom/socket/auth/dto/request/RegisterRequest.java index b68b904..61b51d3 100644 --- a/src/main/java/com/mycom/socket/auth/dto/request/RegisterRequestDto.java +++ b/src/main/java/com/mycom/socket/auth/dto/request/RegisterRequest.java @@ -4,7 +4,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -public record RegisterRequestDto( +public record RegisterRequest( @NotBlank(message = "이메일은 필수입니다") @Email(message = "올바른 이메일 형식이 아닙니다") String email, diff --git a/src/main/java/com/mycom/socket/auth/dto/response/EmailVerificationResponse.java b/src/main/java/com/mycom/socket/auth/dto/response/EmailVerificationResponse.java new file mode 100644 index 0000000..033630b --- /dev/null +++ b/src/main/java/com/mycom/socket/auth/dto/response/EmailVerificationResponse.java @@ -0,0 +1,10 @@ +package com.mycom.socket.auth.dto.response; + +public record EmailVerificationResponse( + String message +) { + public static EmailVerificationResponse of(String message) { + return new EmailVerificationResponse(message); + } +} + diff --git a/src/main/java/com/mycom/socket/auth/dto/response/LoginResponse.java b/src/main/java/com/mycom/socket/auth/dto/response/LoginResponse.java new file mode 100644 index 0000000..3f7a763 --- /dev/null +++ b/src/main/java/com/mycom/socket/auth/dto/response/LoginResponse.java @@ -0,0 +1,10 @@ +package com.mycom.socket.auth.dto.response; + +public record LoginResponse( + String email, + String nickname +) { + public static LoginResponse of(String email, String nickname) { + return new LoginResponse(email, nickname); + } +} diff --git a/src/main/java/com/mycom/socket/auth/dto/response/LoginResponseDto.java b/src/main/java/com/mycom/socket/auth/dto/response/LoginResponseDto.java deleted file mode 100644 index e2cc1d4..0000000 --- a/src/main/java/com/mycom/socket/auth/dto/response/LoginResponseDto.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.mycom.socket.auth.dto.response; - -public record LoginResponseDto( - String email, - String nickname -) { - public static LoginResponseDto of(String email, String nickname) { - return new LoginResponseDto(email, nickname); - } -} diff --git a/src/main/java/com/mycom/socket/auth/dto/response/RegisterResponse.java b/src/main/java/com/mycom/socket/auth/dto/response/RegisterResponse.java new file mode 100644 index 0000000..cb29a53 --- /dev/null +++ b/src/main/java/com/mycom/socket/auth/dto/response/RegisterResponse.java @@ -0,0 +1,12 @@ +package com.mycom.socket.auth.dto.response; + +public record RegisterResponse( + Long memberId, + String email, + String nickname, + String message +) { + public static RegisterResponse of(Long memberId, String email, String nickname) { + return new RegisterResponse(memberId, email, nickname, "회원가입이 완료되었습니다."); + } +} From aa51a67974c8d7c27b54fedf8f1f861af262391e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=A9=E1=84=8B=E1=85=B2=E1=84=8E=E1=85=A1?= =?UTF-8?q?=E1=86=AB?= Date: Thu, 9 Jan 2025 02:31:49 +0900 Subject: [PATCH 3/9] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=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 --- .../member/controller/AuthControllerTest.java | 51 ++++++++++++++++--- .../member/service/LoginIntegrationTest.java | 8 +-- .../socket/member/service/LoginTest.java | 12 ++--- .../member/service/RegisterServiceTest.java | 4 +- 4 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/test/java/com/mycom/socket/member/controller/AuthControllerTest.java b/src/test/java/com/mycom/socket/member/controller/AuthControllerTest.java index e295323..7ab7062 100644 --- a/src/test/java/com/mycom/socket/member/controller/AuthControllerTest.java +++ b/src/test/java/com/mycom/socket/member/controller/AuthControllerTest.java @@ -3,8 +3,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.mycom.socket.auth.config.SecurityConfig; import com.mycom.socket.auth.controller.AuthController; -import com.mycom.socket.auth.dto.request.RegisterRequestDto; +import com.mycom.socket.auth.dto.request.RegisterRequest; +import com.mycom.socket.auth.dto.response.RegisterResponse; import com.mycom.socket.auth.service.AuthService; +import com.mycom.socket.auth.service.MailService; +import com.mycom.socket.global.exception.BadRequestException; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -33,18 +36,27 @@ class AuthControllerTest { @MockBean private AuthService authService; + @MockBean // MailService도 필요 + private MailService mailService; + @Test @WithMockUser void 회원가입_성공() throws Exception { // given - RegisterRequestDto request = new RegisterRequestDto( + RegisterRequest request = new RegisterRequest( "test@example.com", "testUser", "password123", "안녕하세요" ); - given(authService.register(any(RegisterRequestDto.class))) - .willReturn(1L); + + RegisterResponse expectedResponse = RegisterResponse.of(1L, + "test@example.com", + "testUser" + ); + + given(authService.register(any(RegisterRequest.class))) + .willReturn(expectedResponse); // when & then mockMvc.perform(post("/api/auth/register") @@ -52,15 +64,40 @@ class AuthControllerTest { .content(objectMapper.writeValueAsString(request))) .andDo(print()) .andExpect(status().isOk()) - .andExpect(jsonPath("$.message").value("Success")) - .andExpect(jsonPath("$.data").value(1)); + .andExpect(jsonPath("$.memberId").value(1)) + .andExpect(jsonPath("$.email").value("test@example.com")) + .andExpect(jsonPath("$.nickname").value("testUser")) + .andExpect(jsonPath("$.message").value("회원가입이 완료되었습니다.")); + } + + @Test + @WithMockUser + void 회원가입_실패_이메일_미인증() throws Exception { + // given + RegisterRequest request = new RegisterRequest( + "test@example.com", + "testUser", + "password123", + "안녕하세요" + ); + + given(authService.register(any(RegisterRequest.class))) + .willThrow(new BadRequestException("이메일 인증이 필요합니다.")); + + // when & then + mockMvc.perform(post("/api/auth/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("이메일 인증이 필요합니다.")); } @Test @WithMockUser void 회원가입_실패_잘못된_입력값() throws Exception { // given - RegisterRequestDto request = new RegisterRequestDto( + RegisterRequest request = new RegisterRequest( "invalid-email", "t", "123", diff --git a/src/test/java/com/mycom/socket/member/service/LoginIntegrationTest.java b/src/test/java/com/mycom/socket/member/service/LoginIntegrationTest.java index 1fcebb9..7255519 100644 --- a/src/test/java/com/mycom/socket/member/service/LoginIntegrationTest.java +++ b/src/test/java/com/mycom/socket/member/service/LoginIntegrationTest.java @@ -1,7 +1,7 @@ package com.mycom.socket.member.service; -import com.mycom.socket.auth.dto.request.LoginRequestDto; -import com.mycom.socket.auth.dto.response.LoginResponseDto; +import com.mycom.socket.auth.dto.request.LoginRequest; +import com.mycom.socket.auth.dto.response.LoginResponse; import com.mycom.socket.auth.service.AuthService; import com.mycom.socket.go_socket.entity.Member; import com.mycom.socket.go_socket.entity.enums.MemberRole; @@ -46,11 +46,11 @@ void setUp() { @Test void 로그인통합테스트() { // given - LoginRequestDto request = new LoginRequestDto("test@test.com", "password"); + LoginRequest request = new LoginRequest("test@test.com", "password"); HttpServletResponse response = new MockHttpServletResponse(); // when - LoginResponseDto loginResponse = authService.login(request, response); + LoginResponse loginResponse = authService.login(request, response); Cookie cookie = ((MockHttpServletResponse) response).getCookie("Authorization"); // then diff --git a/src/test/java/com/mycom/socket/member/service/LoginTest.java b/src/test/java/com/mycom/socket/member/service/LoginTest.java index aabbbeb..7312d0f 100644 --- a/src/test/java/com/mycom/socket/member/service/LoginTest.java +++ b/src/test/java/com/mycom/socket/member/service/LoginTest.java @@ -1,7 +1,7 @@ package com.mycom.socket.member.service; -import com.mycom.socket.auth.dto.request.LoginRequestDto; -import com.mycom.socket.auth.dto.response.LoginResponseDto; +import com.mycom.socket.auth.dto.request.LoginRequest; +import com.mycom.socket.auth.dto.response.LoginResponse; import com.mycom.socket.auth.jwt.JWTUtil; import com.mycom.socket.auth.service.AuthService; import com.mycom.socket.global.exception.BadRequestException; @@ -50,7 +50,7 @@ class LoginTest { String encodedPassword = "encodedPassword"; String token = "test.token.here"; - LoginRequestDto request = new LoginRequestDto(email, password); + LoginRequest request = new LoginRequest(email, password); Member member = Member.builder() .email(email) .password(encodedPassword) @@ -63,7 +63,7 @@ class LoginTest { when(passwordEncoder.matches(password, encodedPassword)).thenReturn(true); when(jwtUtil.createToken(email)).thenReturn(token); - LoginResponseDto response = authService.login(request, this.response); + LoginResponse response = authService.login(request, this.response); // then ArgumentCaptor cookieCaptor = ArgumentCaptor.forClass(Cookie.class); @@ -91,7 +91,7 @@ class LoginTest { // given String email = "nonexistent@test.com"; String password = "password"; - LoginRequestDto request = new LoginRequestDto(email, password); + LoginRequest request = new LoginRequest(email, password); // when when(memberRepository.findByEmail(email)).thenReturn(Optional.empty()); @@ -108,7 +108,7 @@ class LoginTest { String email = "test@test.com"; String password = "wrongpassword"; String encodedPassword = "encodedPassword"; - LoginRequestDto request = new LoginRequestDto(email, password); + LoginRequest request = new LoginRequest(email, password); Member member = Member.builder() .email(email) diff --git a/src/test/java/com/mycom/socket/member/service/RegisterServiceTest.java b/src/test/java/com/mycom/socket/member/service/RegisterServiceTest.java index 2131fd5..8318419 100644 --- a/src/test/java/com/mycom/socket/member/service/RegisterServiceTest.java +++ b/src/test/java/com/mycom/socket/member/service/RegisterServiceTest.java @@ -1,6 +1,6 @@ package com.mycom.socket.member.service; -import com.mycom.socket.auth.dto.request.RegisterRequestDto; +import com.mycom.socket.auth.dto.request.RegisterRequest; import com.mycom.socket.auth.service.AuthService; import com.mycom.socket.go_socket.entity.Member; import com.mycom.socket.go_socket.repository.MemberRepository; @@ -33,7 +33,7 @@ class RegisterServiceTest { @Test void 회원가입_성공() { // given - RegisterRequestDto request = new RegisterRequestDto( + RegisterRequest request = new RegisterRequest( "test@example.com", "testUser", "password123", From ff09a4e04cecb64b1e5744a2ef9ecde7d657eccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=A9=E1=84=8B=E1=85=B2=E1=84=8E=E1=85=A1?= =?UTF-8?q?=E1=86=AB?= Date: Thu, 9 Jan 2025 02:32:13 +0900 Subject: [PATCH 4/9] =?UTF-8?q?:recycle:=20controller=20=EA=B0=84=EC=86=8C?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 51 +++++++------------ 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/mycom/socket/auth/controller/AuthController.java b/src/main/java/com/mycom/socket/auth/controller/AuthController.java index a733f77..b9c624c 100644 --- a/src/main/java/com/mycom/socket/auth/controller/AuthController.java +++ b/src/main/java/com/mycom/socket/auth/controller/AuthController.java @@ -1,16 +1,14 @@ package com.mycom.socket.auth.controller; -import com.mycom.socket.auth.dto.request.EmailRequestDto; -import com.mycom.socket.auth.dto.request.EmailVerificationRequestDto; -import com.mycom.socket.auth.dto.request.LoginRequestDto; -import com.mycom.socket.auth.dto.request.RegisterRequestDto; -import com.mycom.socket.auth.dto.response.EmailVerificationCheckResponseDto; -import com.mycom.socket.auth.dto.response.EmailVerificationResponseDto; -import com.mycom.socket.auth.dto.response.LoginResponseDto; +import com.mycom.socket.auth.dto.request.EmailRequest; +import com.mycom.socket.auth.dto.request.EmailVerificationRequest; +import com.mycom.socket.auth.dto.request.LoginRequest; +import com.mycom.socket.auth.dto.request.RegisterRequest; +import com.mycom.socket.auth.dto.response.EmailVerificationResponse; +import com.mycom.socket.auth.dto.response.LoginResponse; +import com.mycom.socket.auth.dto.response.RegisterResponse; import com.mycom.socket.auth.service.AuthService; import com.mycom.socket.auth.service.MailService; -import com.mycom.socket.auth.service.RateLimiter; -import com.mycom.socket.global.exception.BaseException; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -23,44 +21,31 @@ public class AuthController { private final AuthService authService; private final MailService mailService; - private final RateLimiter rateLimiter; @PostMapping("/login") - public LoginResponseDto login(@Valid @RequestBody LoginRequestDto request, - HttpServletResponse response) { + public LoginResponse login(@Valid @RequestBody LoginRequest request, + HttpServletResponse response) { return authService.login(request, response); } + @PostMapping("/register") + public RegisterResponse register(@Valid @RequestBody RegisterRequest request) { + return authService.register(request); + } + @PostMapping("/logout") public void logout(HttpServletResponse response) { authService.logout(response); } - @PostMapping("/register") - public Long register(@Valid @RequestBody RegisterRequestDto request) { - return authService.register(request); - } - @PostMapping("/verification") - public EmailVerificationResponseDto mailSend(@Valid @RequestBody EmailRequestDto emailRequestDto) { - try { - boolean isSuccess = mailService.sendMail(emailRequestDto.email()); - return isSuccess ? EmailVerificationResponseDto.createSuccessResponse() : EmailVerificationResponseDto.createFailureResponse("이메일 전송에 실패했습니다."); - } catch (BaseException e) { - return EmailVerificationResponseDto.createFailureResponse(e.getMessage()); - } + public EmailVerificationResponse sendVerificationEmail(@Valid @RequestBody EmailRequest request) { + return mailService.sendMail(request.email()); } @PostMapping("/email/verify") - public EmailVerificationCheckResponseDto mailCheck(@Valid @RequestBody EmailVerificationRequestDto emailRequestDto) { - try{ - rateLimiter.checkRateLimit(emailRequestDto.email());// 시도 횟수 제한 - boolean isVerified = mailService.verifyCode(emailRequestDto.email(), emailRequestDto.code()); - return isVerified ? EmailVerificationCheckResponseDto.createSuccessResponse() : - EmailVerificationCheckResponseDto.createFailureResponse("이메일 인증에 실패했습니다."); - }catch (BaseException e){ - return EmailVerificationCheckResponseDto.createFailureResponse(e.getMessage()); - } + public EmailVerificationResponse verifyEmail(@Valid @RequestBody EmailVerificationRequest request) { + return mailService.verifyCode(request.email(), request.code()); } } From d6c1d3f7810fb74b0d3610906562897360ddcd35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=A9=E1=84=8B=E1=85=B2=E1=84=8E=E1=85=A1?= =?UTF-8?q?=E1=86=AB?= Date: Thu, 9 Jan 2025 02:32:39 +0900 Subject: [PATCH 5/9] =?UTF-8?q?:recycle:=20=EC=BF=A0=ED=82=A4=EC=99=80=20y?= =?UTF-8?q?ml=20=EC=84=A4=EC=A0=95=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/mycom/socket/auth/jwt/JWTFilter.java | 41 +++++++++++-------- .../mycom/socket/auth/jwt/JWTProperties.java | 15 +++++++ .../com/mycom/socket/auth/jwt/JWTUtil.java | 37 ++++++++++++----- .../socket/auth/security/CookieUtil.java | 36 ++++++++++++++++ .../socket/auth/security/LoginFilter.java | 30 ++++++-------- 5 files changed, 114 insertions(+), 45 deletions(-) create mode 100644 src/main/java/com/mycom/socket/auth/jwt/JWTProperties.java create mode 100644 src/main/java/com/mycom/socket/auth/security/CookieUtil.java diff --git a/src/main/java/com/mycom/socket/auth/jwt/JWTFilter.java b/src/main/java/com/mycom/socket/auth/jwt/JWTFilter.java index 55680a6..f6ca338 100644 --- a/src/main/java/com/mycom/socket/auth/jwt/JWTFilter.java +++ b/src/main/java/com/mycom/socket/auth/jwt/JWTFilter.java @@ -7,6 +7,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; @@ -15,33 +16,25 @@ import java.io.IOException; +@Slf4j @RequiredArgsConstructor public class JWTFilter extends OncePerRequestFilter { + private final JWTProperties jwtProperties; private final JWTUtil jwtUtil; private final MemberDetailsService memberDetailsService; @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - String token = resolveTokenFromCookie(request); - + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { try { + String token = resolveTokenFromCookie(request); if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) { - String email = jwtUtil.getEmail(token); - UserDetails userDetails = memberDetailsService.loadUserByUsername(email); - - UsernamePasswordAuthenticationToken authentication = - new UsernamePasswordAuthenticationToken( - userDetails, - null, - userDetails.getAuthorities() - ); - - SecurityContextHolder.getContext().setAuthentication(authentication); + setAuthentication(token); } } catch (Exception e) { + log.warn("인증 처리 실패", e); SecurityContextHolder.clearContext(); } @@ -52,11 +45,25 @@ private String resolveTokenFromCookie(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { - if ("Authorization".equals(cookie.getName())) { + if (jwtProperties.getCookieName().equals(cookie.getName())) { return cookie.getValue(); } } } return null; } + + private void setAuthentication(String token) { + String email = jwtUtil.getEmail(token); + UserDetails userDetails = memberDetailsService.loadUserByUsername(email); + + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } } \ No newline at end of file diff --git a/src/main/java/com/mycom/socket/auth/jwt/JWTProperties.java b/src/main/java/com/mycom/socket/auth/jwt/JWTProperties.java new file mode 100644 index 0000000..3fb556e --- /dev/null +++ b/src/main/java/com/mycom/socket/auth/jwt/JWTProperties.java @@ -0,0 +1,15 @@ +package com.mycom.socket.auth.jwt; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Getter +@Component +@ConfigurationProperties(prefix = "jwt") +public class JWTProperties { + private String secret; + private long accessTokenValidityInSeconds = 1800; + private String cookieName = "Authorization"; + private String issuer = "go_socket"; + private boolean secureCookie = false; +} diff --git a/src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java b/src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java index 2a6d57e..b7e3a3b 100644 --- a/src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java +++ b/src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java @@ -1,11 +1,10 @@ package com.mycom.socket.auth.jwt; -import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; @@ -16,42 +15,60 @@ public class JWTUtil { private final SecretKey secretKey; + private final JWTProperties jwtProperties; - public JWTUtil(@Value("${jwt.secret}") String secret) { - this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + public JWTUtil(JWTProperties jwtProperties) { + this.jwtProperties = jwtProperties; + this.secretKey = Keys.hmacShaKeyFor( + jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8) + ); } + /** + * JWT 토큰 생성 + */ public String createToken(String email) { - Claims claims = Jwts.claims().subject(email).build(); Date now = new Date(); - // 30분 - long accessTokenValidityInMilliseconds = 1000 * 60 * 30; - Date validity = new Date(now.getTime() + accessTokenValidityInMilliseconds); + Date validity = new Date(now.getTime() + + (jwtProperties.getAccessTokenValidityInSeconds() * 1000)); return Jwts.builder() - .claims(claims) + .issuer(jwtProperties.getIssuer()) + .subject(email) .issuedAt(now) .expiration(validity) .signWith(secretKey) .compact(); } + /** + * 토큰 유효성 검증 + */ public boolean validateToken(String token) { try { + if (!StringUtils.hasText(token)) { + return false; + } + Jwts.parser() .verifyWith(secretKey) + .requireIssuer(jwtProperties.getIssuer()) .build() .parseSignedClaims(token); return true; } catch (Exception e) { - log.warn("JWT 토큰 검증 중 에러 발생: {}", e.getMessage()); + log.warn("JWT 토큰 검증 실패", e); return false; } } + /** + * 토큰에서 이메일 추출 + */ public String getEmail(String token) { return Jwts.parser() .verifyWith(secretKey) + .requireIssuer(jwtProperties.getIssuer()) .build() .parseSignedClaims(token) .getPayload() diff --git a/src/main/java/com/mycom/socket/auth/security/CookieUtil.java b/src/main/java/com/mycom/socket/auth/security/CookieUtil.java new file mode 100644 index 0000000..422e6c1 --- /dev/null +++ b/src/main/java/com/mycom/socket/auth/security/CookieUtil.java @@ -0,0 +1,36 @@ +package com.mycom.socket.auth.security; + +import com.mycom.socket.auth.jwt.JWTProperties; +import jakarta.servlet.http.Cookie; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class CookieUtil { + private final JWTProperties jwtProperties; + + /** + * 인증 쿠키 생성 + */ + public Cookie createAuthCookie(String token) { + Cookie cookie = new Cookie(jwtProperties.getCookieName(), token); + cookie.setHttpOnly(true); + cookie.setSecure(jwtProperties.isSecureCookie()); + cookie.setPath("/"); + cookie.setMaxAge((int) jwtProperties.getAccessTokenValidityInSeconds()); + return cookie; + } + + /** + * 인증 쿠키 만료 처리 + */ + public Cookie createExpiredAuthCookie() { + Cookie cookie = new Cookie(jwtProperties.getCookieName(), null); + cookie.setHttpOnly(true); + cookie.setSecure(true); + cookie.setPath("/"); + cookie.setMaxAge(0); // 즉시 만료 + return cookie; + } +} diff --git a/src/main/java/com/mycom/socket/auth/security/LoginFilter.java b/src/main/java/com/mycom/socket/auth/security/LoginFilter.java index e3e0900..5c27f35 100644 --- a/src/main/java/com/mycom/socket/auth/security/LoginFilter.java +++ b/src/main/java/com/mycom/socket/auth/security/LoginFilter.java @@ -3,8 +3,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.mycom.socket.auth.jwt.JWTUtil; import com.mycom.socket.global.dto.ApiResponse; -import com.mycom.socket.auth.dto.request.LoginRequestDto; -import com.mycom.socket.auth.dto.response.LoginResponseDto; +import com.mycom.socket.auth.dto.request.LoginRequest; +import com.mycom.socket.auth.dto.response.LoginResponse; import com.mycom.socket.go_socket.entity.Member; import jakarta.servlet.FilterChain; import jakarta.servlet.http.Cookie; @@ -26,13 +26,14 @@ public class LoginFilter extends UsernamePasswordAuthenticationFilter { private final JWTUtil jwtUtil; // JwtProvider 대신 JWTUtil 사용 private final AuthenticationManager authenticationManager; + private final CookieUtil cookieUtil; private final ObjectMapper objectMapper; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try { - LoginRequestDto loginRequest = objectMapper.readValue(request.getInputStream(), LoginRequestDto.class); + LoginRequest loginRequest = objectMapper.readValue(request.getInputStream(), LoginRequest.class); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginRequest.email(), loginRequest.password()); @@ -44,6 +45,7 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ } } + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException { MemberDetails memberDetails = (MemberDetails) authResult.getPrincipal(); @@ -51,25 +53,17 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR String token = jwtUtil.createToken(member.getEmail()); - // HTTP Only 쿠키에 JWT 토큰 저장 - Cookie cookie = new Cookie("Authorization", token); - cookie.setHttpOnly(true); - cookie.setSecure(true); - cookie.setPath("/"); - cookie.setMaxAge(1800); // 쿠키 만료시간 30분 - - // SameSite 속성 설정 추가 - response.setHeader("Set-Cookie", - String.format("Authorization=%s; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=1800", token)); + // 쿠키 생성 및 설정 + Cookie authCookie = cookieUtil.createAuthCookie(token); + response.addCookie(authCookie); - LoginResponseDto loginResponse = new LoginResponseDto( - member.getEmail(), - member.getNickname() - ); + // 로그인 응답 생성 + LoginResponse loginResponse = new LoginResponse(member.getEmail(), member.getNickname()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding("UTF-8"); - objectMapper.writeValue(response.getWriter(), ApiResponse.success("로그인 성공", loginResponse)); + objectMapper.writeValue(response.getWriter(), loginResponse); + } @Override From 7ea7c93816fa963bbe951e045dc5fecac777ccd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=A9=E1=84=8B=E1=85=B2=E1=84=8E=E1=85=A1?= =?UTF-8?q?=E1=86=AB?= Date: Thu, 9 Jan 2025 02:33:33 +0900 Subject: [PATCH 6/9] =?UTF-8?q?:recycle:=20=EB=B6=84=EB=A6=AC=EB=90=9C=20?= =?UTF-8?q?=EC=BF=A0=ED=82=A4=20=EC=84=A4=EC=A0=95=EA=B3=BC=20=ED=95=A8?= =?UTF-8?q?=EA=BB=98=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../socket/auth/service/AuthService.java | 67 ++++++++++++------- .../socket/auth/service/MailService.java | 58 +++++++++------- .../auth/service/data/VerificationData.java | 16 ++++- 3 files changed, 92 insertions(+), 49 deletions(-) diff --git a/src/main/java/com/mycom/socket/auth/service/AuthService.java b/src/main/java/com/mycom/socket/auth/service/AuthService.java index 1c4e7f2..66e97ef 100644 --- a/src/main/java/com/mycom/socket/auth/service/AuthService.java +++ b/src/main/java/com/mycom/socket/auth/service/AuthService.java @@ -1,11 +1,13 @@ package com.mycom.socket.auth.service; +import com.mycom.socket.auth.dto.response.RegisterResponse; import com.mycom.socket.auth.jwt.JWTUtil; +import com.mycom.socket.auth.security.CookieUtil; import com.mycom.socket.global.exception.BadRequestException; import com.mycom.socket.global.exception.ConflictException; -import com.mycom.socket.auth.dto.request.LoginRequestDto; -import com.mycom.socket.auth.dto.request.RegisterRequestDto; -import com.mycom.socket.auth.dto.response.LoginResponseDto; +import com.mycom.socket.auth.dto.request.LoginRequest; +import com.mycom.socket.auth.dto.request.RegisterRequest; +import com.mycom.socket.auth.dto.response.LoginResponse; import com.mycom.socket.go_socket.entity.Member; import com.mycom.socket.go_socket.entity.enums.MemberRole; import com.mycom.socket.go_socket.repository.MemberRepository; @@ -24,8 +26,19 @@ public class AuthService { private final MemberRepository memberRepository; private final PasswordEncoder passwordEncoder; private final JWTUtil jwtUtil; + private final MailService mailService; + private final CookieUtil cookieUtil; - public LoginResponseDto login(LoginRequestDto request, HttpServletResponse response) { + /** + * 사용자 로그인 처리 + * 이메일과 비밀번호를 검증하고 JWT 토큰을 생성하여 쿠키에 저장 + * + * @param request 로그인 요청 정보 (이메일, 비밀번호) + * @param response HTTP 응답 객체 (쿠키 저장용) + * @return 로그인 성공 시 사용자 정보를 포함한 응답 + * @throws BadRequestException 잘못된 이메일이나 비밀번호인 경우 + */ + public LoginResponse login(LoginRequest request, HttpServletResponse response) { Member member = memberRepository.findByEmail(request.email()) .orElseThrow(() -> new BadRequestException("가입되지 않은 이메일입니다.")); @@ -34,23 +47,21 @@ public LoginResponseDto login(LoginRequestDto request, HttpServletResponse respo } String token = jwtUtil.createToken(member.getEmail()); + response.addCookie(cookieUtil.createAuthCookie(token)); // CookieUtil 사용 - // 쿠키 생성 및 설정 - Cookie cookie = new Cookie("Authorization", token); - cookie.setHttpOnly(true); // JavaScript에서 접근 불가 - cookie.setSecure(true); // HTTPS에서만 전송 - cookie.setPath("/"); // 모든 경로에서 접근 가능 - cookie.setMaxAge(1800); // 30분 - response.addCookie(cookie); - - return LoginResponseDto.of( - member.getEmail(), - member.getNickname() - ); + return LoginResponse.of(member.getEmail(), member.getNickname()); } + /** + * 회원 가입 처리 + * 이메일과 닉네임 중복 검사 후 새로운 회원 정보 저장 + * + * @param request 회원가입 요청 정보 (이메일, 비밀번호, 닉네임, 자기소개) + * @return 저장된 회원의 ID + * @throws ConflictException 이메일 또는 닉네임이 이미 존재하는 경우 + */ @Transactional - public Long register(RegisterRequestDto request) { + public RegisterResponse register(RegisterRequest request) { // 이메일 중복 검사 if (memberRepository.existsByEmail(request.email())) { throw new ConflictException("이미 존재하는 이메일입니다."); @@ -62,6 +73,9 @@ public Long register(RegisterRequestDto request) { } // 이메일 인증 여부 확인 + if (!mailService.isEmailVerified(request.email())) { + throw new BadRequestException("이메일 인증이 필요합니다. 이메일 인증을 먼저 완료해주세요."); + } Member member = Member.builder() .email(request.email()) @@ -72,15 +86,20 @@ public Long register(RegisterRequestDto request) { .build(); Member savedMember = memberRepository.save(member); - return savedMember.getId(); + return RegisterResponse.of( + savedMember.getId(), + savedMember.getEmail(), + savedMember.getNickname() + ); } + /** + * 로그아웃 처리 + * Authorization 쿠키를 무효화하여 로그아웃 처리 + * + * @param response HTTP 응답 객체 (쿠키 무효화용) + */ public void logout(HttpServletResponse response) { - Cookie cookie = new Cookie("Authorization", null); - cookie.setHttpOnly(true); - cookie.setSecure(true); - cookie.setPath("/"); - cookie.setMaxAge(0); // 즉시 만료 - response.addCookie(cookie); + response.addCookie(cookieUtil.createExpiredAuthCookie()); // CookieUtil 사용 } } diff --git a/src/main/java/com/mycom/socket/auth/service/MailService.java b/src/main/java/com/mycom/socket/auth/service/MailService.java index 9a3dacd..6a2d1ec 100644 --- a/src/main/java/com/mycom/socket/auth/service/MailService.java +++ b/src/main/java/com/mycom/socket/auth/service/MailService.java @@ -1,5 +1,6 @@ package com.mycom.socket.auth.service; +import com.mycom.socket.auth.dto.response.EmailVerificationResponse; import com.mycom.socket.auth.service.data.VerificationData; import com.mycom.socket.global.exception.BaseException; import jakarta.mail.MessagingException; @@ -41,14 +42,14 @@ private String createVerificationCode() { /** * 인증메일 생성 - * @param mail 수신자 이메일 주소 + * @param email 수신자 이메일 주소 * @return 생성된 인증메일 */ - public MimeMessage createMail(String mail, String verificationCode) { + public MimeMessage createMail(String email, String verificationCode) { MimeMessage message = javaMailSender.createMimeMessage(); try { message.setFrom(senderEmail); - message.setRecipients(MimeMessage.RecipientType.TO, mail); + message.setRecipients(MimeMessage.RecipientType.TO, email); message.setSubject("이메일 인증"); String body = String.format("""

요청하신 인증 번호입니다.

@@ -65,47 +66,58 @@ public MimeMessage createMail(String mail, String verificationCode) { /** * 인증메일 발송 및 인증번호 반환 - * @param mail 수신자 이메일 주소 + * @param email 수신자 이메일 주소 * @return 생성된 인증번호 */ - public boolean sendMail(String mail) { - rateLimiter.checkRateLimit(mail); + public EmailVerificationResponse sendMail(String email) { + rateLimiter.checkRateLimit(email); + String verificationCode = createVerificationCode(); - verificationDataMap.put(mail, new VerificationData(verificationCode)); + verificationDataMap.put(email, new VerificationData(verificationCode)); - MimeMessage message = createMail(mail, verificationCode); - try{ + MimeMessage message = createMail(email, verificationCode); + try { javaMailSender.send(message); - return true; - }catch (Exception e) { - throw new BaseException("이메일 발송 중 오류가 발생했습니다: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + return EmailVerificationResponse.of("이메일 전송 성공"); + } catch (Exception e) { + throw new BaseException("이메일 발송 중 오류가 발생했습니다: " + e.getMessage(), + HttpStatus.INTERNAL_SERVER_ERROR); } } /** * 인증번호 검증 + * * @param email 수신자 이메일 주소 - * @param code 사용자가 입력한 인증번호 + * @param code 사용자가 입력한 인증번호 * @return 인증번호 일치 여부 */ - public boolean verifyCode(String email, String code) { - if (!StringUtils.hasText(code) || !code.matches("\\d{6}")) { - return false; - } + public EmailVerificationResponse verifyCode(String email, String code) { + validateVerificationCode(code); VerificationData data = verificationDataMap.get(email); - if (data == null || data.isExpired()) { - return false; + throw new BaseException("인증 코드가 만료되었거나 존재하지 않습니다.", HttpStatus.BAD_REQUEST); } - boolean isVerified = data.code().equals(code); + if (!data.code().equals(code)) { + throw new BaseException("인증 코드가 일치하지 않습니다.", HttpStatus.BAD_REQUEST); + } + + verificationDataMap.put(email, data.withVerified()); + return EmailVerificationResponse.of("이메일 인증이 완료되었습니다."); + } - if (isVerified){ - verificationDataMap.remove(email); + private void validateVerificationCode(String code) { + if (!StringUtils.hasText(code) || !code.matches("\\d{6}")) { + throw new BaseException("유효하지 않은 인증 코드 형식입니다.", HttpStatus.BAD_REQUEST); } + } - return isVerified; + public boolean isEmailVerified(String email) { + VerificationData data = verificationDataMap.get(email); + return data != null && !data.isExpired() && data.verified(); } + } diff --git a/src/main/java/com/mycom/socket/auth/service/data/VerificationData.java b/src/main/java/com/mycom/socket/auth/service/data/VerificationData.java index f941f16..ca343df 100644 --- a/src/main/java/com/mycom/socket/auth/service/data/VerificationData.java +++ b/src/main/java/com/mycom/socket/auth/service/data/VerificationData.java @@ -4,15 +4,27 @@ import java.time.Duration; -public record VerificationData(String code, LocalDateTime expiryTime) { +public record VerificationData( + String code, + LocalDateTime expiryTime, + boolean verified +) { private static final Duration CODE_VALID_DURATION = Duration.ofMinutes(5); + public static VerificationData createNew(String code) { + return new VerificationData(code, LocalDateTime.now().plus(CODE_VALID_DURATION), false); + } + public VerificationData(String code) { - this(code, LocalDateTime.now().plus(CODE_VALID_DURATION)); + this(code, LocalDateTime.now().plus(CODE_VALID_DURATION), false); } public boolean isExpired() { return LocalDateTime.now().isAfter(expiryTime); } + + public VerificationData withVerified() { + return new VerificationData(this.code, this.expiryTime, true); + } } From 9bc6dbe7f85a1a63124340c709eaad2c977f3511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=A9=E1=84=8B=E1=85=B2=E1=84=8E=E1=85=A1?= =?UTF-8?q?=E1=86=AB?= Date: Thu, 9 Jan 2025 02:33:43 +0900 Subject: [PATCH 7/9] =?UTF-8?q?:recycle:=20Config=20=EC=84=A4=EC=A0=95=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 --- .../java/com/mycom/socket/auth/config/SecurityConfig.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/mycom/socket/auth/config/SecurityConfig.java b/src/main/java/com/mycom/socket/auth/config/SecurityConfig.java index f04d851..14bbcdf 100644 --- a/src/main/java/com/mycom/socket/auth/config/SecurityConfig.java +++ b/src/main/java/com/mycom/socket/auth/config/SecurityConfig.java @@ -1,6 +1,7 @@ package com.mycom.socket.auth.config; import com.mycom.socket.auth.jwt.JWTFilter; +import com.mycom.socket.auth.jwt.JWTProperties; import com.mycom.socket.auth.jwt.JWTUtil; import com.mycom.socket.auth.service.MemberDetailsService; import lombok.RequiredArgsConstructor; @@ -20,6 +21,7 @@ public class SecurityConfig{ private final JWTUtil jwtUtil; + private final JWTProperties properties; private final MemberDetailsService memberDetailsService; @Bean @@ -30,7 +32,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .httpBasic(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable) - .addFilterBefore(new JWTFilter(jwtUtil, memberDetailsService), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore( + new JWTFilter(properties, jwtUtil, memberDetailsService), + UsernamePasswordAuthenticationFilter.class + ) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) From d0db443830454b43f527bc80f0c8c801c08956fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=A9=E1=84=8B=E1=85=B2=E1=84=8E=E1=85=A1?= =?UTF-8?q?=E1=86=AB?= Date: Thu, 9 Jan 2025 02:34:56 +0900 Subject: [PATCH 8/9] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EB=A1=9C=EC=A7=81=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/service/RegisterServiceTest.java | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/mycom/socket/member/service/RegisterServiceTest.java b/src/test/java/com/mycom/socket/member/service/RegisterServiceTest.java index 8318419..4336b9d 100644 --- a/src/test/java/com/mycom/socket/member/service/RegisterServiceTest.java +++ b/src/test/java/com/mycom/socket/member/service/RegisterServiceTest.java @@ -17,6 +17,12 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.mycom.socket.global.exception.BadRequestException; +import com.mycom.socket.global.exception.ConflictException; +import com.mycom.socket.auth.service.MailService; +import com.mycom.socket.auth.dto.response.RegisterResponse; + @ExtendWith(MockitoExtension.class) class RegisterServiceTest { @@ -30,6 +36,9 @@ class RegisterServiceTest { @Mock private PasswordEncoder passwordEncoder; + @Mock + private MailService mailService; // 추가 필요 + @Test void 회원가입_성공() { // given @@ -43,24 +52,66 @@ class RegisterServiceTest { given(memberRepository.existsByEmail(request.email())).willReturn(false); given(memberRepository.existsByNickname(request.nickname())).willReturn(false); given(passwordEncoder.encode(request.password())).willReturn("encodedPassword"); + given(mailService.isEmailVerified(request.email())).willReturn(true); // 이메일 인증 확인 - // save()를 호출했을 때 반환할 Member 객체 설정 Member savedMember = Member.builder() .email(request.email()) .nickname(request.nickname()) .password("encodedPassword") .intro(request.intro()) .build(); - // ID 설정 (실제로는 DB가 생성) ReflectionTestUtils.setField(savedMember, "id", 1L); given(memberRepository.save(any(Member.class))).willReturn(savedMember); // when - Long memberId = authService.register(request); + RegisterResponse response = authService.register(request); // then - assertThat(memberId).isEqualTo(1L); + assertThat(response.memberId()).isEqualTo(1L); + assertThat(response.email()).isEqualTo(request.email()); + assertThat(response.nickname()).isEqualTo(request.nickname()); + assertThat(response.message()).isEqualTo("회원가입이 완료되었습니다."); + verify(memberRepository).save(any(Member.class)); + verify(mailService).isEmailVerified(request.email()); + } + + @Test + void 회원가입_실패_이메일_미인증() { + // given + RegisterRequest request = new RegisterRequest( + "test@example.com", + "testUser", + "password123", + "안녕하세요" + ); + + given(memberRepository.existsByEmail(request.email())).willReturn(false); + given(memberRepository.existsByNickname(request.nickname())).willReturn(false); + given(mailService.isEmailVerified(request.email())).willReturn(false); + + // when & then + assertThatThrownBy(() -> authService.register(request)) + .isInstanceOf(BadRequestException.class) + .hasMessage("이메일 인증이 필요합니다. 이메일 인증을 먼저 완료해주세요."); + } + + @Test + void 회원가입_실패_이메일_중복() { + // given + RegisterRequest request = new RegisterRequest( + "test@example.com", + "testUser", + "password123", + "안녕하세요" + ); + + given(memberRepository.existsByEmail(request.email())).willReturn(true); + + // when & then + assertThatThrownBy(() -> authService.register(request)) + .isInstanceOf(ConflictException.class) + .hasMessage("이미 존재하는 이메일입니다."); } } From 15b650bf8089360a75373f961ab8052bc5c71f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=A9=E1=84=8B=E1=85=B2=E1=84=8E=E1=85=A1?= =?UTF-8?q?=E1=86=AB?= Date: Thu, 9 Jan 2025 02:46:23 +0900 Subject: [PATCH 9/9] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EB=B0=94=EB=80=90=20=EC=96=91?= =?UTF-8?q?=EC=8B=9D=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20(=EC=BD=94=EB=93=9C=20=ED=81=B4=EB=A6=B0?= =?UTF-8?q?=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mycom/socket/auth/jwt/JWTProperties.java | 2 + .../member/controller/AuthControllerTest.java | 105 ++++++++++-------- .../member/service/LoginIntegrationTest.java | 10 +- .../socket/member/service/LoginTest.java | 32 +++--- 4 files changed, 83 insertions(+), 66 deletions(-) diff --git a/src/main/java/com/mycom/socket/auth/jwt/JWTProperties.java b/src/main/java/com/mycom/socket/auth/jwt/JWTProperties.java index 3fb556e..ad1e8d6 100644 --- a/src/main/java/com/mycom/socket/auth/jwt/JWTProperties.java +++ b/src/main/java/com/mycom/socket/auth/jwt/JWTProperties.java @@ -1,9 +1,11 @@ package com.mycom.socket.auth.jwt; import lombok.Getter; +import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Getter +@Setter @Component @ConfigurationProperties(prefix = "jwt") public class JWTProperties { diff --git a/src/test/java/com/mycom/socket/member/controller/AuthControllerTest.java b/src/test/java/com/mycom/socket/member/controller/AuthControllerTest.java index 7ab7062..2452fd3 100644 --- a/src/test/java/com/mycom/socket/member/controller/AuthControllerTest.java +++ b/src/test/java/com/mycom/socket/member/controller/AuthControllerTest.java @@ -5,8 +5,11 @@ import com.mycom.socket.auth.controller.AuthController; import com.mycom.socket.auth.dto.request.RegisterRequest; import com.mycom.socket.auth.dto.response.RegisterResponse; +import com.mycom.socket.auth.jwt.JWTProperties; +import com.mycom.socket.auth.jwt.JWTUtil; import com.mycom.socket.auth.service.AuthService; import com.mycom.socket.auth.service.MailService; +import com.mycom.socket.auth.service.MemberDetailsService; import com.mycom.socket.global.exception.BadRequestException; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -16,6 +19,8 @@ import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -36,80 +41,86 @@ class AuthControllerTest { @MockBean private AuthService authService; - @MockBean // MailService도 필요 + @MockBean private MailService mailService; + @MockBean + private JWTUtil jwtUtil; + + @MockBean + private JWTProperties jwtProperties; + + @MockBean + private MemberDetailsService memberDetailsService; + + private static final String REGISTER_API_URL = "/api/auth/register"; + private static final String REGISTER_SUCCESS_MESSAGE = "회원가입이 완료되었습니다."; + private static final String EMAIL_UNVERIFIED_MESSAGE = "이메일 인증이 필요합니다."; + @Test @WithMockUser void 회원가입_성공() throws Exception { // given - RegisterRequest request = new RegisterRequest( - "test@example.com", - "testUser", - "password123", - "안녕하세요" - ); - - RegisterResponse expectedResponse = RegisterResponse.of(1L, - "test@example.com", - "testUser" - ); + RegisterRequest request = createRegisterRequest("test@example.com", "testUser", "password123"); + RegisterResponse expectedResponse = createRegisterResponse(1L, "test@example.com", "testUser"); + given(authService.register(any(RegisterRequest.class))).willReturn(expectedResponse); - given(authService.register(any(RegisterRequest.class))) - .willReturn(expectedResponse); + // when + ResultActions resultActions = performRegisterRequest(request); - // when & then - mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andDo(print()) + // then + resultActions .andExpect(status().isOk()) .andExpect(jsonPath("$.memberId").value(1)) .andExpect(jsonPath("$.email").value("test@example.com")) .andExpect(jsonPath("$.nickname").value("testUser")) - .andExpect(jsonPath("$.message").value("회원가입이 완료되었습니다.")); + .andExpect(jsonPath("$.message").value(REGISTER_SUCCESS_MESSAGE)); } @Test @WithMockUser void 회원가입_실패_이메일_미인증() throws Exception { // given - RegisterRequest request = new RegisterRequest( - "test@example.com", - "testUser", - "password123", - "안녕하세요" - ); - + RegisterRequest request = createRegisterRequest("test@example.com", "testUser", "password123"); given(authService.register(any(RegisterRequest.class))) - .willThrow(new BadRequestException("이메일 인증이 필요합니다.")); + .willThrow(new BadRequestException(EMAIL_UNVERIFIED_MESSAGE)); - // when & then - mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andDo(print()) + // when + ResultActions resultActions = performRegisterRequest(request); + + // then + resultActions .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.message").value("이메일 인증이 필요합니다.")); + .andExpect(jsonPath("$.message").value(EMAIL_UNVERIFIED_MESSAGE)); } @Test @WithMockUser void 회원가입_실패_잘못된_입력값() throws Exception { // given - RegisterRequest request = new RegisterRequest( - "invalid-email", - "t", - "123", - "안녕하세요" - ); - - // when & then - mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andDo(print()) + RegisterRequest request = createRegisterRequest("invalid-email", "t", "123"); + + // when + ResultActions resultActions = performRegisterRequest(request); + + // then + resultActions .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.message").exists()); } -} + + + private RegisterRequest createRegisterRequest(String email, String nickname, String password) { + return new RegisterRequest(email, nickname, password, "안녕하세요"); + } + + private RegisterResponse createRegisterResponse(Long memberId, String email, String nickname) { + return RegisterResponse.of(memberId, email, nickname); + } + private ResultActions performRegisterRequest(RegisterRequest request) throws Exception { + return mockMvc.perform(post(REGISTER_API_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()); + } +} \ No newline at end of file diff --git a/src/test/java/com/mycom/socket/member/service/LoginIntegrationTest.java b/src/test/java/com/mycom/socket/member/service/LoginIntegrationTest.java index 7255519..86f7f46 100644 --- a/src/test/java/com/mycom/socket/member/service/LoginIntegrationTest.java +++ b/src/test/java/com/mycom/socket/member/service/LoginIntegrationTest.java @@ -2,6 +2,7 @@ import com.mycom.socket.auth.dto.request.LoginRequest; import com.mycom.socket.auth.dto.response.LoginResponse; +import com.mycom.socket.auth.jwt.JWTProperties; import com.mycom.socket.auth.service.AuthService; import com.mycom.socket.go_socket.entity.Member; import com.mycom.socket.go_socket.entity.enums.MemberRole; @@ -31,6 +32,9 @@ class LoginIntegrationTest { @Autowired private PasswordEncoder passwordEncoder; + @Autowired + private JWTProperties jwtProperties; + @BeforeEach void setUp() { Member testMember = Member.builder() @@ -51,7 +55,7 @@ void setUp() { // when LoginResponse loginResponse = authService.login(request, response); - Cookie cookie = ((MockHttpServletResponse) response).getCookie("Authorization"); + Cookie cookie = ((MockHttpServletResponse) response).getCookie(jwtProperties.getCookieName()); // then assertAll( @@ -59,9 +63,9 @@ void setUp() { () -> assertEquals("tester", loginResponse.nickname()), () -> assertNotNull(cookie), () -> assertTrue(cookie.isHttpOnly()), - () -> assertTrue(cookie.getSecure()), + () -> assertEquals(jwtProperties.isSecureCookie(), cookie.getSecure()), () -> assertEquals("/", cookie.getPath()), - () -> assertEquals(1800, cookie.getMaxAge()) + () -> assertEquals(jwtProperties.getAccessTokenValidityInSeconds(), cookie.getMaxAge()) ); } } \ No newline at end of file diff --git a/src/test/java/com/mycom/socket/member/service/LoginTest.java b/src/test/java/com/mycom/socket/member/service/LoginTest.java index 7312d0f..bd07eb9 100644 --- a/src/test/java/com/mycom/socket/member/service/LoginTest.java +++ b/src/test/java/com/mycom/socket/member/service/LoginTest.java @@ -3,6 +3,7 @@ import com.mycom.socket.auth.dto.request.LoginRequest; import com.mycom.socket.auth.dto.response.LoginResponse; import com.mycom.socket.auth.jwt.JWTUtil; +import com.mycom.socket.auth.security.CookieUtil; import com.mycom.socket.auth.service.AuthService; import com.mycom.socket.global.exception.BadRequestException; import com.mycom.socket.go_socket.entity.Member; @@ -38,6 +39,9 @@ class LoginTest { @Mock private JWTUtil jwtUtil; + @Mock + private CookieUtil cookieUtil; + @Mock private HttpServletResponse response; @@ -58,32 +62,28 @@ class LoginTest { .role(MemberRole.USER) .build(); - // when + Cookie authCookie = new Cookie("Authorization", token); + authCookie.setHttpOnly(true); + authCookie.setSecure(true); + authCookie.setPath("/"); + authCookie.setMaxAge(1800); + when(memberRepository.findByEmail(email)).thenReturn(Optional.of(member)); when(passwordEncoder.matches(password, encodedPassword)).thenReturn(true); when(jwtUtil.createToken(email)).thenReturn(token); + when(cookieUtil.createAuthCookie(token)).thenReturn(authCookie); // CookieUtil 동작 정의 + // when LoginResponse response = authService.login(request, this.response); // then - ArgumentCaptor cookieCaptor = ArgumentCaptor.forClass(Cookie.class); - verify(this.response).addCookie(cookieCaptor.capture()); - Cookie cookie = cookieCaptor.getValue(); - - assertAll( - () -> assertEquals(email, response.email()), - () -> assertEquals(nickname, response.nickname()), - () -> assertEquals("Authorization", cookie.getName()), - () -> assertEquals(token, cookie.getValue()), - () -> assertTrue(cookie.isHttpOnly()), - () -> assertTrue(cookie.getSecure()), - () -> assertEquals("/", cookie.getPath()), - () -> assertEquals(1800, cookie.getMaxAge()) - ); - + verify(this.response).addCookie(authCookie); + assertEquals(email, response.email()); + assertEquals(nickname, response.nickname()); verify(memberRepository).findByEmail(email); verify(passwordEncoder).matches(password, encodedPassword); verify(jwtUtil).createToken(email); + verify(cookieUtil).createAuthCookie(token); } @Test