Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 로그인시 클라이언트로부터 callbackuri 추가로 받아 사용 #456

Merged
merged 2 commits into from
Oct 18, 2023
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
2 changes: 2 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ jacocoTestReport {
'**/*Response*',
'**/*Request*',
'**/BaseTimeEntity',
'**/KakaoOAuthClient',
'**/*Dto*',
'**/S3*',
'**/*Interceptor*',
Expand Down Expand Up @@ -151,6 +152,7 @@ jacocoTestCoverageVerification {
'*.*Exception*',
'*.*Dto',
'*.S3*',
'*.KakaoOAuthClient*',
'*.*Response',
'*.*Request',
'*.BaseTimeEntity',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ public void logout(Long memberId) {
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import zipgo.auth.application.dto.OAuthMemberResponse;
import zipgo.auth.domain.OAuthClient;
import zipgo.auth.dto.TokenDto;

@Service
Expand All @@ -12,9 +13,8 @@ public class AuthServiceFacade {
private final AuthService authService;
private final OAuthClient oAuthClient;

public TokenDto login(String authCode) {
String accessToken = oAuthClient.getAccessToken(authCode);
OAuthMemberResponse oAuthMemberResponse = oAuthClient.getMember(accessToken);
public TokenDto login(String authCode, String redirectUri) {
OAuthMemberResponse oAuthMemberResponse = oAuthClient.request(authCode, redirectUri);
return authService.login(oAuthMemberResponse);
}

Expand Down
9 changes: 9 additions & 0 deletions backend/src/main/java/zipgo/auth/domain/OAuthClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package zipgo.auth.domain;

import zipgo.auth.application.dto.OAuthMemberResponse;

public interface OAuthClient {

OAuthMemberResponse request(String authCode, String redirectUri);

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package zipgo.auth.application;
package zipgo.auth.domain;

import zipgo.auth.application.dto.OAuthMemberResponse;

public interface OAuthClient {

String getAccessToken(String authCode);
public interface OAuthMemberInfoClient {

OAuthMemberResponse getMember(String accessToken);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package zipgo.auth.domain;

public interface OAuthTokenClient {

String getAccessToken(String authCode, String redirectUri);

}
Original file line number Diff line number Diff line change
@@ -1,101 +1,23 @@
package zipgo.auth.infra.kakao;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import zipgo.auth.application.OAuthClient;
import zipgo.auth.application.dto.OAuthMemberResponse;
import zipgo.auth.exception.OAuthResourceNotBringException;
import zipgo.auth.exception.OAuthTokenNotBringException;
import zipgo.auth.infra.kakao.config.KakaoCredentials;
import zipgo.auth.infra.kakao.dto.KakaoMemberResponse;
import zipgo.auth.infra.kakao.dto.KakaoTokenResponse;

import static java.util.Objects.requireNonNull;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
import zipgo.auth.domain.OAuthClient;
import zipgo.auth.domain.OAuthMemberInfoClient;
import zipgo.auth.domain.OAuthTokenClient;

@Component
@RequiredArgsConstructor
public class KakaoOAuthClient implements OAuthClient {

private static final String ACCESS_TOKEN_URI = "https://kauth.kakao.com/oauth/token";
private static final String USER_INFO_URI = "https://kapi.kakao.com/v2/user/me";
private static final String GRANT_TYPE = "authorization_code";

private final KakaoCredentials kakaoCredentials;
private final RestTemplate restTemplate;

@Override
public String getAccessToken(String authCode) {
HttpHeaders header = createRequestHeader();
MultiValueMap<String, String> body = createRequestBodyWithAuthCode(authCode);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, header);
ResponseEntity<KakaoTokenResponse> kakaoTokenResponse = getKakaoToken(request);

return requireNonNull(requireNonNull(kakaoTokenResponse.getBody())).accessToken();
}

private HttpHeaders createRequestHeader() {
HttpHeaders header = new HttpHeaders();
header.setContentType(APPLICATION_FORM_URLENCODED);
return header;
}

private MultiValueMap<String, String> createRequestBodyWithAuthCode(String authCode) {
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", GRANT_TYPE);
body.add("client_id", kakaoCredentials.getClientId());
body.add("redirect_uri", kakaoCredentials.getRedirectUri());
body.add("client_secret", kakaoCredentials.getClientSecret());
body.add("code", authCode);
return body;
}

private ResponseEntity<KakaoTokenResponse> getKakaoToken(HttpEntity<MultiValueMap<String, String>> request) {
try {
return restTemplate.exchange(
ACCESS_TOKEN_URI,
POST,
request,
KakaoTokenResponse.class
);
} catch (HttpClientErrorException e) {
throw new OAuthTokenNotBringException();
}
}
private final OAuthTokenClient kakaoOAuthTokenClient;
private final OAuthMemberInfoClient kakaoOAuthMemberInfoClient;

@Override
public OAuthMemberResponse getMember(String accessToken) {
HttpEntity<HttpHeaders> request = createRequest(accessToken);
ResponseEntity<KakaoMemberResponse> response = getKakaoMember(request);
return response.getBody();
}

private HttpEntity<HttpHeaders> createRequest(String accessToken) {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
return new HttpEntity<>(headers);
}

private ResponseEntity<KakaoMemberResponse> getKakaoMember(HttpEntity<HttpHeaders> request) {
try {
return restTemplate.exchange(
USER_INFO_URI,
GET,
request,
KakaoMemberResponse.class
);
} catch (HttpClientErrorException e) {
throw new OAuthResourceNotBringException();
}
public OAuthMemberResponse request(String authCode, String redirectUri) {
String accessToken = kakaoOAuthTokenClient.getAccessToken(authCode, redirectUri);
return kakaoOAuthMemberInfoClient.getMember(accessToken);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package zipgo.auth.infra.kakao;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import zipgo.auth.application.dto.OAuthMemberResponse;
import zipgo.auth.domain.OAuthMemberInfoClient;
import zipgo.auth.exception.OAuthResourceNotBringException;
import zipgo.auth.infra.kakao.dto.KakaoMemberResponse;

import static org.springframework.http.HttpMethod.GET;

@Component
@RequiredArgsConstructor
public class KakaoOAuthMemberInfoClient implements OAuthMemberInfoClient {

private static final String USER_INFO_URI = "https://kapi.kakao.com/v2/user/me";

private final RestTemplate restTemplate;

@Override
public OAuthMemberResponse getMember(String accessToken) {
HttpEntity<HttpHeaders> request = createRequest(accessToken);
ResponseEntity<KakaoMemberResponse> response = getKakaoMember(request);
return response.getBody();
}

private HttpEntity<HttpHeaders> createRequest(String accessToken) {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
return new HttpEntity<>(headers);
}

private ResponseEntity<KakaoMemberResponse> getKakaoMember(HttpEntity<HttpHeaders> request) {
try {
return restTemplate.exchange(
USER_INFO_URI,
GET,
request,
KakaoMemberResponse.class
);
} catch (HttpClientErrorException e) {
throw new OAuthResourceNotBringException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package zipgo.auth.infra.kakao;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import zipgo.auth.domain.OAuthTokenClient;
import zipgo.auth.exception.OAuthTokenNotBringException;
import zipgo.auth.infra.kakao.config.KakaoCredentials;
import zipgo.auth.infra.kakao.dto.KakaoTokenResponse;

import static java.util.Objects.requireNonNull;
import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;


@Component
@RequiredArgsConstructor
public class KakaoOAuthTokenClient implements OAuthTokenClient {

private static final String ACCESS_TOKEN_URI = "https://kauth.kakao.com/oauth/token";
private static final String GRANT_TYPE = "authorization_code";

private final RestTemplate restTemplate;
private final KakaoCredentials kakaoCredentials;

@Override
public String getAccessToken(String authCode, String redirectUri) {
HttpHeaders header = createRequestHeader();
MultiValueMap<String, String> body = createRequestBodyWithAuthCode(authCode, redirectUri);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, header);
ResponseEntity<KakaoTokenResponse> kakaoTokenResponse = getKakaoToken(request);

return requireNonNull(requireNonNull(kakaoTokenResponse.getBody())).accessToken();
}

private HttpHeaders createRequestHeader() {
HttpHeaders header = new HttpHeaders();
header.setContentType(APPLICATION_FORM_URLENCODED);
return header;
}

private MultiValueMap<String, String> createRequestBodyWithAuthCode(String authCode, String redirectUri) {
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", GRANT_TYPE);
body.add("client_id", kakaoCredentials.getClientId());
body.add("redirect_uri", redirectUri);
body.add("client_secret", kakaoCredentials.getClientSecret());
body.add("code", authCode);
return body;
}

private ResponseEntity<KakaoTokenResponse> getKakaoToken(HttpEntity<MultiValueMap<String, String>> request) {
try {
return restTemplate.exchange(
ACCESS_TOKEN_URI,
POST,
request,
KakaoTokenResponse.class
);
} catch (HttpClientErrorException e) {
throw new OAuthTokenNotBringException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,18 @@
@RequiredArgsConstructor
public class AuthController {

private final AuthServiceFacade authServiceFacade;
private final JwtProvider jwtProvider;
private final RefreshTokenCookieProvider refreshTokenCookieProvider;
private final AuthServiceFacade authServiceFacade;
private final MemberQueryService memberQueryService;
private final PetQueryService petQueryService;

@PostMapping("/login")
public ResponseEntity<LoginResponse> login(@RequestParam("code") String authCode) {
TokenDto tokenDto = authServiceFacade.login(authCode);
public ResponseEntity<LoginResponse> login(
@RequestParam("code") String authCode,
@RequestParam("redirect-uri") String redirectUri
) {
TokenDto tokenDto = authServiceFacade.login(authCode, redirectUri);
ResponseCookie cookie = refreshTokenCookieProvider.createCookie(tokenDto.refreshToken());

String memberId = jwtProvider.getPayload(tokenDto.accessToken());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.mockito.junit.jupiter.MockitoExtension;
import zipgo.auth.application.dto.OAuthMemberResponse;
import zipgo.auth.application.fixture.MemberResponseSuccessFixture;
import zipgo.auth.domain.OAuthClient;
import zipgo.auth.dto.TokenDto;
import zipgo.auth.exception.OAuthResourceNotBringException;
import zipgo.auth.exception.OAuthTokenNotBringException;
Expand Down Expand Up @@ -38,16 +39,14 @@ class AuthServiceFacadeTest {
@Test
void 로그인에_성공하면_토큰을_발급한다() {
// given
when(oAuthClient.getAccessToken("인가 코드"))
.thenReturn("엑세스 토큰");
OAuthMemberResponse 서드파티_사용자_응답 = new MemberResponseSuccessFixture();
when(oAuthClient.getMember("엑세스 토큰"))
.thenReturn(서드파티_사용자_응답);
when(authService.login(서드파티_사용자_응답))
OAuthMemberResponse oAuthMemberResponse = new MemberResponseSuccessFixture();
when(oAuthClient.request("인가 코드", "리다이렉트 유알아이"))
.thenReturn(oAuthMemberResponse);
when(authService.login(oAuthMemberResponse))
.thenReturn(TokenDto.of("생성된 엑세스 토큰", "생성된 리프레시 토큰"));

// when
TokenDto 토큰 = authServiceFacade.login("인가 코드");
TokenDto 토큰 = authServiceFacade.login("인가 코드", "리다이렉트 유알아이");

// then
assertAll(
Expand All @@ -59,25 +58,23 @@ class AuthServiceFacadeTest {
@Test
void 엑세스_토큰을_가져오지_못하면_예외가_발생한다() {
// given
when(oAuthClient.getAccessToken("인가 코드"))
when(oAuthClient.request("인가 코드", "리다이렉트 유알아이"))
.thenThrow(new OAuthTokenNotBringException());

// expect
assertThatThrownBy(() -> authServiceFacade.login("인가 코드"))
assertThatThrownBy(() -> authServiceFacade.login("인가 코드", "리다이렉트 유알아이"))
.isInstanceOf(OAuthTokenNotBringException.class)
.hasMessageContaining("서드파티 서비스에서 토큰을 받아오지 못했습니다. 잠시후 다시 시도해주세요.");
}

@Test
void 사용자_정보를_가져오지_못하면_예외가_발생한다() {
// given
when(oAuthClient.getAccessToken("인가 코드"))
.thenReturn("엑세스 토큰");
when(oAuthClient.getMember("엑세스 토큰"))
when(oAuthClient.request("인가 코드", "리다이렉트 유알아이"))
.thenThrow(new OAuthResourceNotBringException());

// expect
assertThatThrownBy(() -> authServiceFacade.login("인가 코드"))
assertThatThrownBy(() -> authServiceFacade.login("인가 코드", "리다이렉트 유알아이"))
.isInstanceOf(OAuthResourceNotBringException.class)
.hasMessageContaining("서드파티 서비스에서 정보를 받아오지 못했습니다. 잠시후 다시 시도해주세요");
}
Expand Down
Loading