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

[FEAT, REFACTOR] 소셜 토큰 재발급 기능 추가 및 기존 토큰 재발급에 사용되는 DTO명 수정 #252

Merged
merged 18 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9aebd0c
Merge branch 'feature/236-logout-social-token-expire' into feature/24…
kmw2378 May 27, 2024
a7b02f5
[refactor]기존 OAuthReissueRequest/Response 작명을 ReissueRequest/Response…
kmw2378 May 27, 2024
9660e95
[feat]RefreshTokenDto.from() 메서드 추가 (#241)
kmw2378 May 27, 2024
4694845
[refactor]OAuthTokenRequest 필드 및 메서드 수정 (#241)
kmw2378 May 27, 2024
0ca6c18
[refactor]OAuthTokenResponse 필드 및 메서드 수정 (#241)
kmw2378 May 27, 2024
143331e
[feat]OAuthWebClientService.issueToken() 메서드 추가 (#241)
kmw2378 May 27, 2024
d93a0c7
[feat]OAuthService.socialReissue() 메서드 추가 (#241)
kmw2378 May 27, 2024
6bbc598
[feat]OAuthController.socialReissue() 메서드 추가 (#241)
kmw2378 May 27, 2024
1aa1f35
[refactor]application-dev.yml에 카카오 authorization-grant-type 속성 수정 (#241)
kmw2378 May 27, 2024
9bc486c
[test]OAuthServiceTset에 소셜 토큰 재발급 테스트 코드 추가 (#241)
kmw2378 May 27, 2024
3dcfd87
Merge branch 'develop' of https://github.com/KakaoFunding/back-end in…
kmw2378 May 27, 2024
ad65281
[feat]OAuthReissueRequest 추가 (#241)
kmw2378 May 27, 2024
9366ff3
[feat]OAuthReissueResponse 추가 (#241)
kmw2378 May 27, 2024
f5efe77
[fix]충돌 해결 (#241)
kmw2378 May 29, 2024
1eb879b
Merge branch 'develop' into feature/241-social-token-reissue
kmw2378 May 31, 2024
83de67e
Merge branch 'develop' into feature/241-social-token-reissue
kmw2378 May 31, 2024
a04a764
Merge branch 'develop' into feature/241-social-token-reissue
kmw2378 May 31, 2024
8723d20
Merge branch 'develop' into feature/241-social-token-reissue
kmw2378 May 31, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.kakaoshare.backend.domain.member.dto.oauth.authenticate.OAuthLoginRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.authenticate.OAuthLoginResponse;
import org.kakaoshare.backend.domain.member.dto.oauth.issue.OAuthReissueRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.issue.ReissueRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.logout.OAuthLogoutRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.logout.OAuthSocialLogoutRequest;
import org.kakaoshare.backend.domain.member.service.oauth.OAuthService;
Expand Down Expand Up @@ -38,8 +39,14 @@ public ResponseEntity<Void> socialLogout(@RequestBody final OAuthSocialLogoutReq
}

@PostMapping("/reissue")
public ResponseEntity<?> reissue(@RequestBody final OAuthReissueRequest oAuthReissueRequest) {
public ResponseEntity<?> reissue(@RequestBody final ReissueRequest reissueRequest) {
return ResponseEntity.ok()
.body(oAuthService.reissue(oAuthReissueRequest));
.body(oAuthService.reissue(reissueRequest));
}

@PostMapping("/social/reissue")
public ResponseEntity<?> socialReissue(@RequestBody final OAuthReissueRequest oAuthReissueRequest) {
return ResponseEntity.ok()
.body(oAuthService.socialReissue(oAuthReissueRequest));
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.kakaoshare.backend.domain.member.dto.oauth.issue;

public record OAuthReissueRequest(String refreshToken) {
public record OAuthReissueRequest(String provider, String refreshToken) {
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package org.kakaoshare.backend.domain.member.dto.oauth.issue;

import org.kakaoshare.backend.domain.member.dto.oauth.token.OAuthTokenResponse;
import org.kakaoshare.backend.domain.member.dto.oauth.token.RefreshTokenDto;
import org.kakaoshare.backend.domain.member.entity.token.RefreshToken;

public record OAuthReissueResponse(String accessToken, RefreshTokenDto refreshToken) {
public static OAuthReissueResponse of(final String accessToken, final RefreshToken refreshToken) {
return new OAuthReissueResponse(accessToken, RefreshTokenDto.from(refreshToken));
public static OAuthReissueResponse from(final OAuthTokenResponse oAuthTokenResponse) {
return new OAuthReissueResponse(oAuthTokenResponse.access_token(), RefreshTokenDto.from(oAuthTokenResponse));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.kakaoshare.backend.domain.member.dto.oauth.issue;

public record ReissueRequest(String refreshToken) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.kakaoshare.backend.domain.member.dto.oauth.issue;

import org.kakaoshare.backend.domain.member.dto.oauth.token.RefreshTokenDto;
import org.kakaoshare.backend.domain.member.entity.token.RefreshToken;

public record ReissueResponse(String accessToken, RefreshTokenDto refreshToken) {
public static ReissueResponse of(final String accessToken, final RefreshToken refreshToken) {
return new ReissueResponse(accessToken, RefreshTokenDto.from(refreshToken));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@
import org.springframework.security.oauth2.client.registration.ClientRegistration;

@Builder
public record OAuthTokenRequest(String code, String client_id, String client_secret, String grant_type, String redirect_id) {
public static OAuthTokenRequest of(final ClientRegistration registration,
final String code) {
public record OAuthTokenRequest(String grant_type, String client_id, String refresh_token, String client_secret) {
public static OAuthTokenRequest of(final ClientRegistration registration, final String refreshToken) {
return OAuthTokenRequest.builder()
.code(code)
.grant_type(registration.getAuthorizationGrantType().getValue())
.client_id(registration.getClientId())
.refresh_token(refreshToken)
.client_secret(registration.getClientSecret())
.grant_type(registration.getAuthorizationGrantType().getValue())
.redirect_id(registration.getRedirectUri())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package org.kakaoshare.backend.domain.member.dto.oauth.token;

import lombok.Builder;

@Builder
public record OAuthTokenResponse(String access_token, String refresh_token, Long expires_in) {
public static OAuthTokenResponse of(final String accessToken) {
return OAuthTokenResponse.builder()
.access_token(accessToken)
.build();
}
/**
* @author kim-minwoo
* @param refresh_token 요청 시 사용된 리프레시 토큰의 만료 시간이 1개월 미만으로 남았을 때만 갱신되어 전달. 아닌 경우 null
* @param refresh_token_expires_in 요청 시 사용된 리프레시 토큰의 만료 시간이 1개월 미만으로 남았을 때만 갱신되어 전달. 아닌 경우 null
*/
public record OAuthTokenResponse(String token_type, String access_token, String refresh_token, Long expires_in, Long refresh_token_expires_in) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ public record RefreshTokenDto(String value, Long expiration) {
public static RefreshTokenDto from(final RefreshToken refreshToken) {
return new RefreshTokenDto(refreshToken.getValue(), refreshToken.getExpiration());
}

public static RefreshTokenDto from(final OAuthTokenResponse oAuthTokenResponse) {
return new RefreshTokenDto(oAuthTokenResponse.refresh_token(), oAuthTokenResponse.refresh_token_expires_in());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
import org.kakaoshare.backend.domain.member.dto.oauth.authenticate.OAuthLoginResponse;
import org.kakaoshare.backend.domain.member.dto.oauth.issue.OAuthReissueRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.issue.OAuthReissueResponse;
import org.kakaoshare.backend.domain.member.dto.oauth.issue.ReissueRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.issue.ReissueResponse;
import org.kakaoshare.backend.domain.member.dto.oauth.logout.OAuthLogoutRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.logout.OAuthSocialLogoutRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.profile.OAuthProfile;
import org.kakaoshare.backend.domain.member.dto.oauth.profile.OAuthProfileFactory;
import org.kakaoshare.backend.domain.member.dto.oauth.token.OAuthTokenResponse;
import org.kakaoshare.backend.domain.member.entity.Member;
import org.kakaoshare.backend.domain.member.entity.MemberDetails;
import org.kakaoshare.backend.domain.member.entity.token.RefreshToken;
Expand Down Expand Up @@ -65,8 +68,8 @@ public void socialLogout(final OAuthSocialLogoutRequest oAuthSocialLogoutRequest
}

@Transactional
public OAuthReissueResponse reissue(final OAuthReissueRequest oAuthReissueRequest) {
final String refreshTokenValue = oAuthReissueRequest.refreshToken();
public ReissueResponse reissue(final ReissueRequest reissueRequest) {
final String refreshTokenValue = reissueRequest.refreshToken();
final RefreshToken refreshToken = findRefreshTokenByValue(refreshTokenValue);
final String providerId = refreshToken.getProviderId();
final UserDetails userDetails = findUserDetailsByProviderId(providerId);
Expand All @@ -75,7 +78,14 @@ public OAuthReissueResponse reissue(final OAuthReissueRequest oAuthReissueReques
refreshTokenRepository.delete(refreshToken);
refreshTokenRepository.save(newRefreshToken);

return OAuthReissueResponse.of(accessToken, newRefreshToken);
return ReissueResponse.of(accessToken, newRefreshToken);
}

public OAuthReissueResponse socialReissue(final OAuthReissueRequest oAuthReissueRequest) {
final String provider = oAuthReissueRequest.provider();
final ClientRegistration registration = clientRegistrationRepository.findByRegistrationId(provider);
final OAuthTokenResponse oAuthTokenResponse = webClientService.issueToken(registration, oAuthReissueRequest);
return OAuthReissueResponse.from(oAuthTokenResponse); // TODO: 5/27/24 응답 중 refresh_token 값은 요청 시 사용된 리프레시 토큰의 만료 시간이 1개월 미만으로 남았을 때만 갱신되어 전달
}

private OAuthProfile getProfile(final OAuthLoginRequest request, final ClientRegistration registration) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package org.kakaoshare.backend.domain.member.service.oauth;

import org.kakaoshare.backend.domain.member.dto.oauth.issue.OAuthReissueRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.logout.OAuthSocialLogoutRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.token.OAuthTokenResponse;
import org.springframework.security.oauth2.client.registration.ClientRegistration;

import java.util.Map;

public interface OAuthWebClientService {
Map<String, Object> getSocialProfile(final ClientRegistration registration, final String socialToken);

OAuthTokenResponse issueToken(final ClientRegistration registration, final OAuthReissueRequest oAuthReissueRequest);
void expireToken(final ClientRegistration registration, final OAuthSocialLogoutRequest oAuthSocialLogoutRequest);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package org.kakaoshare.backend.domain.member.service.oauth.detail.kakao;

import org.kakaoshare.backend.common.util.MultiValueMapConverter;
import org.kakaoshare.backend.domain.member.dto.oauth.issue.OAuthReissueRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.logout.OAuthSocialLogoutRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.logout.detail.kakao.request.KakaoLogoutRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.token.OAuthTokenRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.token.OAuthTokenResponse;
import org.kakaoshare.backend.domain.member.service.oauth.OAuthWebClientService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.Map;
Expand Down Expand Up @@ -36,6 +43,21 @@ public Map<String, Object> getSocialProfile(final ClientRegistration registratio
.block();
}

@Override
public OAuthTokenResponse issueToken(final ClientRegistration registration,
final OAuthReissueRequest oAuthReissueRequest) {
final String socialRefreshToken = oAuthReissueRequest.refreshToken();
final OAuthTokenRequest oAuthTokenRequest = OAuthTokenRequest.of(registration, socialRefreshToken);
final MultiValueMap<String, String> params = MultiValueMapConverter.convert(oAuthTokenRequest);
return webClient.post()
.uri(getTokenRequestUri(registration))
.headers(header -> header.setContentType(MediaType.APPLICATION_FORM_URLENCODED))
.body(BodyInserters.fromFormData(params))
.retrieve()
.bodyToMono(OAuthTokenResponse.class)
.block();
}

@Override
public void expireToken(final ClientRegistration registration,
final OAuthSocialLogoutRequest oAuthSocialLogoutRequest) {
Expand All @@ -56,4 +78,9 @@ private String getProfileRequestUri(final ClientRegistration registration) {
.getUserInfoEndpoint()
.getUri();
}

private String getTokenRequestUri(final ClientRegistration registration) {
return registration.getProviderDetails()
.getTokenUri();
}
}
3 changes: 1 addition & 2 deletions src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ spring:
client-secret: QH0n8i9qoDE4TkeVl9p9aFPRQVcIgXHC
client-authentication-method: POST
client-name: kakao
redirect-uri: http://localhost:3000/oauth/kakao
authorization-grant-type: authorization_code
authorization-grant-type: refresh_token
scope:
- profile_nickname
- profile_image
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
import org.kakaoshare.backend.domain.member.dto.oauth.authenticate.OAuthLoginResponse;
import org.kakaoshare.backend.domain.member.dto.oauth.issue.OAuthReissueRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.issue.OAuthReissueResponse;
import org.kakaoshare.backend.domain.member.dto.oauth.issue.ReissueRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.issue.ReissueResponse;
import org.kakaoshare.backend.domain.member.dto.oauth.logout.OAuthSocialLogoutRequest;
import org.kakaoshare.backend.domain.member.dto.oauth.profile.OAuthProfile;
import org.kakaoshare.backend.domain.member.dto.oauth.profile.OAuthProfileFactory;
import org.kakaoshare.backend.domain.member.dto.oauth.token.OAuthTokenResponse;
import org.kakaoshare.backend.domain.member.entity.Member;
import org.kakaoshare.backend.domain.member.entity.MemberDetails;
import org.kakaoshare.backend.domain.member.entity.token.RefreshToken;
Expand Down Expand Up @@ -88,7 +91,7 @@ public void authenticateWhenNewMember() throws Exception {
final OAuthProfile oAuthProfile = OAuthProfileFactory.of(attributes, registrationId);
final OAuthLoginRequest request = new OAuthLoginRequest(registrationId, socialAccessToken);

doReturn(registration).when(clientRegistrationRepository).findByRegistrationId(registrationId);
mockingRegistration(registrationId, registration);
doReturn(attributes).when(webClientService).getSocialProfile(registration, socialAccessToken);
doReturn(member).when(memberRepository).save(any());
doReturn(accessToken).when(jwtProvider).createAccessToken(userDetails);
Expand All @@ -109,7 +112,7 @@ public void authenticateWhenExistingMember() throws Exception {
final OAuthProfile oAuthProfile = OAuthProfileFactory.of(attributes, registrationId);
final OAuthLoginRequest request = new OAuthLoginRequest(registrationId, socialAccessToken);

doReturn(registration).when(clientRegistrationRepository).findByRegistrationId(registrationId);
mockingRegistration(registrationId, registration);
doReturn(attributes).when(webClientService).getSocialProfile(registration, socialAccessToken);
doReturn(member).when(memberRepository).save(any());
doReturn(accessToken).when(jwtProvider).createAccessToken(userDetails);
Expand All @@ -133,9 +136,9 @@ public void reissue() throws Exception {
doReturn(newRefreshToken).when(refreshTokenProvider).createToken(providerId);
doReturn(newRefreshToken).when(refreshTokenRepository).save(newRefreshToken);

final OAuthReissueRequest oAuthReissueRequest = new OAuthReissueRequest(refreshTokenValue);
final OAuthReissueResponse actual = oAuthService.reissue(oAuthReissueRequest);
final OAuthReissueResponse expect = OAuthReissueResponse.of(accessToken, newRefreshToken);
final ReissueRequest reissueRequest = new ReissueRequest(refreshTokenValue);
final ReissueResponse actual = oAuthService.reissue(reissueRequest);
final ReissueResponse expect = ReissueResponse.of(accessToken, newRefreshToken);

assertThat(actual).isEqualTo(expect);
}
Expand All @@ -147,7 +150,7 @@ public void socialLogout() throws Exception {
final ClientRegistration registration = getClientRegistration(provider);
final OAuthSocialLogoutRequest oAuthSocialLogoutRequest = new OAuthSocialLogoutRequest(provider, providerId, socialAccessToken);

doReturn(registration).when(clientRegistrationRepository).findByRegistrationId(provider);
mockingRegistration(provider, registration);
doNothing().when(webClientService).expireToken(registration, oAuthSocialLogoutRequest);
assertThatCode(() -> oAuthService.socialLogout(oAuthSocialLogoutRequest))
.doesNotThrowAnyException();
Expand All @@ -160,12 +163,29 @@ public void socialLogoutWhenIvalidProviderId() throws Exception {
final ClientRegistration registration = getClientRegistration(provider);
final OAuthSocialLogoutRequest oAuthSocialLogoutRequest = new OAuthSocialLogoutRequest(provider, providerId, socialAccessToken);

doReturn(registration).when(clientRegistrationRepository).findByRegistrationId(provider);
mockingRegistration(provider, registration);
doThrow(WebClientResponseException.class).when(webClientService).expireToken(registration, oAuthSocialLogoutRequest);
assertThatThrownBy(() -> oAuthService.socialLogout(oAuthSocialLogoutRequest))
.isInstanceOf(WebClientResponseException.class);
}

@Test
@DisplayName("카카오 소셜 토큰 재발급")
public void kakaoTokenReissue() throws Exception {
final String provider = "kakao";
final ClientRegistration registration = getClientRegistration(provider);
final OAuthReissueRequest oAuthReissueRequest = new OAuthReissueRequest(provider, refreshToken.getValue());
final OAuthTokenResponse oAuthTokenResponse = getOAuthTokenResponse(accessToken, refreshToken.getValue());

mockingRegistration(provider, registration);
doReturn(oAuthTokenResponse).when(webClientService).issueToken(registration, oAuthReissueRequest);

final OAuthReissueResponse actual = oAuthService.socialReissue(oAuthReissueRequest);
final OAuthReissueResponse expect = OAuthReissueResponse.from(oAuthTokenResponse);

assertThat(actual).isEqualTo(expect);
}

private ClientRegistration getClientRegistration(final String registrationId) {
return ClientRegistration.withRegistrationId(registrationId)
.clientId("client-id")
Expand All @@ -179,6 +199,10 @@ private ClientRegistration getClientRegistration(final String registrationId) {
.build();
}

private void mockingRegistration(final String provider, final ClientRegistration registration) {
doReturn(registration).when(clientRegistrationRepository).findByRegistrationId(provider);
}

private RefreshToken getRefreshToken() {
return RefreshToken.from(providerId, "refreshToken", 100L);
}
Expand All @@ -197,4 +221,14 @@ private Map<String, Object> kakaoAttributes() {

return attributes;
}

private OAuthTokenResponse getOAuthTokenResponse(final String accessToken, final String refreshToken) {
return new OAuthTokenResponse(
"refresh_token",
accessToken,
refreshToken,
1000L,
10000L
);
}
}
Loading