From 31407abd3b443084add1f2636d8c55887a545f89 Mon Sep 17 00:00:00 2001 From: parksey Date: Tue, 31 Oct 2023 20:20:46 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EA=B8=B0=EB=8A=A5=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../application/AuthenticationService.java | 49 ++++++++-- .../java/com/moabam/api/domain/Member.java | 16 +++- .../api/dto/AuthorizationCodeIssue.java | 46 ---------- .../api/dto/AuthorizationCodeRequest.java | 26 ++++++ .../com/moabam/api/mapper/OAuthMapper.java | 19 ++++ .../api/presentation/MemberController.java | 10 +- .../global/common/util/BaseImageUrl.java | 15 +-- .../global/error/model/ErrorMessage.java | 3 +- .../AuthenticationServiceTest.java | 91 +++++++++++++++---- .../com/moabam/api/domain/MemberTest.java | 2 +- .../presentation/MemberControllerTest.java | 5 +- src/test/resources/application-test.yml | 14 +++ 13 files changed, 197 insertions(+), 100 deletions(-) delete mode 100644 src/main/java/com/moabam/api/dto/AuthorizationCodeIssue.java create mode 100644 src/main/java/com/moabam/api/dto/AuthorizationCodeRequest.java create mode 100644 src/main/java/com/moabam/api/mapper/OAuthMapper.java create mode 100644 src/test/resources/application-test.yml diff --git a/.gitignore b/.gitignore index df8fb3c3..f4a54d77 100644 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,4 @@ gradle-app.setting logs/ application-*.yml src/main/resources/config +!application-test.yml diff --git a/src/main/java/com/moabam/api/application/AuthenticationService.java b/src/main/java/com/moabam/api/application/AuthenticationService.java index 75240f11..bd383e95 100644 --- a/src/main/java/com/moabam/api/application/AuthenticationService.java +++ b/src/main/java/com/moabam/api/application/AuthenticationService.java @@ -1,10 +1,21 @@ package com.moabam.api.application; +import static com.moabam.global.common.util.OAuthParameterNames.*; + +import java.io.IOException; + +import org.springframework.http.MediaType; import org.springframework.stereotype.Service; +import org.springframework.web.util.UriComponentsBuilder; -import com.moabam.api.dto.AuthorizationCodeIssue; +import com.moabam.api.dto.AuthorizationCodeRequest; +import com.moabam.api.mapper.OAuthMapper; +import com.moabam.global.common.util.GlobalConstant; import com.moabam.global.config.OAuthConfig; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.model.ErrorMessage; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @Service @@ -13,12 +24,34 @@ public class AuthenticationService { private final OAuthConfig oAuthConfig; - public String getAuthorizaionCodeUri() { - return AuthorizationCodeIssue.builder() - .clientId(oAuthConfig.client().clientId()) - .redirectUri(oAuthConfig.provider().redirectUri()) - .scope(oAuthConfig.client().scope()) - .build() - .generateQueryParamsWith(oAuthConfig.provider().authorizationUri()); + private String getAuthorizaionCodeUri() { + AuthorizationCodeRequest authorizationCodeRequest = OAuthMapper.toAuthorizationCodeRequest(oAuthConfig); + return generateQueryParamsWith(authorizationCodeRequest); + } + + private String generateQueryParamsWith(AuthorizationCodeRequest authorizationCodeRequest) { + UriComponentsBuilder authorizationCodeUri = UriComponentsBuilder + .fromUriString(oAuthConfig.provider().authorizationUri()) + .queryParam(RESPONSE_TYPE, CODE) + .queryParam(CLIENT_ID, authorizationCodeRequest.clientId()) + .queryParam(REDIRECT_URI, authorizationCodeRequest.redirectUri()); + + if (!authorizationCodeRequest.scope().isEmpty()) { + String scopes = String.join(GlobalConstant.COMMA, authorizationCodeRequest.scope()); + authorizationCodeUri.queryParam(SCOPE, scopes); + } + + return authorizationCodeUri.toUriString(); + } + + public void redirectToLoginPage(HttpServletResponse httpServletResponse) { + String authorizationCodeUri = getAuthorizaionCodeUri(); + + try { + httpServletResponse.setContentType(MediaType.APPLICATION_FORM_URLENCODED + GlobalConstant.CHARSET_UTF_8); + httpServletResponse.sendRedirect(authorizationCodeUri); + } catch (IOException e) { + throw new BadRequestException(ErrorMessage.REQUEST_FAILD); + } } } diff --git a/src/main/java/com/moabam/api/domain/Member.java b/src/main/java/com/moabam/api/domain/Member.java index 28197eec..c2e3701e 100644 --- a/src/main/java/com/moabam/api/domain/Member.java +++ b/src/main/java/com/moabam/api/domain/Member.java @@ -5,6 +5,8 @@ import java.time.LocalDateTime; import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; import com.moabam.global.common.entity.BaseTimeEntity; import com.moabam.global.common.util.BaseImageUrl; @@ -24,13 +26,15 @@ @Getter @Entity -@Table(name = "members") +@Table(name = "member") +@SQLDelete(sql = "UPDATE member SET deleted_at = CURRENT_TIMESTAMP where participant_id = ?") +@Where(clause = "deleted_at IS NOT NULL") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Member extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "member_id") + @Column(name = "id") private Long id; @Column(name = "social_id", nullable = false, unique = true) @@ -39,7 +43,7 @@ public class Member extends BaseTimeEntity { @Column(name = "nickname", nullable = false, unique = true) private String nickname; - @Column(name = "intro") + @Column(name = "intro", length = 30) private String intro; @Column(name = "profile_image", nullable = false) @@ -75,16 +79,18 @@ public class Member extends BaseTimeEntity { @Enumerated(EnumType.STRING) @Column(name = "role", nullable = false) + @ColumnDefault("USER") private Role role; @Column(name = "deleted_at") private LocalDateTime deletedAt; @Builder - private Member(String socialId, String nickname, String profileImage) { + private Member(Long id, String socialId, String nickname, String profileImage) { + this.id = id; this.socialId = requireNonNull(socialId); this.nickname = requireNonNull(nickname); - this.profileImage = requireNonNullElse(profileImage, BaseImageUrl.PROFILE_URL.getUrl()); + this.profileImage = requireNonNullElse(profileImage, BaseImageUrl.PROFILE_URL); this.role = Role.USER; } } diff --git a/src/main/java/com/moabam/api/dto/AuthorizationCodeIssue.java b/src/main/java/com/moabam/api/dto/AuthorizationCodeIssue.java deleted file mode 100644 index 40fe456e..00000000 --- a/src/main/java/com/moabam/api/dto/AuthorizationCodeIssue.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.moabam.api.dto; - -import static com.moabam.global.common.util.OAuthParameterNames.*; -import static java.util.Objects.*; - -import java.util.List; - -import org.springframework.web.util.UriComponentsBuilder; - -import com.moabam.global.common.util.GlobalConstant; - -import lombok.Builder; - -public record AuthorizationCodeIssue( - String clientId, - String redirectUri, - String responseType, - List scope, - String state -) { - - @Builder - public AuthorizationCodeIssue(String clientId, String redirectUri, String responseType, List scope, - String state) { - this.clientId = requireNonNull(clientId); - this.redirectUri = requireNonNull(redirectUri); - this.responseType = responseType; - this.scope = scope; - this.state = state; - } - - public String generateQueryParamsWith(String baseUrl) { - UriComponentsBuilder authorizationCodeUri = UriComponentsBuilder - .fromPath(baseUrl) - .queryParam(RESPONSE_TYPE, CODE) - .queryParam(CLIENT_ID, clientId) - .queryParam(REDIRECT_URI, redirectUri); - - if (!scope.isEmpty()) { - String scopes = String.join(GlobalConstant.COMMA, scope); - authorizationCodeUri.queryParam(SCOPE, scopes); - } - - return authorizationCodeUri.toUriString(); - } -} diff --git a/src/main/java/com/moabam/api/dto/AuthorizationCodeRequest.java b/src/main/java/com/moabam/api/dto/AuthorizationCodeRequest.java new file mode 100644 index 00000000..b6c30db5 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/AuthorizationCodeRequest.java @@ -0,0 +1,26 @@ +package com.moabam.api.dto; + +import static java.util.Objects.*; + +import java.util.List; + +import lombok.Builder; + +public record AuthorizationCodeRequest( + String clientId, + String redirectUri, + String responseType, + List scope, + String state +) { + + @Builder + public AuthorizationCodeRequest(String clientId, String redirectUri, String responseType, List scope, + String state) { + this.clientId = requireNonNull(clientId); + this.redirectUri = requireNonNull(redirectUri); + this.responseType = responseType; + this.scope = scope; + this.state = state; + } +} diff --git a/src/main/java/com/moabam/api/mapper/OAuthMapper.java b/src/main/java/com/moabam/api/mapper/OAuthMapper.java new file mode 100644 index 00000000..7d16b8ef --- /dev/null +++ b/src/main/java/com/moabam/api/mapper/OAuthMapper.java @@ -0,0 +1,19 @@ +package com.moabam.api.mapper; + +import com.moabam.api.dto.AuthorizationCodeRequest; +import com.moabam.global.config.OAuthConfig; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class OAuthMapper { + + public static AuthorizationCodeRequest toAuthorizationCodeRequest(OAuthConfig oAuthConfig) { + return AuthorizationCodeRequest.builder() + .clientId(oAuthConfig.client().clientId()) + .redirectUri(oAuthConfig.provider().redirectUri()) + .scope(oAuthConfig.client().scope()) + .build(); + } +} diff --git a/src/main/java/com/moabam/api/presentation/MemberController.java b/src/main/java/com/moabam/api/presentation/MemberController.java index 050fd8f9..fd2e730f 100644 --- a/src/main/java/com/moabam/api/presentation/MemberController.java +++ b/src/main/java/com/moabam/api/presentation/MemberController.java @@ -1,14 +1,10 @@ package com.moabam.api.presentation; -import java.io.IOException; - -import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.moabam.api.application.AuthenticationService; -import com.moabam.global.common.util.GlobalConstant; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -21,9 +17,7 @@ public class MemberController { private final AuthenticationService authenticationService; @GetMapping - public void socialLogin(HttpServletResponse httpServletResponse) throws IOException { - String authorizationCodeUri = authenticationService.getAuthorizaionCodeUri(); - httpServletResponse.setContentType(MediaType.APPLICATION_FORM_URLENCODED + GlobalConstant.CHARSET_UTF_8); - httpServletResponse.sendRedirect(authorizationCodeUri); + public void socialLogin(HttpServletResponse httpServletResponse) { + authenticationService.redirectToLoginPage(httpServletResponse); } } diff --git a/src/main/java/com/moabam/global/common/util/BaseImageUrl.java b/src/main/java/com/moabam/global/common/util/BaseImageUrl.java index 8a302328..d13f36ff 100644 --- a/src/main/java/com/moabam/global/common/util/BaseImageUrl.java +++ b/src/main/java/com/moabam/global/common/util/BaseImageUrl.java @@ -1,15 +1,10 @@ package com.moabam.global.common.util; -import lombok.Getter; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; -@Getter -public enum BaseImageUrl { +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class BaseImageUrl { - PROFILE_URL("/profile/baseUrl"); - - private String url; - - BaseImageUrl(String url) { - this.url = url; - } + public static final String PROFILE_URL = "/profile/baseUrl"; } diff --git a/src/main/java/com/moabam/global/error/model/ErrorMessage.java b/src/main/java/com/moabam/global/error/model/ErrorMessage.java index 0ad98085..26d157ea 100644 --- a/src/main/java/com/moabam/global/error/model/ErrorMessage.java +++ b/src/main/java/com/moabam/global/error/model/ErrorMessage.java @@ -8,7 +8,8 @@ public enum ErrorMessage { INVALID_REQUEST_FIELD("올바른 요청 정보가 아닙니다."), - LOGIN_FAILED("로그인에 실패했습니다."); + LOGIN_FAILED("로그인에 실패했습니다."), + REQUEST_FAILD("네트우크 접근 실패입니다."); private final String message; } diff --git a/src/test/java/com/moabam/api/application/AuthenticationServiceTest.java b/src/test/java/com/moabam/api/application/AuthenticationServiceTest.java index 3015839c..b77a5f72 100644 --- a/src/test/java/com/moabam/api/application/AuthenticationServiceTest.java +++ b/src/test/java/com/moabam/api/application/AuthenticationServiceTest.java @@ -1,59 +1,114 @@ package com.moabam.api.application; import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import java.io.IOException; import java.util.List; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.util.ReflectionTestUtils; +import com.moabam.api.dto.AuthorizationCodeRequest; +import com.moabam.api.mapper.OAuthMapper; +import com.moabam.global.common.util.GlobalConstant; import com.moabam.global.config.OAuthConfig; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.model.ErrorMessage; + +import jakarta.servlet.http.HttpServletResponse; @ExtendWith(MockitoExtension.class) class AuthenticationServiceTest { @InjectMocks AuthenticationService authenticationService; - OAuthConfig oauthConfig; + AuthenticationService noPropertyService; + OAuthConfig noOAuthConfig; @BeforeEach public void initParams() { oauthConfig = new OAuthConfig( - new OAuthConfig.Provider("https://test.com/test/test", "http://test:8080/test"), + new OAuthConfig.Provider("https://authorization/url", "http://redirect/url"), new OAuthConfig.Client("provider", "testtestetsttest", "authorization_code", List.of("profile_nickname", "profile_image")) ); ReflectionTestUtils.setField(authenticationService, "oAuthConfig", oauthConfig); + + noOAuthConfig = new OAuthConfig( + new OAuthConfig.Provider(null, null), + new OAuthConfig.Client(null, null, null, null) + ); + noPropertyService = new AuthenticationService(noOAuthConfig); + } - @DisplayName("authentication code Url 생성 성공") + @DisplayName("인가코드 URI 생성 매퍼 실패") @Test - void authenticationUrl() { - // Given + When - String authorizaionCodeUri = authenticationService.getAuthorizaionCodeUri(); - - // Then - assertThat(authorizaionCodeUri).contains(oauthConfig.client().clientId(), - String.join(",", oauthConfig.client().scope()), oauthConfig.provider().redirectUri()); + void authorization_code_request_mapping_fail() { + // When + Then + Assertions.assertThatThrownBy(() -> OAuthMapper.toAuthorizationCodeRequest(noOAuthConfig)) + .isInstanceOf(NullPointerException.class); } - @DisplayName("Client id가 없을 때 url 생성 실패") + @DisplayName("인가코드 URI 생성 매퍼 성공") @Test - void create_failby_no_client_id() { + void authorization_code_request_mapping_success() { // Given - AuthenticationService noPropertyService = new AuthenticationService(new OAuthConfig( - new OAuthConfig.Provider(null, null), - new OAuthConfig.Client(null, null, null, null) - )); + AuthorizationCodeRequest authorizationCodeRequest = OAuthMapper.toAuthorizationCodeRequest(oauthConfig); // When + Then - assertThatThrownBy(noPropertyService::getAuthorizaionCodeUri) - .isInstanceOf(NullPointerException.class); + assertThat(authorizationCodeRequest).isNotNull(); + assertAll( + () -> assertThat(authorizationCodeRequest.clientId()).isEqualTo(oauthConfig.client().clientId()), + () -> assertThat(authorizationCodeRequest.redirectUri()).isEqualTo(oauthConfig.provider().redirectUri()) + ); + } + + @DisplayName("인가코드 URI 생성 성공") + @Test + void authorization_code_uri_generate_success() throws IOException { + // given + String uri = "https://authorization/url?" + + "response_type=code&" + + "client_id=testtestetsttest&" + + "redirect_uri=http://redirect/url&scope=profile_nickname,profile_image"; + + MockHttpServletResponse mockHttpServletResponse = mockHttpServletResponse = new MockHttpServletResponse(); + + // when + authenticationService.redirectToLoginPage(mockHttpServletResponse); + + // then + assertThat(mockHttpServletResponse.getContentType()) + .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED + GlobalConstant.CHARSET_UTF_8); + assertThat(mockHttpServletResponse.getRedirectedUrl()).isEqualTo(uri); + } + + @DisplayName("redirect 실패 테스트") + @Test + void redirect_fail_test() { + // given + HttpServletResponse mockHttpServletResponse = Mockito.mock(HttpServletResponse.class); + assertThatThrownBy(() -> { + // Given + doThrow(IOException.class).when(mockHttpServletResponse).sendRedirect(any(String.class)); + + // When + Then + authenticationService.redirectToLoginPage(mockHttpServletResponse); + }).isExactlyInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.REQUEST_FAILD.getMessage()); } } diff --git a/src/test/java/com/moabam/api/domain/MemberTest.java b/src/test/java/com/moabam/api/domain/MemberTest.java index dc435f5c..b08dde3f 100644 --- a/src/test/java/com/moabam/api/domain/MemberTest.java +++ b/src/test/java/com/moabam/api/domain/MemberTest.java @@ -37,7 +37,7 @@ void create_member_noImage_success() { .build(); assertAll( - () -> assertThat(member.getProfileImage()).isEqualTo(BaseImageUrl.PROFILE_URL.getUrl()), + () -> assertThat(member.getProfileImage()).isEqualTo(BaseImageUrl.PROFILE_URL), () -> assertThat(member.getRole()).isEqualTo(Role.USER), () -> assertThat(member.getNightBug()).isZero(), () -> assertThat(member.getGoldenBug()).isZero(), diff --git a/src/test/java/com/moabam/api/presentation/MemberControllerTest.java b/src/test/java/com/moabam/api/presentation/MemberControllerTest.java index e234d9ab..cd4da732 100644 --- a/src/test/java/com/moabam/api/presentation/MemberControllerTest.java +++ b/src/test/java/com/moabam/api/presentation/MemberControllerTest.java @@ -48,17 +48,16 @@ class MemberControllerTest { void authorization_code_request_success() throws Exception { // given String uri = UriComponentsBuilder - .fromPath(oAuthConfig.provider().authorizationUri()) + .fromUriString(oAuthConfig.provider().authorizationUri()) .queryParam(RESPONSE_TYPE, "code") .queryParam(CLIENT_ID, oAuthConfig.client().clientId()) .queryParam(REDIRECT_URI, oAuthConfig.provider().redirectUri()) .queryParam(SCOPE, String.join(",", oAuthConfig.client().scope())) .toUriString(); - // when + // expected ResultActions result = mockMvc.perform(get("/members")); - // then result.andExpect(status().is3xxRedirection()) .andExpect(header().string("Content-type", MediaType.APPLICATION_FORM_URLENCODED_VALUE + GlobalConstant.CHARSET_UTF_8)) diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml new file mode 100644 index 00000000..b149d76f --- /dev/null +++ b/src/test/resources/application-test.yml @@ -0,0 +1,14 @@ +oauth2: + client: + provider: test + client-id: testtestetsttest + client-secret: testtestetsttest + authorization-grant-type: authorization_code + scope: + - profile_nickname + - profile_image + + provider: + authorization_uri: https://test.com/test/test + redirect_uri: http://test:8080/test + token_uri: https://test.test.com/test/test