diff --git a/backend/src/main/java/woowacourse/touroot/authentication/controller/LoginController.java b/backend/src/main/java/woowacourse/touroot/authentication/controller/LoginController.java index 40fc3fae..abb762b8 100644 --- a/backend/src/main/java/woowacourse/touroot/authentication/controller/LoginController.java +++ b/backend/src/main/java/woowacourse/touroot/authentication/controller/LoginController.java @@ -1,5 +1,10 @@ package woowacourse.touroot.authentication.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -8,7 +13,9 @@ import org.springframework.web.bind.annotation.RestController; import woowacourse.touroot.authentication.dto.LoginResponse; import woowacourse.touroot.authentication.service.LoginService; +import woowacourse.touroot.global.exception.dto.ExceptionResponse; +@Tag(name = "로그인") @RequiredArgsConstructor @RestController @RequestMapping("/api/v1/login") @@ -16,6 +23,16 @@ public class LoginController { private final LoginService loginService; + @Operation( + summary = "카카오 로그인", + responses = { + @ApiResponse( + responseCode = "400", + description = "유효하지 않은 인가 코드로 로그인 요청을 했을 때", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + } + ) @GetMapping("/oauth/kakao") public ResponseEntity login(@RequestParam(name = "code") String authorizationCode) { return ResponseEntity.ok() diff --git a/backend/src/main/java/woowacourse/touroot/authentication/dto/LoginResponse.java b/backend/src/main/java/woowacourse/touroot/authentication/dto/LoginResponse.java index 156f99fc..a4cccb33 100644 --- a/backend/src/main/java/woowacourse/touroot/authentication/dto/LoginResponse.java +++ b/backend/src/main/java/woowacourse/touroot/authentication/dto/LoginResponse.java @@ -1,4 +1,14 @@ package woowacourse.touroot.authentication.dto; -public record LoginResponse(String accessToken) { +import io.swagger.v3.oas.annotations.media.Schema; +import woowacourse.touroot.member.domain.Member; + +public record LoginResponse( + @Schema(description = "로그인된 유저의 닉네임") String nickname, + @Schema(description = "로그인된 유저의 프로필 이미지 경로") String profileImageUrl, + @Schema(description = "인가에 필요한 accessToken") String accessToken) { + + public static LoginResponse of(Member member, String accessToken) { + return new LoginResponse(member.getNickname(), member.getProfileImageUri(), accessToken); + } } diff --git a/backend/src/main/java/woowacourse/touroot/authentication/infrastructure/KakaoOauthClient.java b/backend/src/main/java/woowacourse/touroot/authentication/infrastructure/KakaoOauthClient.java index ccb2c1a3..461bea3b 100644 --- a/backend/src/main/java/woowacourse/touroot/authentication/infrastructure/KakaoOauthClient.java +++ b/backend/src/main/java/woowacourse/touroot/authentication/infrastructure/KakaoOauthClient.java @@ -1,18 +1,24 @@ package woowacourse.touroot.authentication.infrastructure; +import java.io.IOException; 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.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpResponse; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestClient; import woowacourse.touroot.authentication.dto.KakaoAccessTokenResponse; import woowacourse.touroot.authentication.dto.OauthUserInformationResponse; +import woowacourse.touroot.global.exception.BadRequestException; +import woowacourse.touroot.global.exception.ClientException; @Component public class KakaoOauthClient { @@ -55,6 +61,7 @@ public OauthUserInformationResponse requestUserInformation(String authorizationC .uri(userInformationRequestUri) .header(HttpHeaders.AUTHORIZATION, "Bearer " + kakaoAccessTokenResponse.accessToken()) .retrieve() + .onStatus(HttpStatusCode::isError, this::handleClientError) .toEntity(OauthUserInformationResponse.class) .getBody(); } @@ -71,7 +78,15 @@ private KakaoAccessTokenResponse requestAccessToken(String authorizationCode) { .contentType(MediaType.APPLICATION_FORM_URLENCODED) .body(params) .retrieve() + .onStatus(HttpStatusCode::isError, this::handleClientError) .toEntity(KakaoAccessTokenResponse.class) .getBody(); } + + private void handleClientError(HttpRequest request, ClientHttpResponse response) throws IOException { + if (response.getStatusCode().is4xxClientError()) { + throw new BadRequestException("잘못된 로그인 요청입니다. 인가코드를 확인해주세요"); + } + throw new ClientException("외부 서비스의 장애로 카카오로그인을 이용할 수 없습니다"); + } } diff --git a/backend/src/main/java/woowacourse/touroot/authentication/service/LoginService.java b/backend/src/main/java/woowacourse/touroot/authentication/service/LoginService.java index 9f02b4e7..abb1a04a 100644 --- a/backend/src/main/java/woowacourse/touroot/authentication/service/LoginService.java +++ b/backend/src/main/java/woowacourse/touroot/authentication/service/LoginService.java @@ -22,9 +22,9 @@ public LoginResponse login(String code) { Member member = memberRepository.findByKakaoId(userInformation.socialLoginId()) .orElseGet(() -> signUp(userInformation)); - return new LoginResponse(tokenProvider.createToken(member)); + return LoginResponse.of(member, tokenProvider.createToken(member)); } - + private Member signUp(OauthUserInformationResponse userInformation) { return memberRepository.save( new Member(userInformation.socialLoginId(), userInformation.nickname(), userInformation.profileImage()) diff --git a/backend/src/main/java/woowacourse/touroot/global/exception/ClientException.java b/backend/src/main/java/woowacourse/touroot/global/exception/ClientException.java new file mode 100644 index 00000000..5abf4cd8 --- /dev/null +++ b/backend/src/main/java/woowacourse/touroot/global/exception/ClientException.java @@ -0,0 +1,8 @@ +package woowacourse.touroot.global.exception; + +public class ClientException extends RuntimeException { + + public ClientException(String message) { + super(message); + } +} diff --git a/backend/src/main/java/woowacourse/touroot/global/exception/GlobalExceptionHandler.java b/backend/src/main/java/woowacourse/touroot/global/exception/GlobalExceptionHandler.java index c8f0872e..6e2bd4f3 100644 --- a/backend/src/main/java/woowacourse/touroot/global/exception/GlobalExceptionHandler.java +++ b/backend/src/main/java/woowacourse/touroot/global/exception/GlobalExceptionHandler.java @@ -34,4 +34,12 @@ public ResponseEntity handleMethodArgumentNotValidException( return ResponseEntity.badRequest() .body(data); } + + @ExceptionHandler(ClientException.class) + public ResponseEntity handleClientException(ClientException exception) { + log.error("CLIENT_EXCEPTION :: message = {}", exception.getMessage()); + + ExceptionResponse data = new ExceptionResponse(exception.getMessage()); + return ResponseEntity.internalServerError().body(data); + } }