From 28fa741d52db6898163d12faec2940a801fa2d34 Mon Sep 17 00:00:00 2001 From: Dora Choo Date: Thu, 22 Aug 2024 14:21:23 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=A4=91=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=ED=99=95=EC=9D=B8=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20=EC=95=88=EB=93=9C=EB=A1=9C=EC=9D=B4=EB=93=9C?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=B0=B1=EC=97=94=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EA=B4=80=20(#404)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 카카오 로그인 API 구현 * feat: providerId를 loginId로 수정 * feat: 소셜 로그인 시 랜덤 생성된 비밀번호 사용 * refactor: 불필요한 api 제거 Co-authored-by: fromitive <46563149+fromitive@users.noreply.github.com> Co-authored-by: SCY * test: 로그인 로직 변경 Co-authored-by: fromitive <46563149+fromitive@users.noreply.github.com> Co-authored-by: SCY * test: MemberFixture 불필요한 함수 제거 및 통일 Co-authored-by: fromitive Co-authored-by: Dora Choo * refactor: 불필요한 정보 제거 Co-authored-by: fromitive Co-authored-by: Dora Choo * feat: 카카오 로그인 에러 핸들러 추가 Co-authored-by: fromitive Co-authored-by: Dora Choo * feat: 민감 정보 로깅에서 제외 Co-authored-by: fromitive Co-authored-by: Dora Choo --------- Co-authored-by: fromitive <46563149+fromitive@users.noreply.github.com> Co-authored-by: SCY Co-authored-by: fromitive --- backend/http/auth.http | 14 +-- .../auth/config/AuthClientConfig.java | 40 +++++++ .../config/AuthClientTimeoutInterceptor.java | 21 ++++ .../auth/controller/AuthController.java | 23 ++-- .../auth/exception/AuthErrorCode.java | 4 +- .../exception/KakaoLoginExceptionHandler.java | 32 +++++ .../chongdae/auth/service/AuthClient.java | 31 +++++ .../chongdae/auth/service/AuthService.java | 36 +++--- .../dto/KakaoLoginFailResponseDto.java | 5 + .../auth/service/dto/KakaoLoginRequest.java | 4 + .../service/dto/KakaoLoginResponseDto.java | 4 + .../auth/service/dto/LoginRequest.java | 6 - .../auth/service/dto/SignupRequest.java | 6 - .../auth/service/dto/SignupResponse.java | 8 -- .../comment/controller/CommentController.java | 2 + .../logging/config/LoggingInterceptor.java | 11 ++ .../logging/config/LoggingMasked.java | 11 ++ .../chongdae/member/domain/AuthProvider.java | 10 ++ .../member/repository/MemberRepository.java | 3 + .../repository/entity/MemberEntity.java | 16 ++- backend/src/main/resources/application.yml | 4 + backend/src/main/resources/data.sql | 27 +++-- .../auth/integration/AuthIntegrationTest.java | 113 ++++-------------- .../integration/CommentIntegrationTest.java | 34 +++--- .../chongdae/global/domain/MemberFixture.java | 16 +-- .../global/helper/CookieProvider.java | 34 +++--- .../integration/OfferingIntegrationTest.java | 69 ++++++----- .../OfferingMemberIntegrationTest.java | 27 ++--- 28 files changed, 353 insertions(+), 258 deletions(-) create mode 100644 backend/src/main/java/com/zzang/chongdae/auth/config/AuthClientConfig.java create mode 100644 backend/src/main/java/com/zzang/chongdae/auth/config/AuthClientTimeoutInterceptor.java create mode 100644 backend/src/main/java/com/zzang/chongdae/auth/exception/KakaoLoginExceptionHandler.java create mode 100644 backend/src/main/java/com/zzang/chongdae/auth/service/AuthClient.java create mode 100644 backend/src/main/java/com/zzang/chongdae/auth/service/dto/KakaoLoginFailResponseDto.java create mode 100644 backend/src/main/java/com/zzang/chongdae/auth/service/dto/KakaoLoginRequest.java create mode 100644 backend/src/main/java/com/zzang/chongdae/auth/service/dto/KakaoLoginResponseDto.java delete mode 100644 backend/src/main/java/com/zzang/chongdae/auth/service/dto/LoginRequest.java delete mode 100644 backend/src/main/java/com/zzang/chongdae/auth/service/dto/SignupRequest.java delete mode 100644 backend/src/main/java/com/zzang/chongdae/auth/service/dto/SignupResponse.java create mode 100644 backend/src/main/java/com/zzang/chongdae/logging/config/LoggingMasked.java create mode 100644 backend/src/main/java/com/zzang/chongdae/member/domain/AuthProvider.java diff --git a/backend/http/auth.http b/backend/http/auth.http index f8b5f8055..cccaf4924 100644 --- a/backend/http/auth.http +++ b/backend/http/auth.http @@ -1,17 +1,9 @@ -### 로그인 API -POST {{base-url}}/auth/login +### 카카오 로그인 API +POST {{base-url}}/auth/login/kakao Content-Type: application/json { - "ci": "dora1234" -} - -### 회원가입 API -POST {{base-url}}/auth/signup -Content-Type: application/json - -{ - "ci": "poke1234567" + "accessToken": "" } ### 토큰 재발급 API diff --git a/backend/src/main/java/com/zzang/chongdae/auth/config/AuthClientConfig.java b/backend/src/main/java/com/zzang/chongdae/auth/config/AuthClientConfig.java new file mode 100644 index 000000000..7146491f1 --- /dev/null +++ b/backend/src/main/java/com/zzang/chongdae/auth/config/AuthClientConfig.java @@ -0,0 +1,40 @@ +package com.zzang.chongdae.auth.config; + +import com.zzang.chongdae.auth.service.AuthClient; +import java.time.Duration; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.ClientHttpRequestFactories; +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.web.client.RestClient; + +@Configuration +public class AuthClientConfig { + + @Value("${auth.connect-timeout-length}") + private Duration connectTimeoutLength; + + @Value("${auth.read-timeout-length}") + private Duration readTimeoutLength; + + @Bean + public AuthClient authClient() { + return new AuthClient(createRestClient()); + } + + private RestClient createRestClient() { + return RestClient.builder() + .requestFactory(createRequestFactory()) + .requestInterceptor(new AuthClientTimeoutInterceptor()) + .build(); + } + + private ClientHttpRequestFactory createRequestFactory() { + ClientHttpRequestFactorySettings requestFactorySettings = ClientHttpRequestFactorySettings.DEFAULTS + .withConnectTimeout(connectTimeoutLength) + .withReadTimeout(readTimeoutLength); + return ClientHttpRequestFactories.get(requestFactorySettings); + } +} diff --git a/backend/src/main/java/com/zzang/chongdae/auth/config/AuthClientTimeoutInterceptor.java b/backend/src/main/java/com/zzang/chongdae/auth/config/AuthClientTimeoutInterceptor.java new file mode 100644 index 000000000..03ffed547 --- /dev/null +++ b/backend/src/main/java/com/zzang/chongdae/auth/config/AuthClientTimeoutInterceptor.java @@ -0,0 +1,21 @@ +package com.zzang.chongdae.auth.config; + +import com.zzang.chongdae.auth.exception.AuthErrorCode; +import com.zzang.chongdae.global.exception.MarketException; +import java.io.IOException; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; + +public class AuthClientTimeoutInterceptor implements ClientHttpRequestInterceptor { + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) { + try { + return execution.execute(request, body); + } catch (IOException exception) { + throw new MarketException(AuthErrorCode.CLIENT_TIME_OUT); + } + } +} diff --git a/backend/src/main/java/com/zzang/chongdae/auth/controller/AuthController.java b/backend/src/main/java/com/zzang/chongdae/auth/controller/AuthController.java index be01cf89c..b454b7693 100644 --- a/backend/src/main/java/com/zzang/chongdae/auth/controller/AuthController.java +++ b/backend/src/main/java/com/zzang/chongdae/auth/controller/AuthController.java @@ -3,10 +3,9 @@ import com.zzang.chongdae.auth.service.AuthService; import com.zzang.chongdae.auth.service.dto.AuthInfoDto; import com.zzang.chongdae.auth.service.dto.AuthTokenDto; -import com.zzang.chongdae.auth.service.dto.LoginRequest; +import com.zzang.chongdae.auth.service.dto.KakaoLoginRequest; import com.zzang.chongdae.auth.service.dto.LoginResponse; -import com.zzang.chongdae.auth.service.dto.SignupRequest; -import com.zzang.chongdae.auth.service.dto.SignupResponse; +import com.zzang.chongdae.logging.config.LoggingMasked; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -26,24 +25,16 @@ public class AuthController { private final CookieProducer cookieExtractor; private final CookieConsumer cookieConsumer; - @PostMapping("/auth/login") - public ResponseEntity login( - @RequestBody @Valid LoginRequest request, HttpServletResponse servletResponse) { - AuthInfoDto authInfo = authService.login(request); + @LoggingMasked + @PostMapping("/auth/login/kakao") + public ResponseEntity kakaoLogin( + @RequestBody @Valid KakaoLoginRequest request, HttpServletResponse servletResponse) { + AuthInfoDto authInfo = authService.kakaoLogin(request); addTokenToHttpServletResponse(authInfo.authToken(), servletResponse); LoginResponse response = new LoginResponse(authInfo.authMember()); return ResponseEntity.ok(response); } - @PostMapping("/auth/signup") - public ResponseEntity signup( - @RequestBody @Valid SignupRequest request, HttpServletResponse servletResponse) { - AuthInfoDto authInfo = authService.signup(request); - addTokenToHttpServletResponse(authInfo.authToken(), servletResponse); - SignupResponse response = new SignupResponse(authInfo.authMember()); - return ResponseEntity.ok(response); - } - @PostMapping("/auth/refresh") public ResponseEntity refresh( HttpServletRequest servletRequest, HttpServletResponse servletResponse) { diff --git a/backend/src/main/java/com/zzang/chongdae/auth/exception/AuthErrorCode.java b/backend/src/main/java/com/zzang/chongdae/auth/exception/AuthErrorCode.java index b0472cfdd..6e0420fdf 100644 --- a/backend/src/main/java/com/zzang/chongdae/auth/exception/AuthErrorCode.java +++ b/backend/src/main/java/com/zzang/chongdae/auth/exception/AuthErrorCode.java @@ -13,7 +13,9 @@ public enum AuthErrorCode implements ErrorResponse { INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."), EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."), INVALID_PASSWORD(HttpStatus.NOT_FOUND, "가입하지 않은 회원입니다."), - DUPLICATED_MEMBER(HttpStatus.CONFLICT, "이미 가입한 회원입니다."); + DUPLICATED_MEMBER(HttpStatus.CONFLICT, "이미 가입한 회원입니다."), + CLIENT_TIME_OUT(HttpStatus.INTERNAL_SERVER_ERROR, "시간이 초과되어 로그인 요청에 실패했습니다."), + KAKAO_LOGIN_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "카카오 로그인에 실패했습니다."); private final HttpStatus status; private final String message; diff --git a/backend/src/main/java/com/zzang/chongdae/auth/exception/KakaoLoginExceptionHandler.java b/backend/src/main/java/com/zzang/chongdae/auth/exception/KakaoLoginExceptionHandler.java new file mode 100644 index 000000000..b9a580db2 --- /dev/null +++ b/backend/src/main/java/com/zzang/chongdae/auth/exception/KakaoLoginExceptionHandler.java @@ -0,0 +1,32 @@ +package com.zzang.chongdae.auth.exception; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zzang.chongdae.auth.service.dto.KakaoLoginFailResponseDto; +import com.zzang.chongdae.global.exception.MarketException; +import java.io.IOException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.ResponseErrorHandler; + +@Slf4j +public class KakaoLoginExceptionHandler implements ResponseErrorHandler { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public boolean hasError(ClientHttpResponse response) throws IOException { + return response.getStatusCode().isError(); + } + + @Override + public void handleError(ClientHttpResponse response) throws IOException { + throw new MarketException(getKakaoLoginErrorCode(response)); + } + + private AuthErrorCode getKakaoLoginErrorCode(final ClientHttpResponse response) throws IOException { + KakaoLoginFailResponseDto kakaoLoginFailResponse = objectMapper.readValue( + response.getBody(), KakaoLoginFailResponseDto.class); + log.error(kakaoLoginFailResponse.toString()); + return AuthErrorCode.KAKAO_LOGIN_INTERNAL_SERVER_ERROR; + } +} diff --git a/backend/src/main/java/com/zzang/chongdae/auth/service/AuthClient.java b/backend/src/main/java/com/zzang/chongdae/auth/service/AuthClient.java new file mode 100644 index 000000000..af9ad6166 --- /dev/null +++ b/backend/src/main/java/com/zzang/chongdae/auth/service/AuthClient.java @@ -0,0 +1,31 @@ +package com.zzang.chongdae.auth.service; + +import com.zzang.chongdae.auth.exception.KakaoLoginExceptionHandler; +import com.zzang.chongdae.auth.service.dto.KakaoLoginResponseDto; +import com.zzang.chongdae.member.domain.AuthProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.web.client.RestClient; + +@RequiredArgsConstructor +public class AuthClient { + + private static final String BEARER_HEADER_FORMAT = "Bearer %s"; + private static final String GET_KAKAO_USER_INFO_URI = "https://kapi.kakao.com/v2/user/me"; + + private final RestClient restClient; + + public String getKakaoUserInfo(String accessToken) { + KakaoLoginResponseDto responseDto = restClient.get() + .uri(GET_KAKAO_USER_INFO_URI) + .header(HttpHeaders.AUTHORIZATION, createAuthorization(accessToken)) + .retrieve() + .onStatus(new KakaoLoginExceptionHandler()) + .body(KakaoLoginResponseDto.class); + return AuthProvider.KAKAO.buildLoginId(responseDto.id().toString()); // TODO: NPE 처리 고려하기 + } + + private String createAuthorization(String accessToken) { + return BEARER_HEADER_FORMAT.formatted(accessToken); + } +} diff --git a/backend/src/main/java/com/zzang/chongdae/auth/service/AuthService.java b/backend/src/main/java/com/zzang/chongdae/auth/service/AuthService.java index ad717ad7b..6de7b4d10 100644 --- a/backend/src/main/java/com/zzang/chongdae/auth/service/AuthService.java +++ b/backend/src/main/java/com/zzang/chongdae/auth/service/AuthService.java @@ -1,19 +1,18 @@ package com.zzang.chongdae.auth.service; -import com.zzang.chongdae.auth.exception.AuthErrorCode; import com.zzang.chongdae.auth.service.dto.AuthInfoDto; import com.zzang.chongdae.auth.service.dto.AuthMemberDto; import com.zzang.chongdae.auth.service.dto.AuthTokenDto; -import com.zzang.chongdae.auth.service.dto.LoginRequest; -import com.zzang.chongdae.auth.service.dto.SignupRequest; +import com.zzang.chongdae.auth.service.dto.KakaoLoginRequest; import com.zzang.chongdae.global.exception.MarketException; +import com.zzang.chongdae.member.domain.AuthProvider; import com.zzang.chongdae.member.exception.MemberErrorCode; import com.zzang.chongdae.member.repository.MemberRepository; import com.zzang.chongdae.member.repository.entity.MemberEntity; import com.zzang.chongdae.member.service.NicknameGenerator; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service @@ -23,26 +22,23 @@ public class AuthService { private final PasswordEncoder passwordEncoder; private final JwtTokenProvider jwtTokenProvider; private final NicknameGenerator nickNameGenerator; - - public AuthInfoDto login(LoginRequest request) { - String password = passwordEncoder.encode(request.ci()); - MemberEntity member = memberRepository.findByPassword(password) - .orElseThrow(() -> new MarketException(AuthErrorCode.INVALID_PASSWORD)); - return createTokenByMember(member); + private final AuthClient authClient; + + public AuthInfoDto kakaoLogin(KakaoLoginRequest request) { + String loginId = authClient.getKakaoUserInfo(request.accessToken()); + AuthProvider provider = AuthProvider.KAKAO; + MemberEntity member = memberRepository.findByLoginId(loginId) + .orElseGet(() -> signup(provider, loginId)); + return login(member); } - @Transactional - public AuthInfoDto signup(SignupRequest request) { - String password = passwordEncoder.encode(request.ci()); - if (memberRepository.existsByPassword(password)) { - throw new MarketException(AuthErrorCode.DUPLICATED_MEMBER); - } - MemberEntity member = new MemberEntity(nickNameGenerator.generate(), password); - MemberEntity savedMember = memberRepository.save(member); - return createTokenByMember(savedMember); + private MemberEntity signup(AuthProvider provider, String loginId) { + String password = passwordEncoder.encode(UUID.randomUUID().toString()); + MemberEntity member = new MemberEntity(nickNameGenerator.generate(), provider, loginId, password); + return memberRepository.save(member); } - private AuthInfoDto createTokenByMember(MemberEntity member) { + private AuthInfoDto login(MemberEntity member) { AuthMemberDto authMember = new AuthMemberDto(member); AuthTokenDto authToken = jwtTokenProvider.createAuthToken(member.getId().toString()); return new AuthInfoDto(authMember, authToken); diff --git a/backend/src/main/java/com/zzang/chongdae/auth/service/dto/KakaoLoginFailResponseDto.java b/backend/src/main/java/com/zzang/chongdae/auth/service/dto/KakaoLoginFailResponseDto.java new file mode 100644 index 000000000..50fea23e2 --- /dev/null +++ b/backend/src/main/java/com/zzang/chongdae/auth/service/dto/KakaoLoginFailResponseDto.java @@ -0,0 +1,5 @@ +package com.zzang.chongdae.auth.service.dto; + +public record KakaoLoginFailResponseDto(String msg, + Long code) { +} diff --git a/backend/src/main/java/com/zzang/chongdae/auth/service/dto/KakaoLoginRequest.java b/backend/src/main/java/com/zzang/chongdae/auth/service/dto/KakaoLoginRequest.java new file mode 100644 index 000000000..769e67393 --- /dev/null +++ b/backend/src/main/java/com/zzang/chongdae/auth/service/dto/KakaoLoginRequest.java @@ -0,0 +1,4 @@ +package com.zzang.chongdae.auth.service.dto; + +public record KakaoLoginRequest(String accessToken) { +} diff --git a/backend/src/main/java/com/zzang/chongdae/auth/service/dto/KakaoLoginResponseDto.java b/backend/src/main/java/com/zzang/chongdae/auth/service/dto/KakaoLoginResponseDto.java new file mode 100644 index 000000000..e85afac6c --- /dev/null +++ b/backend/src/main/java/com/zzang/chongdae/auth/service/dto/KakaoLoginResponseDto.java @@ -0,0 +1,4 @@ +package com.zzang.chongdae.auth.service.dto; + +public record KakaoLoginResponseDto(Long id) { +} diff --git a/backend/src/main/java/com/zzang/chongdae/auth/service/dto/LoginRequest.java b/backend/src/main/java/com/zzang/chongdae/auth/service/dto/LoginRequest.java deleted file mode 100644 index b48937ab0..000000000 --- a/backend/src/main/java/com/zzang/chongdae/auth/service/dto/LoginRequest.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.zzang.chongdae.auth.service.dto; - -import jakarta.validation.constraints.NotNull; - -public record LoginRequest(@NotNull String ci) { -} diff --git a/backend/src/main/java/com/zzang/chongdae/auth/service/dto/SignupRequest.java b/backend/src/main/java/com/zzang/chongdae/auth/service/dto/SignupRequest.java deleted file mode 100644 index e4b03e1fa..000000000 --- a/backend/src/main/java/com/zzang/chongdae/auth/service/dto/SignupRequest.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.zzang.chongdae.auth.service.dto; - -import jakarta.validation.constraints.NotNull; - -public record SignupRequest(@NotNull String ci) { -} diff --git a/backend/src/main/java/com/zzang/chongdae/auth/service/dto/SignupResponse.java b/backend/src/main/java/com/zzang/chongdae/auth/service/dto/SignupResponse.java deleted file mode 100644 index 9911ff562..000000000 --- a/backend/src/main/java/com/zzang/chongdae/auth/service/dto/SignupResponse.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.zzang.chongdae.auth.service.dto; - -public record SignupResponse(Long memberId, String nickname) { - - public SignupResponse(AuthMemberDto authMember) { - this(authMember.id(), authMember.nickname()); - } -} diff --git a/backend/src/main/java/com/zzang/chongdae/comment/controller/CommentController.java b/backend/src/main/java/com/zzang/chongdae/comment/controller/CommentController.java index 67c485f27..287baff8d 100644 --- a/backend/src/main/java/com/zzang/chongdae/comment/controller/CommentController.java +++ b/backend/src/main/java/com/zzang/chongdae/comment/controller/CommentController.java @@ -6,6 +6,7 @@ import com.zzang.chongdae.comment.service.dto.CommentRoomInfoResponse; import com.zzang.chongdae.comment.service.dto.CommentRoomStatusResponse; import com.zzang.chongdae.comment.service.dto.CommentSaveRequest; +import com.zzang.chongdae.logging.config.LoggingMasked; import com.zzang.chongdae.member.repository.entity.MemberEntity; import jakarta.validation.Valid; import java.net.URI; @@ -55,6 +56,7 @@ public ResponseEntity updateCommentRoomStatus( return ResponseEntity.ok(response); } + @LoggingMasked @GetMapping("/comments/messages") public ResponseEntity getAllComment( @RequestParam(value = "offering-id") Long offeringId, diff --git a/backend/src/main/java/com/zzang/chongdae/logging/config/LoggingInterceptor.java b/backend/src/main/java/com/zzang/chongdae/logging/config/LoggingInterceptor.java index 34be43ad4..fbb32f64b 100644 --- a/backend/src/main/java/com/zzang/chongdae/logging/config/LoggingInterceptor.java +++ b/backend/src/main/java/com/zzang/chongdae/logging/config/LoggingInterceptor.java @@ -8,9 +8,11 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import java.lang.reflect.Method; import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; @Slf4j @@ -28,6 +30,15 @@ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws IOException { + + if (!(handler instanceof HandlerMethod handlerMethod)) { + return; + } + Method method = handlerMethod.getMethod(); + if (method.isAnnotationPresent(LoggingMasked.class)) { + return; + } + if (isMultipart(request)) { return; } diff --git a/backend/src/main/java/com/zzang/chongdae/logging/config/LoggingMasked.java b/backend/src/main/java/com/zzang/chongdae/logging/config/LoggingMasked.java new file mode 100644 index 000000000..7df944677 --- /dev/null +++ b/backend/src/main/java/com/zzang/chongdae/logging/config/LoggingMasked.java @@ -0,0 +1,11 @@ +package com.zzang.chongdae.logging.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface LoggingMasked { +} diff --git a/backend/src/main/java/com/zzang/chongdae/member/domain/AuthProvider.java b/backend/src/main/java/com/zzang/chongdae/member/domain/AuthProvider.java new file mode 100644 index 000000000..d2bd5066c --- /dev/null +++ b/backend/src/main/java/com/zzang/chongdae/member/domain/AuthProvider.java @@ -0,0 +1,10 @@ +package com.zzang.chongdae.member.domain; + +public enum AuthProvider { + + KAKAO; + + public String buildLoginId(String loginId) { + return this.name() + loginId; + } +} diff --git a/backend/src/main/java/com/zzang/chongdae/member/repository/MemberRepository.java b/backend/src/main/java/com/zzang/chongdae/member/repository/MemberRepository.java index 34afc9c85..d2bf2ec0d 100644 --- a/backend/src/main/java/com/zzang/chongdae/member/repository/MemberRepository.java +++ b/backend/src/main/java/com/zzang/chongdae/member/repository/MemberRepository.java @@ -5,9 +5,12 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository { + Optional findByPassword(String password); boolean existsByPassword(String password); boolean existsByNickname(String nickname); + + Optional findByLoginId(String loginId); } diff --git a/backend/src/main/java/com/zzang/chongdae/member/repository/entity/MemberEntity.java b/backend/src/main/java/com/zzang/chongdae/member/repository/entity/MemberEntity.java index e840affb5..03fe675bf 100644 --- a/backend/src/main/java/com/zzang/chongdae/member/repository/entity/MemberEntity.java +++ b/backend/src/main/java/com/zzang/chongdae/member/repository/entity/MemberEntity.java @@ -1,8 +1,11 @@ package com.zzang.chongdae.member.repository.entity; import com.zzang.chongdae.global.repository.entity.BaseTimeEntity; +import com.zzang.chongdae.member.domain.AuthProvider; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -30,11 +33,18 @@ public class MemberEntity extends BaseTimeEntity { @Column(unique = true, length = 10) private String nickname; + @Enumerated(EnumType.STRING) + private AuthProvider provider; + @NotNull - private String password; + @Column(unique = true) + private String loginId; - public MemberEntity(String nickname, String password) { - this(null, nickname, password); + @NotNull + private String password; // TODO: 일반 로그인 들어올 시 salt 추가 + + public MemberEntity(String nickname, AuthProvider provider, String loginId, String password) { + this(null, nickname, provider, loginId, password); } public boolean isSame(MemberEntity other) { diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index fff0c18ed..4783f950a 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -56,3 +56,7 @@ logging: file-name-pattern: /logs/backend.%d{yyyy-MM-dd}.%i.log max-file-size: 100MB max-history: 14 + +auth: + connect-timeout-length: 3s + read-timeout-length: 3s diff --git a/backend/src/main/resources/data.sql b/backend/src/main/resources/data.sql index 8575bbf43..5a39be84b 100644 --- a/backend/src/main/resources/data.sql +++ b/backend/src/main/resources/data.sql @@ -1,15 +1,18 @@ -INSERT INTO MEMBER (NICKNAME, CREATED_AT, UPDATED_AT, PASSWORD) -VALUES ('dora', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'PtgtJCnn307FyCBvRprsy+42rX7dg00qVLWkPbl2Ag0='), - ('poke', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'XimRQY0Y2avPH6KxGK4ZOXB4+MT3Sfb605ZEPidVNpQ='), - ('mason', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'WBmuFn7FSb5jG03SKBB0K7MNk0mNg9FLPoHyTbi4Tl0='), - ('ever', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'UhN2JlsRhvNn7XY2WYlfDwI9/d/XoRvr8Ls7tbeYZWg='), - ('alsong', '2024-07-15 00:00:00', '2024-07-15 00:00:00', '+qY3Pnqyjj9amVGZ1Bu63iJX6cpon7kQiIvqAG0ExkE='), - ('seogi', '2024-07-15 00:00:00', '2024-07-15 00:00:00', '0CWUdyVQ1TP+GGlI9W2d5Gao/5HgT0MSeIwald0Qcsw='), - ('chaechae', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'WCkwnMjy/yW6odwkADguEIcHjFVELq+JLy+WeojvJ88='), - ('tommy', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'FDCoOHIo4OPB8wBuYLLf1b3ZRTBSzNll45s8nmyQjdQ='), - ('james', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'Z2XFMZIARj17tEES+PIavOqFkT4xl5eIKPvQdtHTb00='), - ('jason', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'VQaSJjY9GBmpLaCga6sCpKYFBEC3G8VNva5seJzDwvg='), - ('lisa', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'MaG6sq0cGDEXwmZ++LWXXtIcJ6QqQgTwyIll8xExJdk='); +INSERT INTO MEMBER (NICKNAME, CREATED_AT, UPDATED_AT, PASSWORD, LOGIN_ID, PROVIDER) +VALUES ('dora', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'PtgtJCnn307FyCBvRprsy+42rX7dg00qVLWkPbl2Ag0=', + 'KAKAO_dora1234', 'KAKAO'), + ('poke', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'XimRQY0Y2avPH6KxGK4ZOXB4+MT3Sfb605ZEPidVNpQ=', + 'KAKAO_poke1234', 'KAKAO'), + ('mason', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'WBmuFn7FSb5jG03SKBB0K7MNk0mNg9FLPoHyTbi4Tl0=', + 'KAKAO_mason1234', 'KAKAO'), + ('ever', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'UhN2JlsRhvNn7XY2WYlfDwI9/d/XoRvr8Ls7tbeYZWg=', + 'KAKAO_ever1234', 'KAKAO'), + ('alsong', '2024-07-15 00:00:00', '2024-07-15 00:00:00', '+qY3Pnqyjj9amVGZ1Bu63iJX6cpon7kQiIvqAG0ExkE=', + 'KAKAO_alsong1234', 'KAKAO'), + ('seogi', '2024-07-15 00:00:00', '2024-07-15 00:00:00', '0CWUdyVQ1TP+GGlI9W2d5Gao/5HgT0MSeIwald0Qcsw=', + 'KAKAO_seogi1234', 'KAKAO'), + ('chaechae', '2024-07-15 00:00:00', '2024-07-15 00:00:00', 'WCkwnMjy/yW6odwkADguEIcHjFVELq+JLy+WeojvJ88=', + 'KAKAO_chaechae1234', 'KAKAO'); INSERT INTO OFFERING (TOTAL_COUNT, CURRENT_COUNT, TOTAL_PRICE, ORIGIN_PRICE, DISCOUNT_RATE, CREATED_AT, UPDATED_AT, MEMBER_ID, MEETING_DATE, DESCRIPTION, MEETING_ADDRESS, MEETING_ADDRESS_DONG, diff --git a/backend/src/test/java/com/zzang/chongdae/auth/integration/AuthIntegrationTest.java b/backend/src/test/java/com/zzang/chongdae/auth/integration/AuthIntegrationTest.java index 5391e11da..6f8cc7ddf 100644 --- a/backend/src/test/java/com/zzang/chongdae/auth/integration/AuthIntegrationTest.java +++ b/backend/src/test/java/com/zzang/chongdae/auth/integration/AuthIntegrationTest.java @@ -5,13 +5,14 @@ import static com.epages.restdocs.apispec.ResourceSnippetParameters.builder; import static com.epages.restdocs.apispec.Schema.schema; import static io.restassured.RestAssured.given; +import static org.mockito.ArgumentMatchers.any; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document; import com.epages.restdocs.apispec.HeaderDescriptorWithType; import com.epages.restdocs.apispec.ResourceSnippetParameters; -import com.zzang.chongdae.auth.service.dto.LoginRequest; -import com.zzang.chongdae.auth.service.dto.SignupRequest; +import com.zzang.chongdae.auth.service.AuthClient; +import com.zzang.chongdae.auth.service.dto.KakaoLoginRequest; import com.zzang.chongdae.global.integration.IntegrationTest; import com.zzang.chongdae.member.repository.entity.MemberEntity; import io.jsonwebtoken.Jwts; @@ -24,17 +25,22 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.BDDMockito; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.restdocs.payload.FieldDescriptor; class AuthIntegrationTest extends IntegrationTest { - @DisplayName("로그인") + @MockBean + AuthClient authClient; + + @DisplayName("카카오 로그인") @Nested - class Login { + class KakaoLogin { List requestDescriptors = List.of( - fieldWithPath("ci").description("회원 식별자 인증 정보") + fieldWithPath("accessToken").description("카카오 인증 토큰") ); List responseDescriptors = List.of( fieldWithPath("memberId").description("회원 id"), @@ -47,108 +53,39 @@ class Login { """) ); ResourceSnippetParameters successSnippets = builder() - .summary("회원 로그인") - .description("회원 식별자 인증 정보로 로그인 합니다.") + .summary("카카오 로그인") + .description("카카오 인증 토큰으로 로그인 합니다.") .requestFields(requestDescriptors) .responseFields(responseDescriptors) .responseHeaders(responseHeaderDescriptors) - .requestSchema(schema("LonginRequest")) - .responseSchema(schema("LoginResponse")) + .requestSchema(schema("KakaoLonginRequest")) + .responseSchema(schema("KakaoLoginResponse")) .build(); MemberEntity member; @BeforeEach void setUp() { - member = memberFixture.createMember(); + member = memberFixture.createMember("dora"); + BDDMockito.given(authClient.getKakaoUserInfo(any())) + .willReturn(member.getLoginId()); } - @DisplayName("회원 식별자 인증 정보로 로그인한다.") + @DisplayName("카카오 인증 토큰으로 로그인한다.") @Test void should_loginSuccess_when_givenMemberCI() { - LoginRequest request = new LoginRequest( - "dora1234" - ); - - given(spec).log().all() - .filter(document("login-success", resource(successSnippets))) - .contentType(ContentType.JSON) - .body(request) - .when().post("/auth/login") - .then().log().all() - .statusCode(200); - } - } - - @DisplayName("회원가입") - @Nested - class Signup { - List requestDescriptors = List.of( - fieldWithPath("ci").description("회원 식별자 인증 정보") - ); - List responseDescriptors = List.of( - fieldWithPath("memberId").description("회원 id"), - fieldWithPath("nickname").description("닉네임") - ); - List responseHeaderDescriptors = List.of( - headerWithName("Set-Cookie").description(""" - access_token=a.b.c; Path=/; HttpOnly \n - refresh_token=a.b.c; Path=/; HttpOnly - """) - ); - ResourceSnippetParameters successSnippets = builder() - .summary("회원 가입") - .description("회원 식별자 인증 정보로 가입합니다.") - .requestFields(requestDescriptors) - .responseFields(responseDescriptors) - .responseHeaders(responseHeaderDescriptors) - .requestSchema(schema("SignupRequest")) - .responseSchema(schema("SignupResponse")) - .build(); - ResourceSnippetParameters failSnippets = builder() - .responseFields(failResponseDescriptors) - .requestSchema(schema("SignupFailRequest")) - .responseSchema(schema("SignupFailResponse")) - .build(); - - MemberEntity member; - - @BeforeEach - void setUp() { - member = memberFixture.createMember(); - } - - @DisplayName("회원 식별자 인증 정보로 회원가입 한다.") - @Test - void should_signupSuccess_when_givenMemberCI() { - SignupRequest request = new SignupRequest( - "poke1234" + KakaoLoginRequest request = new KakaoLoginRequest( + "whatever" ); given(spec).log().all() - .filter(document("signup-success", resource(successSnippets))) + .filter(document("kakao-login-success", resource(successSnippets))) .contentType(ContentType.JSON) .body(request) - .when().post("/auth/signup") + .when().post("/auth/login/kakao") .then().log().all() .statusCode(200); } - - @DisplayName("이미 가입된 회원이 있으면 예외가 발생한다.") - @Test - void should_throwException_when_givenAlreadyExistMember() { - SignupRequest request = new SignupRequest( - "dora1234" - ); - - given(spec).log().all() - .filter(document("signup-fail-duplicated-member", resource(failSnippets))) - .contentType(ContentType.JSON) - .body(request) - .when().post("/auth/signup") - .then().log().all() - .statusCode(409); - } } @DisplayName("토큰 재발급") @@ -182,7 +119,7 @@ class Refresh { @BeforeEach void setUp() { - member = memberFixture.createMember(); + member = memberFixture.createMember("dora"); now = Date.from(clock.instant()); } @@ -192,7 +129,7 @@ void should_refreshSuccess_when_givenRefreshToken() { given(spec).log().all() .filter(document("refresh-success", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .when().post("/auth/refresh") .then().log().all() .statusCode(200); diff --git a/backend/src/test/java/com/zzang/chongdae/comment/integration/CommentIntegrationTest.java b/backend/src/test/java/com/zzang/chongdae/comment/integration/CommentIntegrationTest.java index be1ae47ae..16171d61e 100644 --- a/backend/src/test/java/com/zzang/chongdae/comment/integration/CommentIntegrationTest.java +++ b/backend/src/test/java/com/zzang/chongdae/comment/integration/CommentIntegrationTest.java @@ -50,7 +50,7 @@ class SaveComment { @BeforeEach void setUp() { - member = memberFixture.createMember(); + member = memberFixture.createMember("dora"); offering = offeringFixture.createOffering(member); offeringMemberFixture.createProposer(member, offering); } @@ -65,7 +65,7 @@ void should_saveCommentSuccess_when_givenCommentSaveRequest() { RestAssured.given(spec).log().all() .filter(document("save-comment-success", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .contentType(ContentType.JSON) .body(request) .when().post("/comments") @@ -83,7 +83,7 @@ void should_throwException_when_emptyValue() { RestAssured.given(spec).log().all() .filter(document("save-comment-fail-request-with-null", resource(failSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .contentType(ContentType.JSON) .body(request) .when().post("/comments") @@ -101,7 +101,7 @@ void should_throwException_when_longContent() { RestAssured.given(spec).log().all() .filter(document("save-comment-fail-request-with-long-content", resource(failSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .contentType(ContentType.JSON) .body(request) .when().post("/comments") @@ -119,7 +119,7 @@ void should_throwException_when_invalidOffering() { RestAssured.given(spec).log().all() .filter(document("save-comment-fail-invalid-offering", resource(failSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .contentType(ContentType.JSON) .body(request) .when().post("/comments") @@ -151,7 +151,7 @@ class GetAllCommentRoom { @BeforeEach void setUp() { - member = memberFixture.createMember(); + member = memberFixture.createMember("dora"); offering = offeringFixture.createOffering(member); offeringMemberFixture.createProposer(member, offering); MemberEntity participant = memberFixture.createMember("참여자"); @@ -164,7 +164,7 @@ void setUp() { void should_responseAllCommentRoom_when_givenMemberId() { RestAssured.given(spec).log().all() .filter(document("get-all-comment-room-success", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .when().get("/comments") .then().log().all() .statusCode(200); @@ -218,7 +218,7 @@ void setUp() { void should_responseCommentRoomInfo_when_givenOfferingId() { given(spec).log().all() .filter(document("get-comment-room-info-success", resource(successSnippets))) - .cookies(cookieProvider.createCookiesWithCi("ever5678")) + .cookies(cookieProvider.createCookiesWithMember(member)) .queryParam("offering-id", offering.getId()) .when().get("/comments/info") .then().log().all() @@ -230,7 +230,7 @@ void should_responseCommentRoomInfo_when_givenOfferingId() { void should_throwException_when_invalidOffering() { given(spec).log().all() .filter(document("get-comment-room-info-fail-invalid-offering", resource(failSnippets))) - .cookies(cookieProvider.createCookiesWithCi("ever5678")) + .cookies(cookieProvider.createCookiesWithMember(member)) .queryParam("offering-id", offering.getId() + 100) .when().get("/comments/info") .then().log().all() @@ -242,7 +242,7 @@ void should_throwException_when_invalidOffering() { void should_throwException_when_invalidMember() { given(spec).log().all() .filter(document("get-comment-room-info-fail-invalid-member", resource(failSnippets))) - .cookies(cookieProvider.createCookiesWithCi("invalid5678")) + .cookies(cookieProvider.createCookiesWithMember(invalidMember)) .queryParam("offering-id", offering.getId()) .when().get("/comments/info") .then().log().all() @@ -292,14 +292,14 @@ void setUp() { void should_updateStatus_when_givenOfferingIdAndMemberId() { RestAssured.given(spec).log().all() .filter(document("update-comment-room-status-success", resource(successSnippets))) - .cookies(cookieProvider.createCookiesWithCi("ever5678")) + .cookies(cookieProvider.createCookiesWithMember(member)) .queryParam("offering-id", offering.getId()) .when().patch("/comments/status") .then().log().all() .statusCode(200); RestAssured.given().log().all() - .cookies(cookieProvider.createCookiesWithCi("ever5678")) + .cookies(cookieProvider.createCookiesWithMember(member)) .queryParam("offering-id", offering.getId()) .when().get("/comments/info") .then().log().all() @@ -312,7 +312,7 @@ void should_updateStatus_when_givenOfferingIdAndMemberId() { void should_throwException_when_invalidOffering() { RestAssured.given(spec).log().all() .filter(document("update-comment-room-status-fail-invalid-offering", resource(failSnippets))) - .cookies(cookieProvider.createCookiesWithCi("ever5678")) + .cookies(cookieProvider.createCookiesWithMember(member)) .queryParam("offering-id", offering.getId() + 100) .when().patch("/comments/status") .then().log().all() @@ -324,7 +324,7 @@ void should_throwException_when_invalidOffering() { void should_throwException_when_invalidMember() { RestAssured.given(spec).log().all() .filter(document("update-comment-room-status-fail-invalid-member", resource(failSnippets))) - .cookies(cookieProvider.createCookiesWithCi("invalid5678")) + .cookies(cookieProvider.createCookiesWithMember(invalidMember)) .queryParam("offering-id", offering.getId() + 100) .when().patch("/comments/status") .then().log().all() @@ -367,7 +367,7 @@ class GetAllComment { @BeforeEach void setUp() { - member = memberFixture.createMember(); + member = memberFixture.createMember("dora"); offering = offeringFixture.createOffering(member); offeringMemberFixture.createProposer(member, offering); commentFixture.createComment(member, offering); @@ -378,7 +378,7 @@ void setUp() { void should_responseAllComment_when_givenOfferingIdAndMemberId() { RestAssured.given(spec).log().all() .filter(document("get-all-comment-success", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .queryParam("offering-id", offering.getId()) .when().get("/comments/messages") .then().log().all() @@ -390,7 +390,7 @@ void should_responseAllComment_when_givenOfferingIdAndMemberId() { void should_throwException_when_invalidOffering() { RestAssured.given(spec).log().all() .filter(document("get-all-comment-fail-invalid-offering", resource(failSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .queryParam("offering-id", offering.getId() + 100) .when().get("/comments/messages") .then().log().all() diff --git a/backend/src/test/java/com/zzang/chongdae/global/domain/MemberFixture.java b/backend/src/test/java/com/zzang/chongdae/global/domain/MemberFixture.java index 6f28b88e3..432193cff 100644 --- a/backend/src/test/java/com/zzang/chongdae/global/domain/MemberFixture.java +++ b/backend/src/test/java/com/zzang/chongdae/global/domain/MemberFixture.java @@ -1,6 +1,6 @@ package com.zzang.chongdae.global.domain; -import com.zzang.chongdae.auth.service.PasswordEncoder; +import com.zzang.chongdae.member.domain.AuthProvider; import com.zzang.chongdae.member.repository.MemberRepository; import com.zzang.chongdae.member.repository.entity.MemberEntity; import org.springframework.beans.factory.annotation.Autowired; @@ -12,16 +12,12 @@ public class MemberFixture { @Autowired private MemberRepository memberRepository; - @Autowired - private PasswordEncoder passwordEncoder; - - public MemberEntity createMember() { - MemberEntity member = new MemberEntity("dora", passwordEncoder.encode("dora1234")); - return memberRepository.save(member); - } - public MemberEntity createMember(String nickname) { - MemberEntity member = new MemberEntity(nickname, passwordEncoder.encode(nickname + "5678")); + MemberEntity member = new MemberEntity( + nickname, + AuthProvider.KAKAO, + AuthProvider.KAKAO.buildLoginId(nickname), + "1234"); return memberRepository.save(member); } } diff --git a/backend/src/test/java/com/zzang/chongdae/global/helper/CookieProvider.java b/backend/src/test/java/com/zzang/chongdae/global/helper/CookieProvider.java index fc59534e6..2533d43eb 100644 --- a/backend/src/test/java/com/zzang/chongdae/global/helper/CookieProvider.java +++ b/backend/src/test/java/com/zzang/chongdae/global/helper/CookieProvider.java @@ -1,30 +1,28 @@ package com.zzang.chongdae.global.helper; -import com.zzang.chongdae.auth.service.dto.LoginRequest; -import io.restassured.RestAssured; +import com.zzang.chongdae.auth.service.JwtTokenProvider; +import com.zzang.chongdae.auth.service.dto.AuthTokenDto; +import com.zzang.chongdae.member.repository.entity.MemberEntity; +import io.restassured.http.Cookie; import io.restassured.http.Cookies; -import org.springframework.http.MediaType; import org.springframework.stereotype.Component; - @Component public class CookieProvider { - public Cookies createCookies() { - return RestAssured.given().log().all() - .contentType(MediaType.APPLICATION_JSON_VALUE) - .body(new LoginRequest("dora1234")) - .when().post("/auth/login") - .then().log().all() - .extract().detailedCookies(); + private static final String ACCESS_TOKEN_COOKIE_NAME = "access_token"; + private static final String REFRESH_TOKEN_COOKIE_NAME = "refresh_token"; + + private final JwtTokenProvider jwtTokenProvider; + + public CookieProvider(JwtTokenProvider jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; } - public Cookies createCookiesWithCi(String ci) { - return RestAssured.given().log().all() - .contentType(MediaType.APPLICATION_JSON_VALUE) - .body(new LoginRequest(ci)) - .when().post("/auth/login") - .then().log().all() - .extract().detailedCookies(); + public Cookies createCookiesWithMember(MemberEntity member) { + AuthTokenDto authToken = jwtTokenProvider.createAuthToken(member.getId().toString()); + Cookie accessTokenCookie = new Cookie.Builder(ACCESS_TOKEN_COOKIE_NAME, authToken.accessToken()).build(); + Cookie refreshTokenCookie = new Cookie.Builder(REFRESH_TOKEN_COOKIE_NAME, authToken.refreshToken()).build(); + return new Cookies(accessTokenCookie, refreshTokenCookie); } } diff --git a/backend/src/test/java/com/zzang/chongdae/offering/integration/OfferingIntegrationTest.java b/backend/src/test/java/com/zzang/chongdae/offering/integration/OfferingIntegrationTest.java index d1f7b32a0..1db7b21a8 100644 --- a/backend/src/test/java/com/zzang/chongdae/offering/integration/OfferingIntegrationTest.java +++ b/backend/src/test/java/com/zzang/chongdae/offering/integration/OfferingIntegrationTest.java @@ -82,9 +82,11 @@ class GetOfferingDetail { .responseSchema(schema("OfferingDetailFailResponse")) .build(); + MemberEntity proposer; + @BeforeEach void setUp() { - MemberEntity proposer = memberFixture.createMember(); + proposer = memberFixture.createMember("dora"); OfferingEntity offering = offeringFixture.createOffering(proposer); offeringMemberFixture.createProposer(proposer, offering); } @@ -94,7 +96,7 @@ void setUp() { void should_responseOfferingDetail_when_givenOfferingId() { given(spec).log().all() .filter(document("get-offering-detail-success", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(proposer)) .pathParam("offering-id", 1) .when().get("/offerings/{offering-id}/detail") .then().log().all() @@ -106,7 +108,7 @@ void should_responseOfferingDetail_when_givenOfferingId() { void should_throwException_when_invalidOffering() { given(spec).log().all() .filter(document("get-offering-detail-fail-invalid-offering", resource(failSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(proposer)) .pathParam("offering-id", 100) .when().get("/offerings/{offering-id}/detail") .then().log().all() @@ -150,9 +152,11 @@ class GetOffering { .responseSchema(schema("OfferingFailResponse")) .build(); + MemberEntity proposer; + @BeforeEach void setUp() { - MemberEntity proposer = memberFixture.createMember(); + proposer = memberFixture.createMember("dora"); OfferingEntity offering = offeringFixture.createOffering(proposer); offeringMemberFixture.createProposer(proposer, offering); } @@ -162,7 +166,7 @@ void setUp() { void should_responseOffering_when_givenOfferingId() { given(spec).log().all() .filter(document("get-offering-success", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(proposer)) .pathParam("offering-id", 1) .when().get("/offerings/{offering-id}") .then().log().all() @@ -174,7 +178,7 @@ void should_responseOffering_when_givenOfferingId() { void should_throwException_when_invalidOffering() { given(spec).log().all() .filter(document("get-offering-fail-invalid-offering", resource(failSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(proposer)) .pathParam("offering-id", 100) .when().get("/offerings/{offering-id}") .then().log().all() @@ -215,9 +219,11 @@ class GetAllOffering { .responseSchema(schema("OfferingAllSuccessResponse")) .build(); + MemberEntity member; + @BeforeEach void setUp() { - MemberEntity member = memberFixture.createMember(); + member = memberFixture.createMember("dora"); for (int i = 0; i < 11; i++) { offeringFixture.createOffering(member); } @@ -228,7 +234,7 @@ void setUp() { void should_responseAllOffering_when_givenPageInfo() { given(spec).log().all() .filter(document("get-all-offering-success", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .queryParam("filter", "RECENT") .queryParam("search", "title") .queryParam("last-id", 10) @@ -267,9 +273,11 @@ class GetOfferingMeeting { .responseSchema(schema("OfferingMeetingFailResponse")) .build(); + MemberEntity member; + @BeforeEach void setUp() { - MemberEntity member = memberFixture.createMember(); + member = memberFixture.createMember("dora"); OfferingEntity offering = offeringFixture.createOffering(member); offeringMemberFixture.createProposer(member, offering); } @@ -279,7 +287,7 @@ void setUp() { void should_responseOfferingMeeting_when_givenOfferingId() { given(spec).log().all() .filter(document("get-offering-meeting-success", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .pathParam("offering-id", 1) .when().get("/offerings/{offering-id}/meetings") .then().log().all() @@ -291,7 +299,7 @@ void should_responseOfferingMeeting_when_givenOfferingId() { void should_throwException_when_invalidOffering() { given(spec).log().all() .filter(document("get-offering-meeting-fail-invalid-offering", resource(failSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .pathParam("offering-id", 100) .when().get("/offerings/{offering-id}/meetings") .then().log().all() @@ -361,7 +369,7 @@ void should_updateSuccess_when_givenOfferingIdAndInfo() { given(spec).log().all() .filter(document("update-offering-meeting-success", resource(successSnippets))) - .cookies(cookieProvider.createCookiesWithCi(proposer.getNickname() + "5678")) + .cookies(cookieProvider.createCookiesWithMember(proposer)) .pathParam("offering-id", offering.getId()) .contentType(ContentType.JSON) .body(request) @@ -382,7 +390,7 @@ void should_throwException_when_notProposer() { given(spec).log().all() .filter(document("update-offering-meeting-fail-not-proposer", resource(failSnippets))) - .cookies(cookieProvider.createCookiesWithCi(participant.getNickname() + "5678")) + .cookies(cookieProvider.createCookiesWithMember(participant)) .pathParam("offering-id", offering.getId()) .contentType(ContentType.JSON) .body(request) @@ -403,7 +411,7 @@ void should_throwException_when_invalidOffering() { given(spec).log().all() .filter(document("update-offering-meeting-fail-invalid-offering", resource(failSnippets))) - .cookies(cookieProvider.createCookiesWithCi(proposer.getNickname() + "5678")) + .cookies(cookieProvider.createCookiesWithMember(proposer)) .pathParam("offering-id", offering.getId() + 10000) .contentType(ContentType.JSON) .body(request) @@ -431,9 +439,11 @@ class GetAllOfferingFilter { .responseSchema(schema("OfferingFilterSuccessResponse")) .build(); + private MemberEntity member; + @BeforeEach void setUp() { - memberFixture.createMember(); + member = memberFixture.createMember("dora"); } @DisplayName("공모 id로 공모 일정 정보를 조회할 수 있다") @@ -441,7 +451,7 @@ void setUp() { void should_responseOfferingFilter_when_givenOfferingId() { given(spec).log().all() .filter(document("get-all-offering-filter-success", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .when().get("/offerings/filters") .then().log().all() .statusCode(200); @@ -484,7 +494,7 @@ class CreateOffering { @BeforeEach void setUp() { - member = memberFixture.createMember(); + member = memberFixture.createMember("dora"); } @DisplayName("공모 정보를 받아 공모를 작성 할 수 있다.") @@ -506,7 +516,7 @@ void should_createOffering_when_givenOfferingCreateRequest() { given(spec).log().all() .filter(document("create-offering-success", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .contentType(ContentType.JSON) .body(request) .when().post("/offerings") @@ -533,7 +543,7 @@ void should_createOffering_when_givenOfferingWithoutOriginPriceCreateRequest() { given(spec).log().all() .filter(document("create-offering-success-without-originPrice", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .contentType(ContentType.JSON) .body(request) .when().post("/offerings") @@ -560,7 +570,7 @@ void should_throwException_when_emptyValue() { given(spec).log().all() .filter(document("create-offering-fail-request-with-null", resource(failSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .contentType(ContentType.JSON) .body(request) .when().post("/offerings") @@ -587,7 +597,7 @@ void should_throwException_when_dividedPrice() { given(spec).log().all() .filter(document("create-offering-fail-with-invalid-originPrice", resource(failSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .contentType(ContentType.JSON) .body(request) .when().post("/offerings") @@ -614,7 +624,7 @@ void should_throwException_when_overMaximumTotalCount() { given(spec).log().all() .filter(document("create-offering-fail-with-invalid-totalCount", resource(failSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .contentType(ContentType.JSON) .body(request) .when().post("/offerings") @@ -650,9 +660,11 @@ class ExtractProductImage { .responseSchema(schema("OfferingProductImageResponse")) .build(); + private MemberEntity member; + @BeforeEach void setUp() { - memberFixture.createMember(); + member = memberFixture.createMember("dora"); } @DisplayName("상품 링크를 받아 이미지를 추출합니다.") @@ -662,7 +674,7 @@ void should_extractImageUrl_when_givenProductUrl() { given(spec).log().all() .filter(document("extract-product-image-success", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .contentType(ContentType.JSON) .body(request) .when().post("/offerings/product-images/og") @@ -677,7 +689,7 @@ void should_returnEmptyString_when_fail() { given(spec).log().all() .filter(document("extract-product-image-fail", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .contentType(ContentType.JSON) .body(request) .when().post("/offerings/product-images/og") @@ -692,7 +704,7 @@ void should_throwException_when_emptyValue() { given(spec).log().all() .filter(document("extract-product-image-fail-request-with-null", resource(failSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .contentType(ContentType.JSON) .body(request) .when().post("/offerings/product-images/og") @@ -724,10 +736,11 @@ class UploadProductImage { .build(); private File image; + private MemberEntity member; @BeforeEach void setUp() { - memberFixture.createMember(); + member = memberFixture.createMember("dora"); image = new File("src/test/resources/test-image.png"); MultipartFile mockImage = new MockMultipartFile("emptyImageFile", new byte[0]); given(storageService.uploadFile(mockImage, "path")) @@ -739,7 +752,7 @@ void setUp() { void should_uploadImageUrl_when_givenImageFile() { given(spec).log().all() .filter(document("upload-product-image-success", resource(successSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(member)) .multiPart("image", image) .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) .when().post("/offerings/product-images/s3") diff --git a/backend/src/test/java/com/zzang/chongdae/offeringmember/integration/OfferingMemberIntegrationTest.java b/backend/src/test/java/com/zzang/chongdae/offeringmember/integration/OfferingMemberIntegrationTest.java index 9938d1908..cf88123e3 100644 --- a/backend/src/test/java/com/zzang/chongdae/offeringmember/integration/OfferingMemberIntegrationTest.java +++ b/backend/src/test/java/com/zzang/chongdae/offeringmember/integration/OfferingMemberIntegrationTest.java @@ -51,7 +51,7 @@ class Participate { @BeforeEach void setUp() { - proposer = memberFixture.createMember(); + proposer = memberFixture.createMember("dora"); participant = memberFixture.createMember("poke"); offering = offeringFixture.createOffering(proposer); offeringMemberFixture.createProposer(proposer, offering); @@ -65,7 +65,7 @@ void should_participateSuccess() { ); RestAssured.given(spec).log().all() .filter(document("participate-success", resource(successSnippets))) - .cookies(cookieProvider.createCookiesWithCi("poke5678")) + .cookies(cookieProvider.createCookiesWithMember(participant)) .contentType(ContentType.JSON) .body(request) .when().post("/participations") @@ -81,7 +81,7 @@ void should_throwException_when_givenProposerParticipate() { ); RestAssured.given(spec).log().all() .filter(document("participate-fail-my-offering", resource(failSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(proposer)) .contentType(ContentType.JSON) .body(request) .when().post("/participations") @@ -97,7 +97,7 @@ void should_throwException_when_invalidOffering() { ); RestAssured.given(spec).log().all() .filter(document("participate-fail-invalid-offering", resource(failSnippets))) - .cookies(cookieProvider.createCookiesWithCi("poke5678")) + .cookies(cookieProvider.createCookiesWithMember(participant)) .contentType(ContentType.JSON) .body(request) .when().post("/participations") @@ -113,7 +113,7 @@ void should_throwException_when_emptyValue() { ); RestAssured.given(spec).log().all() .filter(document("participate-fail-request-with-null", resource(failSnippets))) - .cookies(cookieProvider.createCookiesWithCi("poke5678")) + .cookies(cookieProvider.createCookiesWithMember(participant)) .contentType(ContentType.JSON) .body(request) .when().post("/participations") @@ -152,7 +152,7 @@ class CancelParticipate { @BeforeEach void setUp() { - proposer = memberFixture.createMember(); + proposer = memberFixture.createMember("dora"); participant = memberFixture.createMember("poke"); offeringWithGrouping = offeringFixture.createOffering(proposer); @@ -165,7 +165,6 @@ void setUp() { offeringNotParticipated = offeringFixture.createOffering(proposer); offeringMemberFixture.createProposer(proposer, offeringNotParticipated); - } @DisplayName("공모 참여 취소를 할 수 있다.") @@ -173,7 +172,7 @@ void setUp() { void should_cancelSuccess() { RestAssured.given(spec).log().all() .filter(document("cancel-success", resource(successSnippets))) - .cookies(cookieProvider.createCookiesWithCi("poke5678")) + .cookies(cookieProvider.createCookiesWithMember(participant)) .queryParam("offering-id", offeringWithGrouping.getId()) .when().delete("/participations") .then().log().all() @@ -185,7 +184,7 @@ void should_cancelSuccess() { void should_throwException_when_cancelProposer() { RestAssured.given(spec).log().all() .filter(document("cancel-fail-offering-proposer", resource(failSnippets))) - .cookies(cookieProvider.createCookies()) + .cookies(cookieProvider.createCookiesWithMember(proposer)) .queryParam("offering-id", offeringWithGrouping.getId()) .when().delete("/participations") .then().log().all() @@ -197,7 +196,7 @@ void should_throwException_when_cancelProposer() { void should_throwException_when_cancelInProgress() { RestAssured.given(spec).log().all() .filter(document("cancel-fail-offering-in-progress", resource(failSnippets))) - .cookies(cookieProvider.createCookiesWithCi("poke5678")) + .cookies(cookieProvider.createCookiesWithMember(participant)) .queryParam("offering-id", offeringWithInProgress.getId()) .when().delete("/participations") .then().log().all() @@ -209,7 +208,7 @@ void should_throwException_when_cancelInProgress() { void should_throwException_when_cancelNotParticipated() { RestAssured.given(spec).log().all() .filter(document("cancel-fail-offering-not-participated", resource(failSnippets))) - .cookies(cookieProvider.createCookiesWithCi("poke5678")) + .cookies(cookieProvider.createCookiesWithMember(participant)) .queryParam("offering-id", offeringNotParticipated.getId()) .when().delete("/participations") .then().log().all() @@ -264,7 +263,7 @@ void setUp() { void should_participantsSuccess() { RestAssured.given(spec).log().all() .filter(document("participants-success", resource(successSnippets))) - .cookies(cookieProvider.createCookiesWithCi("dora5678")) + .cookies(cookieProvider.createCookiesWithMember(proposer)) .queryParam("offering-id", offering.getId()) .when().get("/participants") .then().log().all() @@ -276,7 +275,7 @@ void should_participantsSuccess() { void should_throwException_when_invalidOffering() { RestAssured.given(spec).log().all() .filter(document("participants-fail-invalid-offering", resource(failSnippets))) - .cookies(cookieProvider.createCookiesWithCi("dora5678")) + .cookies(cookieProvider.createCookiesWithMember(proposer)) .queryParam("offering-id", 1000) .when().get("/participants") .then().log().all() @@ -288,7 +287,7 @@ void should_throwException_when_invalidOffering() { void should_throwException_when_emptyValue() { RestAssured.given(spec).log().all() .filter(document("participants-fail-request-with-null", resource(failSnippets))) - .cookies(cookieProvider.createCookiesWithCi("dora5678")) + .cookies(cookieProvider.createCookiesWithMember(proposer)) .when().get("/participants?offering-id=") .then().log().all() .statusCode(400);