diff --git a/account-service/src/main/java/com/synapse/account_service/config/SecurityConfig.java b/account-service/src/main/java/com/synapse/account_service/config/SecurityConfig.java index dbadb18..a75c7d9 100644 --- a/account-service/src/main/java/com/synapse/account_service/config/SecurityConfig.java +++ b/account-service/src/main/java/com/synapse/account_service/config/SecurityConfig.java @@ -18,6 +18,7 @@ import com.synapse.account_service.convert.authority.CustomAuthorityMapper; import com.synapse.account_service.filter.JwtAuthenticationFilter; import com.synapse.account_service.service.CustomOAuth2UserService; +import com.synapse.account_service.service.CustomOidcUserService; import com.synapse.account_service.service.CustomUserDetailsService; import com.synapse.account_service.service.handler.LoginFailureHandler; import com.synapse.account_service.service.handler.LoginSuccessHandler; @@ -32,6 +33,7 @@ public class SecurityConfig { private final LoginSuccessHandler loginSuccessHandler; private final LoginFailureHandler loginFailureHandler; private final CustomOAuth2UserService customOAuth2UserService; + private final CustomOidcUserService customOidcUserService; private final ObjectMapper objectMapper; private final PasswordEncoder passwordEncoder; @@ -48,7 +50,10 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticat .addFilterAt(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .oauth2Login(oauth2 -> oauth2 - .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) + .userInfoEndpoint(userInfo -> userInfo + .userService(customOAuth2UserService) + .oidcUserService(customOidcUserService) + ) .successHandler(loginSuccessHandler) .failureHandler(loginFailureHandler) ) diff --git a/account-service/src/main/java/com/synapse/account_service/convert/DelegatingProviderUserConverter.java b/account-service/src/main/java/com/synapse/account_service/convert/DelegatingProviderUserConverter.java index b8e63fe..163a8b6 100644 --- a/account-service/src/main/java/com/synapse/account_service/convert/DelegatingProviderUserConverter.java +++ b/account-service/src/main/java/com/synapse/account_service/convert/DelegatingProviderUserConverter.java @@ -22,7 +22,9 @@ public DelegatingProviderUserConverter() { new UserDetailsProviderUserConverter(), new OAuth2GoogleProviderUserConverter(), new OAuth2KakaoProviderUserConverter(), - new OAuth2KakaoOidcProviderUserConverter()); + new OAuth2KakaoOidcProviderUserConverter(), + new OAuth2NaverProviderUserConverter() + ); this.converters = Collections.unmodifiableList(new LinkedList<>(providerUserConverters)); } diff --git a/account-service/src/main/java/com/synapse/account_service/convert/OAuth2NaverProviderUserConverter.java b/account-service/src/main/java/com/synapse/account_service/convert/OAuth2NaverProviderUserConverter.java new file mode 100644 index 0000000..11945a4 --- /dev/null +++ b/account-service/src/main/java/com/synapse/account_service/convert/OAuth2NaverProviderUserConverter.java @@ -0,0 +1,22 @@ +package com.synapse.account_service.convert; + +import com.synapse.account_service.domain.ProviderUser; +import com.synapse.account_service.domain.enums.OAuth2Config; +import com.synapse.account_service.domain.socials.NaverUser; +import com.synapse.account_service.util.OAuth2Utils; + +public final class OAuth2NaverProviderUserConverter implements ProviderUserConverter { + + @Override + public ProviderUser convert(ProviderUserRequest providerUserRequest) { + + if (!providerUserRequest.clientRegistration().getRegistrationId().equals(OAuth2Config.SocialType.NAVER.getSocialName())) { + return null; + } + + return new NaverUser(OAuth2Utils.getSubAttributes( + providerUserRequest.oAuth2User(), "response"), + providerUserRequest.oAuth2User(), + providerUserRequest.clientRegistration()); + } +} diff --git a/account-service/src/main/java/com/synapse/account_service/domain/enums/OAuth2Config.java b/account-service/src/main/java/com/synapse/account_service/domain/enums/OAuth2Config.java index 737297b..8947651 100644 --- a/account-service/src/main/java/com/synapse/account_service/domain/enums/OAuth2Config.java +++ b/account-service/src/main/java/com/synapse/account_service/domain/enums/OAuth2Config.java @@ -3,7 +3,9 @@ public class OAuth2Config { public enum SocialType { GOOGLE("google"), - KAKAO("kakao"); + KAKAO("kakao"), + NAVER("naver") + ; private final String socialName; diff --git a/account-service/src/main/java/com/synapse/account_service/domain/socials/NaverUser.java b/account-service/src/main/java/com/synapse/account_service/domain/socials/NaverUser.java new file mode 100644 index 0000000..be6b816 --- /dev/null +++ b/account-service/src/main/java/com/synapse/account_service/domain/socials/NaverUser.java @@ -0,0 +1,28 @@ +package com.synapse.account_service.domain.socials; + +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.user.OAuth2User; + +import com.synapse.account_service.domain.Attributes; + +public class NaverUser extends OAuth2ProviderUser { + + public NaverUser(Attributes attributes, OAuth2User oAuth2User, ClientRegistration clientRegistration) { + super(attributes.getSubAttributes(), oAuth2User, clientRegistration); + } + + @Override + public String getId() { + return (String) getAttributes().get("id"); + } + + @Override + public String getUsername() { + return (String) getAttributes().get("name"); + } + + @Override + public String getPicture() { + return (String) getAttributes().get("profile_image"); + } +} diff --git a/account-service/src/main/java/com/synapse/account_service/exception/ExceptionType.java b/account-service/src/main/java/com/synapse/account_service/exception/ExceptionType.java index 3c5fa8c..f24b6c9 100644 --- a/account-service/src/main/java/com/synapse/account_service/exception/ExceptionType.java +++ b/account-service/src/main/java/com/synapse/account_service/exception/ExceptionType.java @@ -18,6 +18,7 @@ public enum ExceptionType { DUPLICATED_USERNAME(CONFLICT, "002", "이미 존재하는 사용자 이름입니다."), EXCEPTION(INTERNAL_SERVER_ERROR, "003", "예상치 못한 오류가 발생했습니다."), NOT_FOUND_MEMBER(NOT_FOUND, "004", "존재하지 않는 사용자입니다."), + DUPLICATED_USERNAME_AND_EMAIL(CONFLICT, "005", "이미 존재하는 사용자 이름과 이메일입니다."), INVALID_TOKEN(UNAUTHORIZED, "005", "유효하지 않은 토큰입니다."), EXPIRED_TOKEN(UNAUTHORIZED, "006", "만료된 토큰입니다."), diff --git a/account-service/src/main/java/com/synapse/account_service/repository/MemberRepository.java b/account-service/src/main/java/com/synapse/account_service/repository/MemberRepository.java index ced555f..0a197a7 100644 --- a/account-service/src/main/java/com/synapse/account_service/repository/MemberRepository.java +++ b/account-service/src/main/java/com/synapse/account_service/repository/MemberRepository.java @@ -14,6 +14,9 @@ public interface MemberRepository extends JpaRepository { Optional findByUsername(String username); Optional findByProviderAndRegistrationId(String provider, String registrationId); + @Query("SELECT m FROM Member m WHERE m.username = :username AND m.email = :email") + Optional findByUsernameAndEmail(@Param("username") String username, @Param("email") String email); + @Query("SELECT m FROM Member m WHERE (m.provider = :provider AND m.registrationId = :registrationId) OR m.email = :email OR m.username = :username") Optional findBySocialIdOrEmailOrUsername(@Param("provider") String provider, @Param("registrationId") String registrationId, @Param("email") String email, @Param("username") String username); } diff --git a/account-service/src/main/java/com/synapse/account_service/service/AccountService.java b/account-service/src/main/java/com/synapse/account_service/service/AccountService.java index f48173e..1cdf086 100644 --- a/account-service/src/main/java/com/synapse/account_service/service/AccountService.java +++ b/account-service/src/main/java/com/synapse/account_service/service/AccountService.java @@ -48,11 +48,8 @@ public SignUpResponse registerMember(SignUpRequest request) { private Member createAndSaveNewMember(String email, String username, String password, String provider, String registrationId) { // 중복 검사 - memberRepository.findByEmail(email).ifPresent(m -> { - throw new DuplicatedException(ExceptionType.DUPLICATED_EMAIL); - }); - memberRepository.findByUsername(username).ifPresent(m -> { - throw new DuplicatedException(ExceptionType.DUPLICATED_USERNAME); + memberRepository.findByUsernameAndEmail(username, email).ifPresent(m -> { + throw new DuplicatedException(ExceptionType.DUPLICATED_USERNAME_AND_EMAIL); }); Member member = Member.builder() diff --git a/account-service/src/main/java/com/synapse/account_service/util/OAuth2Utils.java b/account-service/src/main/java/com/synapse/account_service/util/OAuth2Utils.java index f1de400..377029f 100644 --- a/account-service/src/main/java/com/synapse/account_service/util/OAuth2Utils.java +++ b/account-service/src/main/java/com/synapse/account_service/util/OAuth2Utils.java @@ -15,6 +15,15 @@ public static Attributes getMainAttributes(OAuth2User oAuth2User) { .build(); } + @SuppressWarnings("unchecked") + public static Attributes getSubAttributes(OAuth2User oAuth2User, String subAttributesKey) { + + Map subAttributes = (Map) oAuth2User.getAttributes().get(subAttributesKey); + return Attributes.builder() + .subAttributes(subAttributes) + .build(); + } + @SuppressWarnings("unchecked") public static Attributes getOtherAttributes(OAuth2User oAuth2User, String subAttributesKey, String otherAttributesKey) { diff --git a/account-service/src/main/resources/application-local.yml b/account-service/src/main/resources/application-local.yml index bd9cfe1..0622468 100644 --- a/account-service/src/main/resources/application-local.yml +++ b/account-service/src/main/resources/application-local.yml @@ -13,7 +13,7 @@ spring: highlight: sql: true hbm2ddl: - auto: create-drop + auto: create dialect: org.hibernate.dialect.PostgreSQLDialect open-in-view: false show-sql: true diff --git a/account-service/src/main/resources/application.yml b/account-service/src/main/resources/application.yml index 09b442c..c9a94eb 100644 --- a/account-service/src/main/resources/application.yml +++ b/account-service/src/main/resources/application.yml @@ -15,3 +15,4 @@ spring: import: - security/application-db.yml - security/application-jwt.yml + - security/application-oauth2.yml diff --git a/account-service/src/test/java/com/synapse/account_service/service/AccountServiceTest.java b/account-service/src/test/java/com/synapse/account_service/service/AccountServiceTest.java index 8bf5c7e..4ef6bee 100644 --- a/account-service/src/test/java/com/synapse/account_service/service/AccountServiceTest.java +++ b/account-service/src/test/java/com/synapse/account_service/service/AccountServiceTest.java @@ -41,8 +41,7 @@ void signUp_success() { // given: 테스트 준비 SignUpRequest request = new SignUpRequest("test@example.com", "테스트유저", "password123"); - given(memberRepository.findByEmail(anyString())).willReturn(Optional.empty()); - given(memberRepository.findByUsername(anyString())).willReturn(Optional.empty()); + given(memberRepository.findByUsernameAndEmail(anyString(), anyString())).willReturn(Optional.empty()); given(passwordEncoder.encode(anyString())).willReturn("encodedPassword"); given(memberRepository.save(any(Member.class))).willAnswer(invocation -> { Member memberToSave = invocation.getArgument(0); @@ -61,15 +60,15 @@ void signUp_success() { } @Test - @DisplayName("이메일 중복으로 회원가입 실패") - void signUp_fail_withDuplicateEmail() { + @DisplayName("이메일 또는 사용자명 중복으로 회원가입 실패") + void signUp_fail_withDuplicateUsernameAndEmail() { // given SignUpRequest request = new SignUpRequest("test@example.com", "테스트유저", "password123"); - // memberRepository.findByEmail이 호출되면, 이미 존재하는 Member 객체를 반환하도록 설정 - given(memberRepository.findByEmail(anyString())).willReturn(Optional.of(Member.builder().build())); + // memberRepository.findByUsernameAndEmail이 호출되면, 이미 존재하는 Member 객체를 반환하도록 설정 + given(memberRepository.findByUsernameAndEmail(anyString(), anyString())).willReturn(Optional.of(Member.builder().build())); - // when & then: BusinessException이 발생하는지 검증 + // when & then: DuplicatedException이 발생하는지 검증 assertThrows(DuplicatedException.class, () -> { accountService.registerMember(request); });