From f581c00afef533f0a8b0cfafcc930e90a5d73ea3 Mon Sep 17 00:00:00 2001 From: songhyeonpk Date: Fri, 28 Mar 2025 16:46:09 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=EC=97=90=20=EC=9D=B8=EC=A6=9D=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + .../port/SocialAuthClientPort.java | 10 +++ .../server/common/config/WebClientConfig.java | 18 ++++ .../infrastructure/oauth/SocialAuthUser.java | 16 ++++ .../oauth/kakao/KakaoAuthUser.java | 17 ++++ .../oauth/kakao/KakaoErrorResponse.java | 15 ++++ .../oauth/kakao/KakaoOAuthClient.java | 89 +++++++++++++++++++ .../oauth/kakao/KakaoProperties.java | 19 ++++ .../oauth/kakao/KakaoTokenResponse.java | 27 ++++++ .../oauth/kakao/KakaoUserInfoResponse.java | 42 +++++++++ src/main/resources/application-security.yml | 12 +-- 11 files changed, 261 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/ftm/server/application/port/SocialAuthClientPort.java create mode 100644 src/main/java/com/ftm/server/common/config/WebClientConfig.java create mode 100644 src/main/java/com/ftm/server/infrastructure/oauth/SocialAuthUser.java create mode 100644 src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoAuthUser.java create mode 100644 src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoErrorResponse.java create mode 100644 src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoOAuthClient.java create mode 100644 src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoProperties.java create mode 100644 src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoTokenResponse.java create mode 100644 src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoUserInfoResponse.java diff --git a/build.gradle b/build.gradle index e424593..bef3d57 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,8 @@ dependencies { annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" + implementation 'org.springframework.boot:spring-boot-starter-webflux' + // Redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' diff --git a/src/main/java/com/ftm/server/application/port/SocialAuthClientPort.java b/src/main/java/com/ftm/server/application/port/SocialAuthClientPort.java new file mode 100644 index 0000000..50769ed --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/SocialAuthClientPort.java @@ -0,0 +1,10 @@ +package com.ftm.server.application.port; + +import com.ftm.server.application.dto.command.SocialAuthCommand; +import com.ftm.server.infrastructure.oauth.SocialAuthUser; + +public interface SocialAuthClientPort { + + // OAuthClient 인증 (소셜 인증) + R authenticate(T command); +} diff --git a/src/main/java/com/ftm/server/common/config/WebClientConfig.java b/src/main/java/com/ftm/server/common/config/WebClientConfig.java new file mode 100644 index 0000000..93916de --- /dev/null +++ b/src/main/java/com/ftm/server/common/config/WebClientConfig.java @@ -0,0 +1,18 @@ +package com.ftm.server.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.client.WebClient; + +@Configuration +public class WebClientConfig { + + @Bean + public WebClient webClient(WebClient.Builder builder) { + return builder.defaultHeader( + HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .build(); + } +} diff --git a/src/main/java/com/ftm/server/infrastructure/oauth/SocialAuthUser.java b/src/main/java/com/ftm/server/infrastructure/oauth/SocialAuthUser.java new file mode 100644 index 0000000..3a604ea --- /dev/null +++ b/src/main/java/com/ftm/server/infrastructure/oauth/SocialAuthUser.java @@ -0,0 +1,16 @@ +package com.ftm.server.infrastructure.oauth; + +import com.ftm.server.domain.enums.SocialProvider; +import lombok.Getter; + +@Getter +public abstract class SocialAuthUser { + + private final SocialProvider socialProvider; + private final String socialId; + + protected SocialAuthUser(SocialProvider socialProvider, String socialId) { + this.socialProvider = socialProvider; + this.socialId = socialId; + } +} diff --git a/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoAuthUser.java b/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoAuthUser.java new file mode 100644 index 0000000..8723903 --- /dev/null +++ b/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoAuthUser.java @@ -0,0 +1,17 @@ +package com.ftm.server.infrastructure.oauth.kakao; + +import com.ftm.server.domain.enums.SocialProvider; +import com.ftm.server.infrastructure.oauth.SocialAuthUser; +import lombok.Getter; + +@Getter +public class KakaoAuthUser extends SocialAuthUser { + + private KakaoAuthUser(String socialId) { + super(SocialProvider.KAKAO, socialId); + } + + public static KakaoAuthUser from(String socialId) { + return new KakaoAuthUser(socialId); + } +} diff --git a/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoErrorResponse.java b/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoErrorResponse.java new file mode 100644 index 0000000..df81b29 --- /dev/null +++ b/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoErrorResponse.java @@ -0,0 +1,15 @@ +package com.ftm.server.infrastructure.oauth.kakao; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class KakaoErrorResponse { + + private String error; + + @JsonProperty("error_description") + private String errorDescription; +} diff --git a/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoOAuthClient.java b/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoOAuthClient.java new file mode 100644 index 0000000..9ef9fc8 --- /dev/null +++ b/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoOAuthClient.java @@ -0,0 +1,89 @@ +package com.ftm.server.infrastructure.oauth.kakao; + +import static com.ftm.server.common.consts.StaticConsts.*; + +import com.ftm.server.application.dto.command.KakaoAuthCommand; +import com.ftm.server.application.port.SocialAuthClientPort; +import com.ftm.server.common.annotation.InfraService; +import com.ftm.server.common.exception.CustomException; +import com.ftm.server.common.response.enums.ErrorResponseCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatusCode; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +@Slf4j +@InfraService +@RequiredArgsConstructor +public class KakaoOAuthClient implements SocialAuthClientPort { + + private final KakaoProperties kakaoProperties; + private final WebClient webClient; + + @Override + public KakaoAuthUser authenticate(KakaoAuthCommand command) { + // Access Token 요청 + KakaoTokenResponse token = getKakaoToken(command.getAuthorizationCode()); + + // 카카오 유저 정보 요청 + KakaoUserInfoResponse userInfo = getKakaoUserInfo(token.getAccessToken()); + + return KakaoAuthUser.from(userInfo.getId()); + } + + private KakaoTokenResponse getKakaoToken(String authorizationCode) { + return requestToken(authorizationCode).block(); + } + + private KakaoUserInfoResponse getKakaoUserInfo(String accessToken) { + return requestUserInfo(accessToken).block(); + } + + private Mono requestToken(String authorizationCode) { + return webClient + .post() + .uri(kakaoProperties.getTokenUri()) + .body( + BodyInserters.fromFormData("grant_type", AUTHORIZATION_GRANT_TYPE) + .with("client_id", kakaoProperties.getClientId()) + .with("redirect_uri", kakaoProperties.getRedirectUri()) + .with("code", authorizationCode) + .with("client_secret", kakaoProperties.getClientSecret())) + .retrieve() + .onStatus( + HttpStatusCode::is4xxClientError, + response -> + error(response, ErrorResponseCode.KAKAO_AUTH_TOKEN_EXCHANGE_FAILED)) + .bodyToMono(KakaoTokenResponse.class); + } + + private Mono requestUserInfo(String accessToken) { + return webClient + .get() + .uri(kakaoProperties.getUserInfoUri()) + .header(HttpHeaders.AUTHORIZATION, AUTHORIZATION_HEADER_PREFIX + accessToken) + .retrieve() + .onStatus( + HttpStatusCode::is4xxClientError, + response -> + error(response, ErrorResponseCode.KAKAO_USER_PROFILE_FETCH_FAILED)) + .bodyToMono(KakaoUserInfoResponse.class); + } + + private Mono error(ClientResponse clientResponse, ErrorResponseCode code) { + return clientResponse + .bodyToMono(KakaoErrorResponse.class) + .flatMap( + body -> { + log.error( + "[KAKAO_ERROR] error: {}, description: {}", + body.getError(), + body.getErrorDescription()); + return Mono.error(new CustomException(code)); + }); + } +} diff --git a/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoProperties.java b/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoProperties.java new file mode 100644 index 0000000..e212e10 --- /dev/null +++ b/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoProperties.java @@ -0,0 +1,19 @@ +package com.ftm.server.infrastructure.oauth.kakao; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "kakao") +@Getter +@Setter +public class KakaoProperties { + + private String clientId; + private String clientSecret; + private String redirectUri; + private String tokenUri; + private String userInfoUri; +} diff --git a/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoTokenResponse.java b/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoTokenResponse.java new file mode 100644 index 0000000..137afd0 --- /dev/null +++ b/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoTokenResponse.java @@ -0,0 +1,27 @@ +package com.ftm.server.infrastructure.oauth.kakao; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class KakaoTokenResponse { + + @JsonProperty("token_type") + private String tokenType; + + @JsonProperty("access_token") + private String accessToken; + + @JsonProperty("expires_in") + private Integer expiresIn; + + @JsonProperty("refresh_token") + private String refreshToken; + + @JsonProperty("refresh_token_expires_in") + private Integer refreshTokenExpiresIn; + + private String scope; +} diff --git a/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoUserInfoResponse.java b/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoUserInfoResponse.java new file mode 100644 index 0000000..b9dddb0 --- /dev/null +++ b/src/main/java/com/ftm/server/infrastructure/oauth/kakao/KakaoUserInfoResponse.java @@ -0,0 +1,42 @@ +package com.ftm.server.infrastructure.oauth.kakao; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class KakaoUserInfoResponse { + + private String id; + + @JsonProperty("kakao_account") + private KakaoAccount kakaoAccount; + + @Getter + @NoArgsConstructor + static class KakaoAccount { + + @JsonProperty("profile_needs_agreement") + private Boolean profileNeedsAgreement; + + @JsonProperty("profile_nickname_needs_agreement") + private Boolean profileNicknameNeedsAgreement; + + @JsonProperty("profile_image_needs_agreement") + private Boolean profileImageNeedsAgreement; + + @JsonProperty("profile") + private KakaoProfile profile; + } + + @Getter + @NoArgsConstructor + static class KakaoProfile { + + private String nickname; + + @JsonProperty("profile_image_url") + private String profileImageUrl; + } +} diff --git a/src/main/resources/application-security.yml b/src/main/resources/application-security.yml index d424991..8381424 100644 --- a/src/main/resources/application-security.yml +++ b/src/main/resources/application-security.yml @@ -3,9 +3,9 @@ spring: activate: on-profile: "security" - kakao: - clinet-id: ${KAKAO_CLIENT_ID} - client-secret: ${KAKAO_CLIENT_SECRET} - redirect-uri: "${BASE_URL}/api/auth/kakao/callback" - token-uri: "https://kauth.kakao.com/oauth/token" - user-info-uri: "https://kapi.kakao.com/v2/user/me" \ No newline at end of file +kakao: + clinet-id: ${KAKAO_CLIENT_ID} + client-secret: ${KAKAO_CLIENT_SECRET} + redirect-uri: "${BASE_URL}/api/auth/kakao/callback" + token-uri: "https://kauth.kakao.com/oauth/token" + user-info-uri: "https://kapi.kakao.com/v2/user/me" \ No newline at end of file From 0672d631fe4b0c453f9da20b66d5fc6097604e23 Mon Sep 17 00:00:00 2001 From: songhyeonpk Date: Fri, 28 Mar 2025 16:50:01 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20api=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/command/KakaoAuthCommand.java | 16 +++++ .../dto/command/SocialAuthCommand.java | 13 ++++ .../dto/query/FindSocialUserQuery.java | 20 ++++++ .../application/port/AuthenticationPort.java | 4 ++ .../port/repository/UserRepository.java | 3 + .../application/service/UserService.java | 7 ++ .../usecase/auth/KakaoLoginUseCase.java | 49 +++++++++++++ .../server/common/consts/StaticConsts.java | 9 +++ .../response/enums/ErrorResponseCode.java | 6 +- .../server/domain/vo/PendingSocialUserVo.java | 22 ++++++ .../domain/vo/SocialLoginOutcomeVo.java | 15 ++++ .../domain/vo/SocialLoginSuccessVo.java | 22 ++++++ .../security/AuthenticationService.java | 8 +++ .../security/SecurityConfig.java | 2 +- .../controller/auth/KakaoLoginController.java | 68 +++++++++++++++++++ .../web/dto/request/KakaoLoginRequest.java | 11 +++ .../web/dto/response/SocialLoginResponse.java | 36 ++++++++++ 17 files changed, 309 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/ftm/server/application/dto/command/KakaoAuthCommand.java create mode 100644 src/main/java/com/ftm/server/application/dto/command/SocialAuthCommand.java create mode 100644 src/main/java/com/ftm/server/application/dto/query/FindSocialUserQuery.java create mode 100644 src/main/java/com/ftm/server/application/usecase/auth/KakaoLoginUseCase.java create mode 100644 src/main/java/com/ftm/server/common/consts/StaticConsts.java create mode 100644 src/main/java/com/ftm/server/domain/vo/PendingSocialUserVo.java create mode 100644 src/main/java/com/ftm/server/domain/vo/SocialLoginOutcomeVo.java create mode 100644 src/main/java/com/ftm/server/domain/vo/SocialLoginSuccessVo.java create mode 100644 src/main/java/com/ftm/server/web/controller/auth/KakaoLoginController.java create mode 100644 src/main/java/com/ftm/server/web/dto/request/KakaoLoginRequest.java create mode 100644 src/main/java/com/ftm/server/web/dto/response/SocialLoginResponse.java diff --git a/src/main/java/com/ftm/server/application/dto/command/KakaoAuthCommand.java b/src/main/java/com/ftm/server/application/dto/command/KakaoAuthCommand.java new file mode 100644 index 0000000..23598af --- /dev/null +++ b/src/main/java/com/ftm/server/application/dto/command/KakaoAuthCommand.java @@ -0,0 +1,16 @@ +package com.ftm.server.application.dto.command; + +import com.ftm.server.web.dto.request.KakaoLoginRequest; +import lombok.Getter; + +@Getter +public class KakaoAuthCommand extends SocialAuthCommand { + + private KakaoAuthCommand(String authorizationCode) { + super(authorizationCode); + } + + public static KakaoAuthCommand from(KakaoLoginRequest request) { + return new KakaoAuthCommand(request.getAuthorizationCode()); + } +} diff --git a/src/main/java/com/ftm/server/application/dto/command/SocialAuthCommand.java b/src/main/java/com/ftm/server/application/dto/command/SocialAuthCommand.java new file mode 100644 index 0000000..18cb7e5 --- /dev/null +++ b/src/main/java/com/ftm/server/application/dto/command/SocialAuthCommand.java @@ -0,0 +1,13 @@ +package com.ftm.server.application.dto.command; + +import lombok.Getter; + +@Getter +public abstract class SocialAuthCommand { + + private final String authorizationCode; + + protected SocialAuthCommand(String authorizationCode) { + this.authorizationCode = authorizationCode; + } +} diff --git a/src/main/java/com/ftm/server/application/dto/query/FindSocialUserQuery.java b/src/main/java/com/ftm/server/application/dto/query/FindSocialUserQuery.java new file mode 100644 index 0000000..6a8337c --- /dev/null +++ b/src/main/java/com/ftm/server/application/dto/query/FindSocialUserQuery.java @@ -0,0 +1,20 @@ +package com.ftm.server.application.dto.query; + +import com.ftm.server.domain.enums.SocialProvider; +import lombok.Getter; + +@Getter +public class FindSocialUserQuery { + + private final SocialProvider socialProvider; + private final String socialId; + + private FindSocialUserQuery(SocialProvider socialProvider, String socialId) { + this.socialProvider = socialProvider; + this.socialId = socialId; + } + + public static FindSocialUserQuery of(SocialProvider socialProvider, String socialId) { + return new FindSocialUserQuery(socialProvider, socialId); + } +} diff --git a/src/main/java/com/ftm/server/application/port/AuthenticationPort.java b/src/main/java/com/ftm/server/application/port/AuthenticationPort.java index a9b34cc..8929afb 100644 --- a/src/main/java/com/ftm/server/application/port/AuthenticationPort.java +++ b/src/main/java/com/ftm/server/application/port/AuthenticationPort.java @@ -1,6 +1,7 @@ package com.ftm.server.application.port; import com.ftm.server.application.dto.command.UserLoginCommand; +import com.ftm.server.domain.entity.User; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; @@ -11,6 +12,9 @@ public interface AuthenticationPort { // 일반 유저 인증 객체 생성 Authentication createAuthenticationFromCredentials(UserLoginCommand command); + // 소셜 유저 인증 객체 생성 + Authentication createAuthenticationFromSocial(User user); + // 인증 세션 등록 (시큐리티 컨텍스트 저장) void saveAuthenticatedSession( Authentication authentication, diff --git a/src/main/java/com/ftm/server/application/port/repository/UserRepository.java b/src/main/java/com/ftm/server/application/port/repository/UserRepository.java index 1cd7390..66db833 100644 --- a/src/main/java/com/ftm/server/application/port/repository/UserRepository.java +++ b/src/main/java/com/ftm/server/application/port/repository/UserRepository.java @@ -1,6 +1,7 @@ package com.ftm.server.application.port.repository; import com.ftm.server.domain.entity.User; +import com.ftm.server.domain.enums.SocialProvider; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -9,4 +10,6 @@ public interface UserRepository extends JpaRepository { Boolean existsByEmail(String email); Optional findByEmail(String email); + + Optional findBySocialProviderAndSocialId(SocialProvider socialProvider, String socialId); } diff --git a/src/main/java/com/ftm/server/application/service/UserService.java b/src/main/java/com/ftm/server/application/service/UserService.java index b8cc985..3756fb3 100644 --- a/src/main/java/com/ftm/server/application/service/UserService.java +++ b/src/main/java/com/ftm/server/application/service/UserService.java @@ -3,10 +3,12 @@ import com.ftm.server.application.dto.command.GeneralUserCreationCommand; import com.ftm.server.application.dto.query.FindByEmailQuery; import com.ftm.server.application.dto.query.FindByIdQuery; +import com.ftm.server.application.dto.query.FindSocialUserQuery; import com.ftm.server.application.port.repository.UserRepository; import com.ftm.server.common.exception.CustomException; import com.ftm.server.domain.entity.User; import com.ftm.server.domain.vo.EmailDuplicationVo; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -28,6 +30,11 @@ public User queryUser(FindByIdQuery query) { .orElseThrow(() -> CustomException.USER_NOT_FOUND); } + public Optional querySocialUser(FindSocialUserQuery query) { + return userRepository.findBySocialProviderAndSocialId( + query.getSocialProvider(), query.getSocialId()); + } + public User createGeneralUser(GeneralUserCreationCommand command) { User user = User.createGeneralUser(command); userRepository.save(user); diff --git a/src/main/java/com/ftm/server/application/usecase/auth/KakaoLoginUseCase.java b/src/main/java/com/ftm/server/application/usecase/auth/KakaoLoginUseCase.java new file mode 100644 index 0000000..6b80f2b --- /dev/null +++ b/src/main/java/com/ftm/server/application/usecase/auth/KakaoLoginUseCase.java @@ -0,0 +1,49 @@ +package com.ftm.server.application.usecase.auth; + +import com.ftm.server.application.dto.command.KakaoAuthCommand; +import com.ftm.server.application.dto.query.FindByUserIdQuery; +import com.ftm.server.application.dto.query.FindSocialUserQuery; +import com.ftm.server.application.port.SocialAuthClientPort; +import com.ftm.server.application.service.UserImageService; +import com.ftm.server.application.service.UserService; +import com.ftm.server.common.annotation.UseCase; +import com.ftm.server.domain.entity.User; +import com.ftm.server.domain.entity.UserImage; +import com.ftm.server.domain.vo.PendingSocialUserVo; +import com.ftm.server.domain.vo.SocialLoginOutcomeVo; +import com.ftm.server.domain.vo.SocialLoginSuccessVo; +import com.ftm.server.domain.vo.UserSummaryVo; +import com.ftm.server.infrastructure.oauth.kakao.KakaoAuthUser; +import java.util.Optional; +import lombok.RequiredArgsConstructor; + +@UseCase +@RequiredArgsConstructor +public class KakaoLoginUseCase { + + private final SocialAuthClientPort oAuthClientPort; + private final UserService userService; + private final UserImageService userImageService; + + public SocialLoginOutcomeVo kakaoLogin(KakaoAuthCommand command) { + // 카카오 인증 수행 + KakaoAuthUser kakaoUser = oAuthClientPort.authenticate(command); + + Optional saved = + userService.querySocialUser( + FindSocialUserQuery.of( + kakaoUser.getSocialProvider(), kakaoUser.getSocialId())); + + // 가입된 회원인 경우 + if (saved.isPresent()) { + User user = saved.get(); + UserImage image = + userImageService.queryUserImageByUserId(FindByUserIdQuery.of(user.getId())); + + return SocialLoginSuccessVo.from(user, UserSummaryVo.of(user, image)); + } + + // 가입된 회원이 아닌 경우 + return PendingSocialUserVo.from(kakaoUser.getSocialProvider(), kakaoUser.getSocialId()); + } +} diff --git a/src/main/java/com/ftm/server/common/consts/StaticConsts.java b/src/main/java/com/ftm/server/common/consts/StaticConsts.java new file mode 100644 index 0000000..bce595f --- /dev/null +++ b/src/main/java/com/ftm/server/common/consts/StaticConsts.java @@ -0,0 +1,9 @@ +package com.ftm.server.common.consts; + +public class StaticConsts { + + public static final String AUTHORIZATION_HEADER_PREFIX = "Bearer "; + public static final String AUTHORIZATION_GRANT_TYPE = "authorization_code"; + public static final String PENDING_SOCIAL_USER_SESSION_KEY = "PENDING_SOCIAL_USER_INFO"; + public static final int PENDING_SOCIAL_USER_SESSION_TTL = 300; // 5분 +} diff --git a/src/main/java/com/ftm/server/common/response/enums/ErrorResponseCode.java b/src/main/java/com/ftm/server/common/response/enums/ErrorResponseCode.java index b026ea1..6fe11b4 100644 --- a/src/main/java/com/ftm/server/common/response/enums/ErrorResponseCode.java +++ b/src/main/java/com/ftm/server/common/response/enums/ErrorResponseCode.java @@ -29,7 +29,11 @@ public enum ErrorResponseCode { // 500번 UNKNOWN_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "E500_001", "알 수 없는 서버 에러가 발생했습니다."), - FAIL_TO_SEND_EMAIL(HttpStatus.INTERNAL_SERVER_ERROR, "E500_002", "서버 내부 문제로 메일 전송에 실패했습니다."); + FAIL_TO_SEND_EMAIL(HttpStatus.INTERNAL_SERVER_ERROR, "E500_002", "서버 내부 문제로 메일 전송에 실패했습니다."), + + // 502번 (외부 서비스에서 문제 발생) + KAKAO_AUTH_TOKEN_EXCHANGE_FAILED(HttpStatus.BAD_GATEWAY, "E502_001", "카카오 인증 토큰 요청에 실패했습니다."), + KAKAO_USER_PROFILE_FETCH_FAILED(HttpStatus.BAD_GATEWAY, "E502_002", "카카오 사용자 정보 요청에 실패했습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/ftm/server/domain/vo/PendingSocialUserVo.java b/src/main/java/com/ftm/server/domain/vo/PendingSocialUserVo.java new file mode 100644 index 0000000..a6a01d2 --- /dev/null +++ b/src/main/java/com/ftm/server/domain/vo/PendingSocialUserVo.java @@ -0,0 +1,22 @@ +package com.ftm.server.domain.vo; + +import com.ftm.server.domain.enums.SocialProvider; +import lombok.Getter; + +/** 가입이 필요한 소셜 유저인 경우 VO */ +@Getter +public class PendingSocialUserVo extends SocialLoginOutcomeVo { + + private final SocialProvider socialProvider; + private final String socialId; + + private PendingSocialUserVo(SocialProvider socialProvider, String socialId) { + super(false); + this.socialProvider = socialProvider; + this.socialId = socialId; + } + + public static PendingSocialUserVo from(SocialProvider socialProvider, String socialId) { + return new PendingSocialUserVo(socialProvider, socialId); + } +} diff --git a/src/main/java/com/ftm/server/domain/vo/SocialLoginOutcomeVo.java b/src/main/java/com/ftm/server/domain/vo/SocialLoginOutcomeVo.java new file mode 100644 index 0000000..30e0aa2 --- /dev/null +++ b/src/main/java/com/ftm/server/domain/vo/SocialLoginOutcomeVo.java @@ -0,0 +1,15 @@ +package com.ftm.server.domain.vo; + +import java.io.Serializable; +import lombok.Getter; + +/** 소셜 로그인 결과 VO */ +@Getter +public abstract class SocialLoginOutcomeVo implements Serializable { + + private final boolean registered; + + protected SocialLoginOutcomeVo(boolean registered) { + this.registered = registered; + } +} diff --git a/src/main/java/com/ftm/server/domain/vo/SocialLoginSuccessVo.java b/src/main/java/com/ftm/server/domain/vo/SocialLoginSuccessVo.java new file mode 100644 index 0000000..3842591 --- /dev/null +++ b/src/main/java/com/ftm/server/domain/vo/SocialLoginSuccessVo.java @@ -0,0 +1,22 @@ +package com.ftm.server.domain.vo; + +import com.ftm.server.domain.entity.User; +import lombok.Getter; + +/** 가입된 유저라 로그인에 성공한 경우 VO */ +@Getter +public class SocialLoginSuccessVo extends SocialLoginOutcomeVo { + + private final User user; + private final UserSummaryVo userSummaryVo; + + private SocialLoginSuccessVo(User user, UserSummaryVo userSummaryVo) { + super(true); + this.user = user; + this.userSummaryVo = userSummaryVo; + } + + public static SocialLoginSuccessVo from(User user, UserSummaryVo userSummaryVo) { + return new SocialLoginSuccessVo(user, userSummaryVo); + } +} diff --git a/src/main/java/com/ftm/server/infrastructure/security/AuthenticationService.java b/src/main/java/com/ftm/server/infrastructure/security/AuthenticationService.java index 19287c7..7b3b2d8 100644 --- a/src/main/java/com/ftm/server/infrastructure/security/AuthenticationService.java +++ b/src/main/java/com/ftm/server/infrastructure/security/AuthenticationService.java @@ -3,6 +3,7 @@ import com.ftm.server.application.dto.command.UserLoginCommand; import com.ftm.server.application.port.AuthenticationPort; import com.ftm.server.common.annotation.InfraService; +import com.ftm.server.domain.entity.User; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -38,6 +39,13 @@ public Authentication createAuthenticationFromCredentials(UserLoginCommand comma return authenticationManager.authenticate(token); } + @Override + public Authentication createAuthenticationFromSocial(User user) { + UserPrincipal userPrincipal = UserPrincipal.of(user); + return new UsernamePasswordAuthenticationToken( + userPrincipal, null, userPrincipal.getAuthorities()); + } + @Override public void saveAuthenticatedSession( Authentication authentication, diff --git a/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java b/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java index bbf94f0..3110dbf 100644 --- a/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java +++ b/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java @@ -53,7 +53,7 @@ public class SecurityConfig { private static final String[] POST_ANONYMOUS_MATCHERS = { "/api/users/email/authentication", "/api/users/email/authentication/code", - "/api/auth/login", + "/api/auth/login/**", "/api/users" }; diff --git a/src/main/java/com/ftm/server/web/controller/auth/KakaoLoginController.java b/src/main/java/com/ftm/server/web/controller/auth/KakaoLoginController.java new file mode 100644 index 0000000..eb55fb0 --- /dev/null +++ b/src/main/java/com/ftm/server/web/controller/auth/KakaoLoginController.java @@ -0,0 +1,68 @@ +package com.ftm.server.web.controller.auth; + +import static com.ftm.server.common.consts.StaticConsts.*; + +import com.ftm.server.application.dto.command.KakaoAuthCommand; +import com.ftm.server.application.usecase.auth.KakaoLoginUseCase; +import com.ftm.server.common.response.ApiResponse; +import com.ftm.server.common.response.enums.SuccessResponseCode; +import com.ftm.server.domain.enums.SocialProvider; +import com.ftm.server.domain.vo.PendingSocialUserVo; +import com.ftm.server.domain.vo.SocialLoginOutcomeVo; +import com.ftm.server.domain.vo.SocialLoginSuccessVo; +import com.ftm.server.infrastructure.security.AuthenticationService; +import com.ftm.server.web.dto.request.KakaoLoginRequest; +import com.ftm.server.web.dto.response.SocialLoginResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class KakaoLoginController { + + private final KakaoLoginUseCase kakaoLoginUseCase; + private final AuthenticationService authenticationService; + + @PostMapping("/api/auth/login/kakao") + public ResponseEntity> kakaoLogin( + @RequestBody KakaoLoginRequest request, + HttpServletRequest req, + HttpServletResponse res) { + SocialLoginOutcomeVo result = kakaoLoginUseCase.kakaoLogin(KakaoAuthCommand.from(request)); + + // 가입된 유저인 경우 로그인 처리 + if (result.isRegistered()) { + SocialLoginSuccessVo loginUser = (SocialLoginSuccessVo) result; + + Authentication auth = + authenticationService.createAuthenticationFromSocial(loginUser.getUser()); + authenticationService.saveAuthenticatedSession(auth, req, res); + + return ResponseEntity.status(HttpStatus.OK) + .body( + ApiResponse.success( + SuccessResponseCode.OK, + SocialLoginResponse.from( + SocialProvider.KAKAO, loginUser.getUserSummaryVo()))); + } + + // 가입이 필요한 유저인 경우 + PendingSocialUserVo pendingSocialUser = (PendingSocialUserVo) result; + + // 임시 세션 등록 + HttpSession session = req.getSession(true); + session.setAttribute(PENDING_SOCIAL_USER_SESSION_KEY, pendingSocialUser); + session.setMaxInactiveInterval(PENDING_SOCIAL_USER_SESSION_TTL); + + return ResponseEntity.status(HttpStatus.ACCEPTED) + .body(ApiResponse.success(SuccessResponseCode.ACCEPTED)); + } +} diff --git a/src/main/java/com/ftm/server/web/dto/request/KakaoLoginRequest.java b/src/main/java/com/ftm/server/web/dto/request/KakaoLoginRequest.java new file mode 100644 index 0000000..4692182 --- /dev/null +++ b/src/main/java/com/ftm/server/web/dto/request/KakaoLoginRequest.java @@ -0,0 +1,11 @@ +package com.ftm.server.web.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class KakaoLoginRequest { + + private final String authorizationCode; +} diff --git a/src/main/java/com/ftm/server/web/dto/response/SocialLoginResponse.java b/src/main/java/com/ftm/server/web/dto/response/SocialLoginResponse.java new file mode 100644 index 0000000..1de295d --- /dev/null +++ b/src/main/java/com/ftm/server/web/dto/response/SocialLoginResponse.java @@ -0,0 +1,36 @@ +package com.ftm.server.web.dto.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ftm.server.domain.enums.SocialProvider; +import com.ftm.server.domain.vo.UserSummaryVo; +import java.time.LocalDateTime; +import lombok.Getter; + +@Getter +public class SocialLoginResponse { + + private final Long id; + private final String nickname; + private final SocialProvider socialProvider; + private final String profileImageUrl; + private final String mildLevelName; + private final String spicyLevelName; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm", shape = JsonFormat.Shape.STRING) + private final LocalDateTime loginTime; + + SocialLoginResponse(SocialProvider socialProvider, UserSummaryVo userSummaryVo) { + this.id = userSummaryVo.getId(); + this.nickname = userSummaryVo.getNickname(); + this.socialProvider = socialProvider; + this.profileImageUrl = userSummaryVo.getProfileImageUrl(); + this.mildLevelName = userSummaryVo.getMildLevelName(); + this.spicyLevelName = userSummaryVo.getSpicyLevelName(); + this.loginTime = LocalDateTime.now(); + } + + public static SocialLoginResponse from( + SocialProvider socialProvider, UserSummaryVo userSummaryVo) { + return new SocialLoginResponse(socialProvider, userSummaryVo); + } +} From 9a5f77374edf07a37459d7fd254c01483e7b970f Mon Sep 17 00:00:00 2001 From: songhyeonpk Date: Fri, 28 Mar 2025 16:50:35 +0900 Subject: [PATCH 3/4] =?UTF-8?q?test:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20api=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ftm/server/domain/entity/User.java | 11 + .../com/ftm/server/auth/KakaoLoginTest.java | 212 ++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 src/test/java/com/ftm/server/auth/KakaoLoginTest.java diff --git a/src/main/java/com/ftm/server/domain/entity/User.java b/src/main/java/com/ftm/server/domain/entity/User.java index 65f21e1..8d83d7d 100644 --- a/src/main/java/com/ftm/server/domain/entity/User.java +++ b/src/main/java/com/ftm/server/domain/entity/User.java @@ -118,4 +118,15 @@ public static User createGeneralUser(GeneralUserCreationCommand command) { .role(UserRole.USER) .build(); } + + public static User createTestKakaoUser() { + return User.builder() + .nickname("test") + .socialProvider(SocialProvider.KAKAO) + .socialId("test_kakao_id") + .groomingScore(0) + .isDeleted(false) + .role(UserRole.USER) + .build(); + } } diff --git a/src/test/java/com/ftm/server/auth/KakaoLoginTest.java b/src/test/java/com/ftm/server/auth/KakaoLoginTest.java new file mode 100644 index 0000000..adf48e2 --- /dev/null +++ b/src/test/java/com/ftm/server/auth/KakaoLoginTest.java @@ -0,0 +1,212 @@ +package com.ftm.server.auth; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doThrow; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.JsonFieldType.*; +import static org.springframework.restdocs.payload.JsonFieldType.STRING; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.ftm.server.BaseTest; +import com.ftm.server.application.dto.command.KakaoAuthCommand; +import com.ftm.server.application.port.SocialAuthClientPort; +import com.ftm.server.application.port.repository.UserImageRepository; +import com.ftm.server.application.port.repository.UserRepository; +import com.ftm.server.common.exception.CustomException; +import com.ftm.server.common.response.enums.ErrorResponseCode; +import com.ftm.server.domain.entity.User; +import com.ftm.server.domain.entity.UserImage; +import com.ftm.server.infrastructure.oauth.kakao.KakaoAuthUser; +import com.ftm.server.web.dto.request.KakaoLoginRequest; +import java.util.List; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.FieldDescriptor; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; + +public class KakaoLoginTest extends BaseTest { + + @MockitoBean private SocialAuthClientPort kakaoClient; + + @Autowired private UserRepository userRepository; + @Autowired private UserImageRepository userImageRepository; + + private final List requestFieldKakaoLogin = + List.of(fieldWithPath("authorizationCode").type(STRING).description("카카오 인증 코드")); + + private final List responseFieldKakaoLogin = + List.of( + fieldWithPath("status").type(NUMBER).description("응답 상태"), + fieldWithPath("code").type(STRING).description("상태 코드"), + fieldWithPath("message").type(STRING).description("메시지"), + fieldWithPath("data").type(OBJECT).optional().description("data"), + fieldWithPath("data.id").type(NUMBER).description("유저 ID"), + fieldWithPath("data.nickname").type(STRING).description("유저 닉네임"), + fieldWithPath("data.socialProvider").type(STRING).description("소셜 제공자"), + fieldWithPath("data.profileImageUrl") + .type(STRING) + .description("유저 프로필 이미지 URL"), + fieldWithPath("data.mildLevelName") + .type(STRING) + .optional() + .description("순한맛 그루밍 레벨 이름"), + fieldWithPath("data.spicyLevelName") + .type(STRING) + .optional() + .description("매운맛 그루밍 레벨 이름"), + fieldWithPath("data.loginTime").type(STRING).description("로그인 시간")); + + private ResultActions getResultActions(KakaoLoginRequest request) throws Exception { + return mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/auth/login/kakao") + .contentType(APPLICATION_JSON_VALUE) + .content(mapper.writeValueAsString(request))); + } + + private RestDocumentationResultHandler getDocument( + Integer identifier, String sessionDescription) { + return document( + "kakaoLogin/" + identifier, + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint(), getModifiedHeader()), + requestFields(requestFieldKakaoLogin), + responseHeaders( + headerWithName("Set-Cookie").description(sessionDescription).optional()), + responseFields(responseFieldKakaoLogin), + resource( + ResourceSnippetParameters.builder() + .tag("인증/인가") + .summary("카카오 로그인 api") + .description("카카오 로그인 api 입니다.") + .responseFields(responseFieldKakaoLogin) + .build())); + } + + @Test + @Transactional + void 카카오_로그인_성공1() throws Exception { + // given + User testUser = userRepository.save(User.createTestKakaoUser()); + UserImage testUserImage = UserImage.createUserImage(testUser); + userImageRepository.save(testUserImage); + + KakaoLoginRequest request = new KakaoLoginRequest("test_code"); + KakaoAuthUser testKakaoUser = KakaoAuthUser.from("test_kakao_id"); + given(kakaoClient.authenticate(any(KakaoAuthCommand.class))).willReturn(testKakaoUser); + + // when + ResultActions resultActions = getResultActions(request); + MvcResult result = resultActions.andReturn(); + + // 세션 쿠키 수동 추가 (문서화 통과용) + result.getResponse().addHeader("Set-Cookie", "SESSION=mock-session-id; Path=/; HttpOnly"); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(header().string("Set-Cookie", Matchers.containsString("SESSION"))) + .andDo(print()); + + // documentation + resultActions.andDo(getDocument(1, "SESSION 쿠키: 로그인 성공 시 발급되는 세션 쿠키 (기본 만료 시간 30분)")); + } + + @Test + @Transactional + void 카카오_로그인_성공2() throws Exception { + // given + KakaoLoginRequest request = new KakaoLoginRequest("test_code"); + KakaoAuthUser testKakaoUser = KakaoAuthUser.from("test_kakao_id"); + given(kakaoClient.authenticate(any(KakaoAuthCommand.class))).willReturn(testKakaoUser); + + // when + ResultActions resultActions = getResultActions(request); + MvcResult result = resultActions.andReturn(); + + // 세션 쿠키 수동 추가 (문서화 통과용) + result.getResponse().addHeader("Set-Cookie", "SESSION=mock-session-id; Path=/; HttpOnly"); + + // then + resultActions + .andExpect(status().isAccepted()) + .andExpect(header().string("Set-Cookie", Matchers.containsString("SESSION"))) + .andDo(print()); + + // documentation + resultActions.andDo(getDocument(2, "SESSION 쿠키: 소셜 회원가입이 필요한 사용자를 위한 임시 세션 (만료 시간 5분)")); + } + + @Test + @Transactional + void 카카오_로그인_실패1() throws Exception { + // given + KakaoLoginRequest request = new KakaoLoginRequest("test_code"); + doThrow(new CustomException(ErrorResponseCode.KAKAO_AUTH_TOKEN_EXCHANGE_FAILED)) + .when(kakaoClient) + .authenticate(any(KakaoAuthCommand.class)); + + // when + ResultActions resultActions = getResultActions(request); + + // then + resultActions + .andExpect( + status().is( + ErrorResponseCode.KAKAO_AUTH_TOKEN_EXCHANGE_FAILED + .getHttpStatus() + .value())) + .andExpect( + jsonPath("code") + .value( + ErrorResponseCode.KAKAO_AUTH_TOKEN_EXCHANGE_FAILED + .getCode())) + .andDo(print()); + + // documentation + resultActions.andDo(getDocument(3, "")); + } + + @Test + @Transactional + void 카카오_로그인_실패2() throws Exception { + // given + KakaoLoginRequest request = new KakaoLoginRequest("test_code"); + doThrow(new CustomException(ErrorResponseCode.KAKAO_USER_PROFILE_FETCH_FAILED)) + .when(kakaoClient) + .authenticate(any(KakaoAuthCommand.class)); + + // when + ResultActions resultActions = getResultActions(request); + + // then + resultActions + .andExpect( + status().is( + ErrorResponseCode.KAKAO_USER_PROFILE_FETCH_FAILED + .getHttpStatus() + .value())) + .andExpect( + jsonPath("code") + .value(ErrorResponseCode.KAKAO_USER_PROFILE_FETCH_FAILED.getCode())) + .andDo(print()); + + // documentation + resultActions.andDo(getDocument(4, "")); + } +} From 690eeeaa24bc4d4f6c0b46dc3fcb1fe446cf3d8c Mon Sep 17 00:00:00 2001 From: songhyeonpk Date: Fri, 28 Mar 2025 16:50:55 +0900 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20api=20=EB=AC=B8=EC=84=9C=ED=99=94?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/auth-api.adoc | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/auth-api.adoc b/src/docs/asciidoc/auth-api.adoc index 36eb9a1..9c6d731 100644 --- a/src/docs/asciidoc/auth-api.adoc +++ b/src/docs/asciidoc/auth-api.adoc @@ -12,4 +12,27 @@ include::{snippetsDir}/loginUser/1/http-response.adoc[] include::{snippetsDir}/loginUser/1/response-fields.adoc[] ==== 실패 Response -include::{snippetsDir}/loginUser/2/http-response.adoc[] \ No newline at end of file +include::{snippetsDir}/loginUser/2/http-response.adoc[] + +--- + +=== **2. 카카오 로그인** + +카카오 로그인 api 입니다. + +==== Request +include::{snippetsDir}/kakaoLogin/1/http-request.adoc[] + +==== 성공 Response +성공 1. 카카오 로그인 성공, 서비스에 가입된 유저인 경우 +include::{snippetsDir}/kakaoLogin/1/http-response.adoc[] +include::{snippetsDir}/kakaoLogin/1/response-fields.adoc[] + +성공 2. 카카오 로그인은 성공했지만, 서비스에 가입된 유저가 아닌 경우 (세션에 카카오 유저 정보를 임시로 등록하고 응답 쿠키에 SESSION 등록, 만료시간 5분) +include::{snippetsDir}/kakaoLogin/2/http-response.adoc[] + +==== 실패 Response +실패 1. +include::{snippetsDir}/kakaoLogin/3/http-response.adoc[] +실패 2. +include::{snippetsDir}/kakaoLogin/4/http-response.adoc[]