Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/docs/asciidoc/auth-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@ include::{snippetsDir}/kakaoLogin/2/response-headers.adoc[]
include::{snippetsDir}/kakaoLogin/1/response-fields.adoc[]

==== 실패 Response
실패 1.
실패 1. 유효하지 않은 인가 코드일 경우 예외가 발생한다.
include::{snippetsDir}/kakaoLogin/3/http-response.adoc[]
실패 2.
실패 2. 유효하지 않은 리다이렉트 키일 경우 에외가 발생한다. (허용 키 : test, local, dev)
include::{snippetsDir}/kakaoLogin/4/http-response.adoc[]
실패 3. 카카오 서버로부터 토큰 조회에 실패할 경우 예외가 발생한다.
include::{snippetsDir}/kakaoLogin/5/http-response.adoc[]
실패 4. 카카오 서버로부터 유저 정보 조회에 실패할 경우 예외가 발생한다.
include::{snippetsDir}/kakaoLogin/6/http-response.adoc[]

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -34,7 +35,7 @@ public class KakaoLoginController {

@PostMapping("/api/auth/login/kakao")
public ResponseEntity<ApiResponse<SocialLoginResponse>> kakaoLogin(
@RequestBody KakaoLoginRequest request,
@RequestBody @Valid KakaoLoginRequest request,
HttpServletRequest req,
HttpServletResponse res) {
SocialLoginOutcomeVo result = kakaoLoginUseCase.execute(KakaoLoginCommand.from(request));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package com.ftm.server.adapter.in.web.auth.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class KakaoLoginRequest {

@NotBlank(message = "인가 코드는 필수 요청 정보입니다.")
private final String authorizationCode;

@NotBlank(message = "리다이렉트 키는 필수 요청 정보입니다.")
@Pattern(regexp = "^(test|local|dev)$", message = "리다이렉트 키는 test, local, dev만 허용됩니다.")
private final String redirectKey;
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,31 @@ public class KakaoOAuthClientAdapter
@Override
public KakaoAuthUser authenticate(KakaoLoginCommand command) {
// Access Token 요청
KakaoTokenResponse token = getKakaoToken(command.getAuthorizationCode());
String redirectUri = kakaoProperties.getRedirectUriByEnv(command.getRedirectEnv());
KakaoTokenResponse token = getKakaoToken(command.getAuthorizationCode(), redirectUri);

// 카카오 유저 정보 요청
KakaoUserInfoResponse userInfo = getKakaoUserInfo(token.getAccessToken());

return KakaoAuthUser.from(userInfo.getId());
}

private KakaoTokenResponse getKakaoToken(String authorizationCode) {
return requestToken(authorizationCode).block();
private KakaoTokenResponse getKakaoToken(String authorizationCode, String redirectUri) {
return requestToken(authorizationCode, redirectUri).block();
}

private KakaoUserInfoResponse getKakaoUserInfo(String accessToken) {
return requestUserInfo(accessToken).block();
}

private Mono<KakaoTokenResponse> requestToken(String authorizationCode) {
private Mono<KakaoTokenResponse> requestToken(String authorizationCode, String redirectUri) {
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("redirect_uri", redirectUri)
.with("code", authorizationCode)
.with("client_secret", kakaoProperties.getClientSecret()))
.retrieve()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package com.ftm.server.application.command.auth;

import com.ftm.server.adapter.in.web.auth.dto.request.KakaoLoginRequest;
import com.ftm.server.domain.enums.RedirectEnv;
import lombok.Getter;

@Getter
public class KakaoLoginCommand extends SocialLoginCommand {

private KakaoLoginCommand(String authorizationCode) {
private final RedirectEnv redirectEnv;

private KakaoLoginCommand(String authorizationCode, String redirectKey) {
super(authorizationCode);
this.redirectEnv = RedirectEnv.from(redirectKey);
}

public static KakaoLoginCommand from(KakaoLoginRequest request) {
return new KakaoLoginCommand(request.getAuthorizationCode());
return new KakaoLoginCommand(request.getAuthorizationCode(), request.getRedirectKey());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public enum ErrorResponseCode {
BAD_REQUEST_PAGING_INDEX_RANGE(HttpStatus.BAD_REQUEST, "E400_011", "페이지 번호는 최소 0 이상이여야 합니다."),
BAD_REQUEST_PAGING_SIZE_RANGE(
HttpStatus.BAD_REQUEST, "E400_012", "페이지당 개수는 최소 1 이상, 10 이하여야 합니다."),
INVALID_REDIRECT_ENV(HttpStatus.BAD_REQUEST, "E400_013", "유효하지 않은 리다이렉트 환경입니다."),

// 401번
NOT_AUTHENTICATED(HttpStatus.UNAUTHORIZED, "E401_001", "인증되지 않은 사용자입니다."),
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/com/ftm/server/domain/enums/RedirectEnv.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.ftm.server.domain.enums;

import com.ftm.server.common.exception.CustomException;
import com.ftm.server.common.response.enums.ErrorResponseCode;
import java.util.Locale;

public enum RedirectEnv {
TEST,
LOCAL,
DEV;

public static RedirectEnv from(String value) {
if (value == null || value.isBlank()) {
return LOCAL;
}

return switch (value.trim().toLowerCase(Locale.ROOT)) {
case "test" -> TEST;
case "local" -> LOCAL;
case "dev" -> DEV;
default -> throw new CustomException(ErrorResponseCode.INVALID_REDIRECT_ENV);
};
}

public String key() {
return name().toLowerCase();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.ftm.server.infrastructure.oauth.kakao;

import com.ftm.server.domain.enums.RedirectEnv;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
Expand All @@ -13,7 +15,16 @@ public class KakaoProperties {

private String clientId;
private String clientSecret;
private String redirectUri;
private String tokenUri;
private String userInfoUri;
private Map<String, String> redirectUris;

public String getRedirectUriByEnv(RedirectEnv env) {
String redirectUri = redirectUris.get(env.key());
if (redirectUri != null && !redirectUri.isBlank()) {
return redirectUri;
}

return redirectUris.get("local");
}
}
7 changes: 5 additions & 2 deletions src/main/resources/application-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ spring:
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"
user-info-uri: "https://kapi.kakao.com/v2/user/me"
redirect-uris:
test: "http://localhost:8080/api/auth/kakao/callback"
local: "http://localhost:3000/api/auth/kakao/callback"
dev: "https://ftm-client.vercel.app/api/auth/kakao/callback"
70 changes: 62 additions & 8 deletions src/test/java/com/ftm/server/auth/KakaoLoginTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ public class KakaoLoginTest extends BaseTest {
@Autowired private SaveUserImagePort saveUserImagePort;

private final List<FieldDescriptor> requestFieldKakaoLogin =
List.of(fieldWithPath("authorizationCode").type(STRING).description("카카오 인증 코드"));
List.of(
fieldWithPath("authorizationCode").type(STRING).description("카카오 인증 코드"),
fieldWithPath("redirectKey")
.type(STRING)
.description("리다이렉트 키 (test, local, dev)"));

private final List<FieldDescriptor> responseFieldKakaoLogin =
List.of(
Expand Down Expand Up @@ -105,7 +109,7 @@ private RestDocumentationResultHandler getDocument(
UserImage userImage = UserImage.createUserImage(testUser.getId());
saveUserImagePort.saveUserDefaultImage(userImage);

KakaoLoginRequest request = new KakaoLoginRequest("test_code");
KakaoLoginRequest request = new KakaoLoginRequest("test_code", "test");
KakaoAuthUser testKakaoUser = KakaoAuthUser.from("test_kakao_id");
given(kakaoOAuthClientPort.authenticate(any(KakaoLoginCommand.class)))
.willReturn(testKakaoUser);
Expand All @@ -132,7 +136,7 @@ private RestDocumentationResultHandler getDocument(
@Transactional
void 카카오_로그인_성공2() throws Exception {
// given
KakaoLoginRequest request = new KakaoLoginRequest("test_code");
KakaoLoginRequest request = new KakaoLoginRequest("test_code", "test");
KakaoAuthUser testKakaoUser = KakaoAuthUser.from("test_kakao_id");
given(kakaoOAuthClientPort.authenticate(any(KakaoLoginCommand.class)))
.willReturn(testKakaoUser);
Expand Down Expand Up @@ -160,7 +164,57 @@ private RestDocumentationResultHandler getDocument(
@Transactional
void 카카오_로그인_실패1() throws Exception {
// given
KakaoLoginRequest request = new KakaoLoginRequest("test_code");
KakaoLoginRequest request = new KakaoLoginRequest("", "test");

// when
ResultActions resultActions = getResultActions(request);

// then
resultActions
.andExpect(
status().is(
ErrorResponseCode.INVALID_REQUEST_ARGUMENT
.getHttpStatus()
.value()))
.andExpect(
jsonPath("code")
.value(ErrorResponseCode.INVALID_REQUEST_ARGUMENT.getCode()))
.andDo(print());

// documentation
resultActions.andDo(getDocument(3, ""));
}

@Test
@Transactional
void 카카오_로그인_실패2() throws Exception {
// given
KakaoLoginRequest request = new KakaoLoginRequest("test_code", "abc");

// when
ResultActions resultActions = getResultActions(request);

// then
resultActions
.andExpect(
status().is(
ErrorResponseCode.INVALID_REQUEST_ARGUMENT
.getHttpStatus()
.value()))
.andExpect(
jsonPath("code")
.value(ErrorResponseCode.INVALID_REQUEST_ARGUMENT.getCode()))
.andDo(print());

// documentation
resultActions.andDo(getDocument(4, ""));
}

@Test
@Transactional
void 카카오_로그인_실패3() throws Exception {
// given
KakaoLoginRequest request = new KakaoLoginRequest("test_code", "test");
doThrow(new CustomException(ErrorResponseCode.KAKAO_AUTH_TOKEN_EXCHANGE_FAILED))
.when(kakaoOAuthClientPort)
.authenticate(any(KakaoLoginCommand.class));
Expand All @@ -183,14 +237,14 @@ private RestDocumentationResultHandler getDocument(
.andDo(print());

// documentation
resultActions.andDo(getDocument(3, ""));
resultActions.andDo(getDocument(5, ""));
}

@Test
@Transactional
void 카카오_로그인_실패2() throws Exception {
void 카카오_로그인_실패4() throws Exception {
// given
KakaoLoginRequest request = new KakaoLoginRequest("test_code");
KakaoLoginRequest request = new KakaoLoginRequest("test_code", "test");
doThrow(new CustomException(ErrorResponseCode.KAKAO_USER_PROFILE_FETCH_FAILED))
.when(kakaoOAuthClientPort)
.authenticate(any(KakaoLoginCommand.class));
Expand All @@ -211,6 +265,6 @@ private RestDocumentationResultHandler getDocument(
.andDo(print());

// documentation
resultActions.andDo(getDocument(4, ""));
resultActions.andDo(getDocument(6, ""));
}
}
Loading