diff --git a/build.gradle b/build.gradle index 94f6baee3f..b8b9652b99 100644 --- a/build.gradle +++ b/build.gradle @@ -19,12 +19,20 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' + + // spring security 설정 + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' + + // OAuth 2.0 설정 + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' } tasks.named('test') { diff --git a/src/main/java/org/capstone/maru/config/JpaConfig.java b/src/main/java/org/capstone/maru/config/JpaConfig.java new file mode 100644 index 0000000000..59ff2d6f20 --- /dev/null +++ b/src/main/java/org/capstone/maru/config/JpaConfig.java @@ -0,0 +1,17 @@ +package org.capstone.maru.config; + +import java.util.Optional; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.domain.AuditorAware; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@EnableJpaAuditing +@Configuration +public class JpaConfig { + + @Bean + public AuditorAware auditorAware() { + return () -> Optional.of("tester"); + } +} diff --git a/src/main/java/org/capstone/maru/config/SecurityConfig.java b/src/main/java/org/capstone/maru/config/SecurityConfig.java new file mode 100644 index 0000000000..af5d657ebf --- /dev/null +++ b/src/main/java/org/capstone/maru/config/SecurityConfig.java @@ -0,0 +1,101 @@ +package org.capstone.maru.config; + + +import lombok.extern.slf4j.Slf4j; +import org.capstone.maru.dto.security.KakaoOAuth2Response; +import org.capstone.maru.dto.security.SharedPostPrincipal; +import org.capstone.maru.service.MemberAccountService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.security.servlet.PathRequest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.SecurityFilterChain; + +@Slf4j +@Configuration +public class SecurityConfig { + + @Bean + @ConditionalOnProperty(name = "spring.h2.console.enabled", havingValue = "true") + public WebSecurityCustomizer configureH2ConsoleEnable() { + return web -> web.ignoring() + .requestMatchers(PathRequest.toH2Console()); + } + + @Bean + public SecurityFilterChain securityFilterChain( + HttpSecurity httpSecurity, + OAuth2UserService oAuth2UserService + ) throws Exception { + log.info("SecurityFilterChain 빈 등록 완료"); + return httpSecurity + .authorizeHttpRequests(auth -> auth + .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() + .requestMatchers( + HttpMethod.GET, + "/" + ).permitAll() + .anyRequest().authenticated() + ) + .oauth2Login(oAuth -> oAuth + .userInfoEndpoint(userInfo -> userInfo + .userService(oAuth2UserService) + ) + ) + .csrf( + csrf -> csrf + .ignoringRequestMatchers("/api/**") + .disable() + ) + .build(); + } + + + @Bean + public OAuth2UserService oAuth2UserService( + MemberAccountService memberAccountService, + PasswordEncoder passwordEncoder + ) { + final DefaultOAuth2UserService delegate = new DefaultOAuth2UserService(); + + return userRequest -> { + OAuth2User oAuth2User = delegate.loadUser(userRequest); + + KakaoOAuth2Response kakaoOAuthResponse = KakaoOAuth2Response.from( + oAuth2User.getAttributes()); + + String registrationId = userRequest.getClientRegistration() + .getRegistrationId(); // "kakao" + + String providerId = String.valueOf(kakaoOAuthResponse.id()); + String memberId = registrationId + "_" + providerId; + + return memberAccountService + .searchMember(memberId) + .map(SharedPostPrincipal::from) + .orElseGet(() -> + SharedPostPrincipal.from( + memberAccountService.saveUser( + memberId, + kakaoOAuthResponse.email(), + kakaoOAuthResponse.nickname() + ) + ) + ); + }; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } +} diff --git a/src/main/java/org/capstone/maru/controller/MainController.java b/src/main/java/org/capstone/maru/controller/MainController.java new file mode 100644 index 0000000000..4c48fa81be --- /dev/null +++ b/src/main/java/org/capstone/maru/controller/MainController.java @@ -0,0 +1,21 @@ +package org.capstone.maru.controller; + +import org.capstone.maru.dto.security.SharedPostPrincipal; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class MainController { + + @GetMapping("/") + public String root() { + return "home"; + } + + @GetMapping("/test") + public String test(@AuthenticationPrincipal SharedPostPrincipal sharedPostPrincipal) { + return sharedPostPrincipal.getName(); + } + +} diff --git a/src/main/java/org/capstone/maru/domain/AuditingFields.java b/src/main/java/org/capstone/maru/domain/AuditingFields.java new file mode 100644 index 0000000000..98c878f5e9 --- /dev/null +++ b/src/main/java/org/capstone/maru/domain/AuditingFields.java @@ -0,0 +1,39 @@ +package org.capstone.maru.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.ToString; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import org.springframework.format.annotation.DateTimeFormat; + +@Getter +@ToString(callSuper = true) +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class AuditingFields { + + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @CreatedDate + @Column(nullable = false) + protected LocalDateTime createdAt; // 생성일시 + + @CreatedBy + @Column(nullable = false, updatable = false, length = 100) + protected String createdBy; // 생성자 + + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @LastModifiedDate + @Column(nullable = false) + protected LocalDateTime modifiedAt; // 수정일시 + + @LastModifiedBy + @Column(nullable = false, length = 100) + protected String modifiedBy; // 수정자 +} diff --git a/src/main/java/org/capstone/maru/domain/MemberAccount.java b/src/main/java/org/capstone/maru/domain/MemberAccount.java new file mode 100644 index 0000000000..4c6f643f11 --- /dev/null +++ b/src/main/java/org/capstone/maru/domain/MemberAccount.java @@ -0,0 +1,94 @@ +package org.capstone.maru.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.Table; +import java.util.Objects; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.capstone.maru.dto.Role; +import org.capstone.maru.dto.SocialType; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString(callSuper = true) +@Table(indexes = { + @Index(columnList = "memberId", unique = true), + @Index(columnList = "email", unique = true), + @Index(columnList = "createdAt"), + @Index(columnList = "createdBy") +}) +@Entity +public class MemberAccount extends AuditingFields { + + @Id + @Column(nullable = false, length = 50) + private String memberId; + + @Setter + @Column(length = 100) + private String email; + + @Setter + @Column(length = 100) + private String nickname; + + private SocialType socialType; + + private Role role; + + @Builder + private MemberAccount( + String memberId, + String email, + String nickname, + String createdBy, + SocialType socialType + ) { + this.memberId = memberId; + this.email = email; + this.nickname = nickname; + this.createdBy = createdBy; + this.modifiedBy = createdBy; + this.socialType = socialType; + } + + public static MemberAccount of( + String memberId, + String email, + String nickname + ) { + return new MemberAccount(memberId, email, nickname, null, null); + } + + public static MemberAccount of( + String memberId, + String email, + String nickname, + String createdBy + ) { + return new MemberAccount(memberId, email, nickname, createdBy, null); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MemberAccount that)) { + return false; + } + return this.getMemberId() != null && this.getMemberId().equals(that.getMemberId()); + } + + @Override + public int hashCode() { + return Objects.hash(this.getMemberId()); + } +} diff --git a/src/main/java/org/capstone/maru/dto/CustomOAuth2User.java b/src/main/java/org/capstone/maru/dto/CustomOAuth2User.java new file mode 100644 index 0000000000..d050359fb8 --- /dev/null +++ b/src/main/java/org/capstone/maru/dto/CustomOAuth2User.java @@ -0,0 +1,14 @@ +package org.capstone.maru.dto; + +import java.util.Collection; +import java.util.Map; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; + +public class CustomOAuth2User extends DefaultOAuth2User { + + public CustomOAuth2User(Collection authorities, + Map attributes, String nameAttributeKey, String email) { + super(authorities, attributes, nameAttributeKey); + } +} diff --git a/src/main/java/org/capstone/maru/dto/KakaoOAuth2UserInfo.java b/src/main/java/org/capstone/maru/dto/KakaoOAuth2UserInfo.java new file mode 100644 index 0000000000..240431a460 --- /dev/null +++ b/src/main/java/org/capstone/maru/dto/KakaoOAuth2UserInfo.java @@ -0,0 +1,21 @@ +package org.capstone.maru.dto; + +import java.util.Map; + +public class KakaoOAuth2UserInfo extends OAuth2UserInfo { + + public KakaoOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getId() { + return String.valueOf(attributes.get("id")); + } + + @Override + public String getNickname() { + return null; + } + +} diff --git a/src/main/java/org/capstone/maru/dto/MemberAccountDto.java b/src/main/java/org/capstone/maru/dto/MemberAccountDto.java new file mode 100644 index 0000000000..94c9933b81 --- /dev/null +++ b/src/main/java/org/capstone/maru/dto/MemberAccountDto.java @@ -0,0 +1,72 @@ +package org.capstone.maru.dto; + +import java.time.LocalDateTime; +import org.capstone.maru.domain.MemberAccount; + +public record MemberAccountDto( + String memberId, + String email, + String nickname, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy +) { + + public static MemberAccountDto of( + String memberId, + String email, + String nickname + ) { + return new MemberAccountDto( + memberId, + email, + nickname, + null, + null, + null, + null + ); + } + + public static MemberAccountDto of( + String memberId, + String email, + String nickname, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy + ) { + return new MemberAccountDto( + memberId, + email, + nickname, + createdAt, + createdBy, + modifiedAt, + modifiedBy + ); + } + + public static MemberAccountDto from(MemberAccount entity) { + return new MemberAccountDto( + entity.getMemberId(), + entity.getEmail(), + entity.getNickname(), + entity.getCreatedAt(), + entity.getCreatedBy(), + entity.getModifiedAt(), + entity.getModifiedBy() + ); + } + + public MemberAccount toEntity() { + return MemberAccount.of( + memberId, + email, + nickname, + createdBy + ); + } +} \ No newline at end of file diff --git a/src/main/java/org/capstone/maru/dto/NaverOAuth2UserInfo.java b/src/main/java/org/capstone/maru/dto/NaverOAuth2UserInfo.java new file mode 100644 index 0000000000..b1c0a11e7f --- /dev/null +++ b/src/main/java/org/capstone/maru/dto/NaverOAuth2UserInfo.java @@ -0,0 +1,20 @@ +package org.capstone.maru.dto; + +import java.util.Map; + +public class NaverOAuth2UserInfo extends OAuth2UserInfo { + + public NaverOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getId() { + return String.valueOf(attributes.get("id")); + } + + @Override + public String getNickname() { + return null; + } +} diff --git a/src/main/java/org/capstone/maru/dto/OAuth2UserInfo.java b/src/main/java/org/capstone/maru/dto/OAuth2UserInfo.java new file mode 100644 index 0000000000..595161562d --- /dev/null +++ b/src/main/java/org/capstone/maru/dto/OAuth2UserInfo.java @@ -0,0 +1,17 @@ +package org.capstone.maru.dto; + +import java.util.Map; + +public abstract class OAuth2UserInfo { + + protected Map attributes; + + public OAuth2UserInfo(Map attributes) { + this.attributes = attributes; + } + + public abstract String getId(); //소셜 식별 값 : 구글 - "sub", 카카오 - "id", 네이버 - "id" + + public abstract String getNickname(); + +} diff --git a/src/main/java/org/capstone/maru/dto/OAuthAttributes.java b/src/main/java/org/capstone/maru/dto/OAuthAttributes.java new file mode 100644 index 0000000000..99104b527b --- /dev/null +++ b/src/main/java/org/capstone/maru/dto/OAuthAttributes.java @@ -0,0 +1,65 @@ +package org.capstone.maru.dto; + +import java.util.Map; +import lombok.Builder; +import lombok.Getter; +import org.capstone.maru.domain.MemberAccount; + +@Getter +public class OAuthAttributes { + + private String nameAttributeKey; // OAuth2 로그인 진행 시 키가 되는 필드 값, PK와 같은 의미 + private OAuth2UserInfo oauth2UserInfo; // 소셜 타입별 로그인 유저 정보(닉네임, 이메일, 프로필 사진 등등) + + @Builder + public OAuthAttributes(String nameAttributeKey, OAuth2UserInfo oauth2UserInfo) { + this.nameAttributeKey = nameAttributeKey; + this.oauth2UserInfo = oauth2UserInfo; + } + + /** + * SocialType에 맞는 메소드 호출하여 OAuthAttributes 객체 반환 파라미터 : userNameAttributeName -> OAuth2 로그인 시 + * 키(PK)가 되는 값 / attributes : OAuth 서비스의 유저 정보들 소셜별 of 메소드(ofGoogle, ofKaKao, ofNaver)들은 각각 소셜 + * 로그인 API에서 제공하는 회원의 식별값(id), attributes, nameAttributeKey를 저장 후 build + */ + public static OAuthAttributes of(SocialType socialType, + String userNameAttributeName, Map attributes) { + + if (socialType == SocialType.NAVER) { + return ofNaver(userNameAttributeName, attributes); + } + if (socialType == SocialType.KAKAO) { + return ofKakao(userNameAttributeName, attributes); + } + return null; + } + + private static OAuthAttributes ofKakao(String userNameAttributeName, + Map attributes) { + return OAuthAttributes.builder() + .nameAttributeKey(userNameAttributeName) + .oauth2UserInfo(new KakaoOAuth2UserInfo(attributes)) + .build(); + } + + public static OAuthAttributes ofNaver(String userNameAttributeName, + Map attributes) { + return OAuthAttributes.builder() + .nameAttributeKey(userNameAttributeName) + .oauth2UserInfo(new NaverOAuth2UserInfo(attributes)) + .build(); + } + + /** + * of메소드로 OAuthAttributes 객체가 생성되어, 유저 정보들이 담긴 OAuth2UserInfo가 소셜 타입별로 주입된 상태 OAuth2UserInfo에서 + * socialId(식별값), nickname을 가져와서 build + */ + public MemberAccount toEntity(SocialType socialType, OAuth2UserInfo oauth2UserInfo) { + return MemberAccount.builder() + .memberId(oauth2UserInfo.getId()) + .nickname(oauth2UserInfo.getNickname()) + .socialType(socialType) + .build(); + } + +} diff --git a/src/main/java/org/capstone/maru/dto/Role.java b/src/main/java/org/capstone/maru/dto/Role.java new file mode 100644 index 0000000000..4871045593 --- /dev/null +++ b/src/main/java/org/capstone/maru/dto/Role.java @@ -0,0 +1,14 @@ +package org.capstone.maru.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum Role { + + GUEST("ROLE_GUEST"), USER("ROLE_USER"); + + private final String key; +} + diff --git a/src/main/java/org/capstone/maru/dto/SocialType.java b/src/main/java/org/capstone/maru/dto/SocialType.java new file mode 100644 index 0000000000..448e60a772 --- /dev/null +++ b/src/main/java/org/capstone/maru/dto/SocialType.java @@ -0,0 +1,5 @@ +package org.capstone.maru.dto; + +public enum SocialType { + KAKAO, NAVER +} diff --git a/src/main/java/org/capstone/maru/dto/security/KakaoOAuth2Response.java b/src/main/java/org/capstone/maru/dto/security/KakaoOAuth2Response.java new file mode 100644 index 0000000000..946ddf975e --- /dev/null +++ b/src/main/java/org/capstone/maru/dto/security/KakaoOAuth2Response.java @@ -0,0 +1,73 @@ +package org.capstone.maru.dto.security; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Map; + +/** + * https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info 카카오로부터 사용자 정보를 불러올 + * 때 카카오에서 응답 메시지 형식을 맞춘 것이다. 자세한 내용은 위 링크를 참조 + */ +@SuppressWarnings("unchecked") +public record KakaoOAuth2Response( + Long id, + LocalDateTime connectedAt, + Map properties, + KakaoAccount kakaoAccount +) { + + public record KakaoAccount( + Boolean profileNicknameNeedsAgreement, + Profile profile, + Boolean hasEmail, + Boolean emailNeedsAgreement, + Boolean isEmailValid, + Boolean isEmailVerified, + String email + ) { + + public record Profile(String nickname) { + + public static Profile from(Map attributes) { + return new Profile(String.valueOf(attributes.get("nickname"))); + } + } + + public static KakaoAccount from(Map attributes) { + return new KakaoAccount( + Boolean.valueOf(String.valueOf(attributes.get("profile_nickname_needs_agreement"))), + Profile.from((Map) attributes.get("profile")), + Boolean.valueOf(String.valueOf(attributes.get("has_email"))), + Boolean.valueOf(String.valueOf(attributes.get("email_needs_agreement"))), + Boolean.valueOf(String.valueOf(attributes.get("is_email_valid"))), + Boolean.valueOf(String.valueOf(attributes.get("is_email_verified"))), + String.valueOf(attributes.get("email")) + ); + } + + public String nickname() { + return this.profile().nickname(); + } + } + + public static KakaoOAuth2Response from(Map attributes) { + return new KakaoOAuth2Response( + Long.valueOf(String.valueOf(attributes.get("id"))), + LocalDateTime.parse( + String.valueOf(attributes.get("connected_at")), + DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.systemDefault()) + ), + (Map) attributes.get("properties"), + KakaoAccount.from((Map) attributes.get("kakao_account")) + ); + } + + public String email() { + return this.kakaoAccount().email(); + } + + public String nickname() { + return this.kakaoAccount().nickname(); + } +} diff --git a/src/main/java/org/capstone/maru/dto/security/SharedPostPrincipal.java b/src/main/java/org/capstone/maru/dto/security/SharedPostPrincipal.java new file mode 100644 index 0000000000..b70a8f464a --- /dev/null +++ b/src/main/java/org/capstone/maru/dto/security/SharedPostPrincipal.java @@ -0,0 +1,143 @@ +package org.capstone.maru.dto.security; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.Getter; +import org.capstone.maru.dto.MemberAccountDto; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.core.user.OAuth2User; + +public record SharedPostPrincipal( + String memberId, + String email, + String nickname, + Collection authorities, + Map oAuth2Attributes +) implements UserDetails, OAuth2User { + + /** + * of 파라미터를 받아서 현제 객체를 리턴합니다. + * + * @param memberId + * @param email + * @param nickname + * @return + */ + public static SharedPostPrincipal of( + String memberId, + String email, + String nickname + ) { + return of(memberId, email, nickname, Map.of()); + } + + public static SharedPostPrincipal of( + String memberId, + String email, + String nickname, + Map oAuth2Attributes + ) { + Set roleTypes = Set.of(RoleType.MEMBER); + + return new SharedPostPrincipal( + memberId, + email, + nickname, + roleTypes + .stream() + .map(RoleType::getName) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toUnmodifiableSet()), + oAuth2Attributes + ); + } + + /** + * MemberAccountDto를 받아서 현재 객체를 리턴합니다. + * + * @param dto + * @return + */ + public static SharedPostPrincipal from(MemberAccountDto dto) { + return SharedPostPrincipal.of( + dto.memberId(), + dto.email(), + dto.nickname() + ); + } + + /** + * 현재 객체를 dto로 바꿔줍니다 + * + * @return + */ + public MemberAccountDto toDto() { + return MemberAccountDto.of( + memberId, + email, + nickname + ); + } + + // -- OAuth2User -- // + @Override + public String getName() { + return memberId; + } + + @Override + public Map getAttributes() { + return oAuth2Attributes; + } + + // -- UserDetails -- // + @Override + public String getUsername() { + return memberId; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + public enum RoleType { + MEMBER("ROLE_MEMBER"); + + @Getter + private final String name; + + RoleType(String name) { + this.name = name; + } + } +} diff --git a/src/main/java/org/capstone/maru/repository/MemberAccountRepository.java b/src/main/java/org/capstone/maru/repository/MemberAccountRepository.java new file mode 100644 index 0000000000..8774cd7f06 --- /dev/null +++ b/src/main/java/org/capstone/maru/repository/MemberAccountRepository.java @@ -0,0 +1,11 @@ +package org.capstone.maru.repository; + +import java.util.Optional; +import org.capstone.maru.domain.MemberAccount; +import org.capstone.maru.dto.SocialType; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberAccountRepository extends JpaRepository { + + Optional findBySocialTypeAndSocialId(SocialType socialType, String id); +} diff --git a/src/main/java/org/capstone/maru/service/CustomOAuth2UserService.java b/src/main/java/org/capstone/maru/service/CustomOAuth2UserService.java new file mode 100644 index 0000000000..dea770a352 --- /dev/null +++ b/src/main/java/org/capstone/maru/service/CustomOAuth2UserService.java @@ -0,0 +1,89 @@ +package org.capstone.maru.service; + +import java.util.Collections; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.capstone.maru.domain.MemberAccount; +import org.capstone.maru.dto.CustomOAuth2User; +import org.capstone.maru.dto.OAuthAttributes; +import org.capstone.maru.dto.SocialType; +import org.capstone.maru.repository.MemberAccountRepository; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class CustomOAuth2UserService implements OAuth2UserService { + + private final MemberAccountRepository userRepository; + private static final String NAVER = "naver"; + private static final String KAKAO = "kakao"; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + + OAuth2UserService delegate = new DefaultOAuth2UserService(); + OAuth2User oAuth2User = delegate.loadUser(userRequest); + + /** + * userRequest에서 registrationId 추출 후 registrationId으로 SocialType 저장 + * http://localhost:8080/oauth2/authorization/kakao에서 kakao가 registrationId + * userNameAttributeName은 이후에 nameAttributeKey로 설정된다. + */ + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + SocialType socialType = getSocialType(registrationId); + String userNameAttributeName = userRequest.getClientRegistration() + .getProviderDetails().getUserInfoEndpoint() + .getUserNameAttributeName(); // OAuth2 로그인 시 키(PK)가 되는 값 + Map attributes = oAuth2User.getAttributes(); // 소셜 로그인에서 API가 제공하는 userInfo의 Json 값(유저 정보들) + + // socialType에 따라 유저 정보를 통해 OAuthAttributes 객체 생성 + OAuthAttributes extractAttributes = OAuthAttributes.of(socialType, userNameAttributeName, + attributes); + + MemberAccount createdUser = getUser(extractAttributes, socialType); + + // DefaultOAuth2User를 구현한 CustomOAuth2User 객체를 생성해서 반환 + return new CustomOAuth2User( + Collections.singleton(new SimpleGrantedAuthority(createdUser.getRole().getKey())), + attributes, + extractAttributes.getNameAttributeKey(), + createdUser.getEmail() + ); + } + + private SocialType getSocialType(String registrationId) { + if (NAVER.equals(registrationId)) { + return SocialType.NAVER; + } + if (KAKAO.equals(registrationId)) { + return SocialType.KAKAO; + } + return null; + } + + private MemberAccount getUser(OAuthAttributes attributes, SocialType socialType) { + MemberAccount findUser = userRepository.findBySocialTypeAndSocialId(socialType, + attributes.getOauth2UserInfo().getId()).orElse(null); + + if (findUser == null) { + return saveUser(attributes, socialType); + } + return findUser; + } + + /** + * OAuthAttributes의 toEntity() 메소드를 통해 빌더로 User 객체 생성 후 반환 생성된 User 객체를 DB에 저장 : socialType, + * socialId, email, role 값만 있는 상태 + */ + private MemberAccount saveUser(OAuthAttributes attributes, SocialType socialType) { + MemberAccount createdUser = attributes.toEntity(socialType, attributes.getOauth2UserInfo()); + return userRepository.save(createdUser); + } + +} diff --git a/src/main/java/org/capstone/maru/service/MemberAccountService.java b/src/main/java/org/capstone/maru/service/MemberAccountService.java new file mode 100644 index 0000000000..92760a9768 --- /dev/null +++ b/src/main/java/org/capstone/maru/service/MemberAccountService.java @@ -0,0 +1,40 @@ +package org.capstone.maru.service; + +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.capstone.maru.domain.MemberAccount; +import org.capstone.maru.dto.MemberAccountDto; +import org.capstone.maru.repository.MemberAccountRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class MemberAccountService { + + private final MemberAccountRepository memberAccountRepository; + + @Transactional(readOnly = true) + public Optional searchMember(String memberId) { + return memberAccountRepository.findById(memberId) + .map(MemberAccountDto::from); + } + + public MemberAccountDto saveUser( + String memberId, + String email, + String nickname + ) { + return MemberAccountDto.from( + memberAccountRepository.save( + MemberAccount.of( + memberId, + email, + nickname, + memberId + ) + ) + ); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 8b13789179..0000000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000000..5bc8c80370 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,61 @@ +debug: false +management.endpoints.web.exposure.include: "*" + +logging: + level: + org.org.capstone.maru: debug + org.springframework.web.servlet: debug + org.hibernate.type.descriptor.sql.BasicBinder: trace + +spring: + datasource: + driver-class-name: org.h2.Driver + url: 'jdbc:h2:mem:test' + username: sa + password: + h2: + console: + enabled: true + path: /h2-console + jpa: + database-platform: org.hibernate.dialect.H2Dialect + open-in-view: false + defer-datasource-initialization: true + hibernate: + ddl-auto: create + show-sql: true + properties: + hibernate: + dialect: org.hibernate.dialect.H2Dialect + format_sql: true + default_batch_fetch_size: 100 + use_sql_comments: false + sql: + init: + mode: always + security: + oauth2: + client: + registration: + kakao: + client-id: 0d7c948d8eaadc0e08f3e42636bba3bd + clientSecret: bIH2tF6jMbrRsdAp9DPL7yh97LP8pPXk + authorization-grant-type: authorization_code + redirect-uri: "{baseUrl}/login/oauth2/code/kakao" + client-authentication-method: client_secret_post + naver: + client-id: OvLyXvoT1NlEr8Wfiu2u + client-secret: U6Or_rMFOQ + authorization-grant-type: authorization_code + redirect-uri: http://localhost:8080/login/auth + provider: + kakao: + authorization-uri: https://kauth.kakao.com/oauth/authorize + token-uri: https://kauth.kakao.com/oauth/token + user-info-uri: https://kapi.kakao.com/v2/user/me + user-name-attribute: id + naver: + authorization-uri: https://nid.naver.com/oauth2.0/authorize + token-uri: https://nid.naver.com/oauth2.0/token + user-info-uri: https://openapi.naver.com/v1/nid/me + user-name-attribute: response diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 0000000000..022c495a52 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,100 @@ +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('739c7eaa-c311-425c-ab2f-339a62ddcb34', 'sfassbender0@java.com', 'sdarkott0', '2023-10-22', 'cportman0', '2024-01-02', 'wpeasegood0'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('9b7b19d7-9f88-49b9-8012-c00698e3d137', 'eduval1@yandex.ru', 'qarnaudet1', '2024-01-03', 'kessel1', '2023-08-25', 'mburtonwood1'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('1aad7d15-8c02-4619-a7c7-736860ce8431', 'bsavery2@list-manage.com', 'gsidnall2', '2023-07-10', 'gkeech2', '2023-11-22', 'lphillins2'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('f97d8632-248d-41c6-8b6b-bc8363ad8942', 'aokelly3@flavors.me', 'jgoldstone3', '2023-04-20', 'kjahndel3', '2024-02-03', 'ashillabear3'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('35eec375-7766-4d70-9279-0a336c52d8be', 'scrucetti4@wordpress.com', 'ashardlow4', '2023-05-07', 'rcabane4', '2023-09-02', 'dbaldinotti4'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('94f820af-5f0b-4dd5-afc8-90b8e7650e54', 'bmechell5@reverbnation.com', 'jwimsett5', '2023-04-15', 'mfaussett5', '2024-03-05', 'mbrixey5'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('730a3ff6-1fda-4f96-9c8c-622a5c35a0c2', 'tbing6@geocities.com', 'hwatling6', '2023-07-20', 'estayte6', '2023-10-28', 'gbesnardeau6'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('105a1c92-0396-4fcb-a81e-b4677f13f6c4', 'aespinos7@vinaora.com', 'fbotha7', '2023-06-17', 'dantos7', '2023-08-06', 'ablankenship7'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('6d8480d5-72da-41e8-a24f-fe7fdb278e44', 'ktry8@tumblr.com', 'rchilcott8', '2023-03-22', 'bbinny8', '2024-02-27', 'ksolesbury8'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('60455895-ab6d-4b43-aecc-1ffdc53c9fb6', 'agilliland9@skyrock.com', 'afuller9', '2023-08-16', 'pgunnell9', '2023-05-08', 'dbaudino9'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('0446bc73-57a7-4c06-9543-47656e67fa7e', 'afarrowa@meetup.com', 'mpescuda', '2023-03-19', 'bjardina', '2024-02-19', 'lpedronia'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('67e22f6b-7f22-4b0c-9656-393ffe7f24d5', 'mkentwellb@cnet.com', 'bkleinhausb', '2023-04-23', 'mbrunickeb', '2023-12-31', 'gmylesb'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('fb735475-b8a9-4c13-a91a-b18b1112be28', 'mdriussic@mlb.com', 'eshirrellc', '2023-12-31', 'gashmanc', '2023-07-07', 'efunnellc'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('c91cab58-9fa7-4ce8-a47f-16593374e158', 'dgentreaud@g.co', 'llestranged', '2024-03-06', 'kforgand', '2023-05-27', 'palexsandrovichd'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('7f1c9819-7eb1-4d9c-8d4c-028d171cbfb1', 'lfaireste@discovery.com', 'csheringhame', '2023-07-25', 'gtasselle', '2023-04-21', 'bpulhame'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('3ca19a3e-2268-47f7-baba-2f105eb42967', 'sdehavenf@aboutads.info', 'bfishwickf', '2023-10-11', 'tmethringhamf', '2023-12-21', 'ashenfischf'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('37b5317d-949b-4e68-9fe8-8ab4d6f943a4', 'wkamiyamag@cnn.com', 'ceversfieldg', '2024-01-17', 'rdoubleg', '2023-07-11', 'mchazierg'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('703eeed2-5e70-435c-a0b5-9e1a997945d3', 'jmillthorpeh@imageshack.us', 'criseamh', '2023-09-26', 'gsnawdenh', '2024-02-20', 'wpassionh'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('cf45dbbb-edc2-43f1-8bad-561c8782dfe1', 'lfellibrandi@tamu.edu', 'cdossetteri', '2024-02-10', 'wstarmorei', '2023-10-30', 'cbeamonti'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('3aea9169-b769-407a-bc52-3a6588335b9c', 'jballentimej@clickbank.net', 'jgreserj', '2024-02-09', 'scuxonj', '2023-03-16', 'ksowreyj'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('3a441813-ef5a-4665-9409-fd6d965e9cff', 'mofogertyk@fotki.com', 'jquadriok', '2024-01-27', 'rsiviork', '2023-10-04', 'obeechcraftk'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('08844fae-38e6-4439-a778-5ba4c4a53a82', 'ccannelll@myspace.com', 'jcourceyl', '2024-01-05', 'amaylerl', '2023-03-26', 'jdemetrl'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('24e1714c-d112-4168-b13e-6bd5125629ef', 'gvallentinem@amazon.de', 'tandreuccim', '2023-06-18', 'srawsthornem', '2023-11-16', 'opoletm'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('4050caa7-ec6a-44a8-835f-188b5a0c3b72', 'zmayworthn@aboutads.info', 'iszretern', '2023-12-26', 'breimersn', '2024-02-10', 'rwoolaghann'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('31a88eb0-d345-4295-9bcd-328370771dd2', 'tgiabuzzio@rakuten.co.jp', 'nkiebeso', '2023-09-13', 'jscarreo', '2024-01-27', 'galasdairo'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('11d46b9e-7e48-4766-923f-b458d9703eca', 'hhebbornep@vistaprint.com', 'stoulamainp', '2023-08-08', 'jivettsp', '2023-09-14', 'sjosifovicp'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('f6ee43b8-4e32-4330-a433-04abc0f0f0f3', 'glindermannq@php.net', 'bgligoriq', '2023-05-01', 'ncosansq', '2023-12-22', 'ngartellq'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('dfa9b064-789e-4023-8ac1-d65577296fe6', 'gorpinr@e-recht24.de', 'bsiggsr', '2023-11-28', 'nboldersonr', '2023-05-08', 'msnuggr'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('73600c53-778a-4488-a58b-96b7c46a436b', 'ddyersons@tamu.edu', 'mrowbreys', '2023-08-11', 'eskiltons', '2023-12-19', 'ygowanss'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('9c4f94aa-43ec-416c-bc5f-38c4953faf37', 'sglowachat@360.cn', 'ejimenot', '2023-03-28', 'cyarnellt', '2023-08-17', 'ecapst'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('d88d0d89-7170-4f30-8261-82e70d790be8', 'mescudieru@disqus.com', 'lseresu', '2023-11-22', 'kbloreu', '2023-10-23', 'saiersu'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('bc5c8128-6504-4b34-863b-00e18ba819ab', 'dheminsleyv@nature.com', 'dcarvillev', '2024-01-18', 'scrasterv', '2024-02-05', 'tbroxtonv'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('442b61c1-304b-4a71-8aa0-93569487a638', 'mcrilleyw@bing.com', 'myounghusbandw', '2024-02-08', 'ashortew', '2023-12-29', 'adoellew'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('b89e373c-5915-4439-819f-4b852024c3d1', 'fmairsx@163.com', 'kgeorgotx', '2023-04-13', 'clavignex', '2024-01-02', 'oellinorx'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('447d0844-31e9-4049-9c4d-0f19f807fd7e', 'bsatyfordy@qq.com', 'nmoorey', '2023-07-23', 'dkyddey', '2024-02-09', 'gluppitty'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('ea3f3dfe-a677-4227-a3a6-1e5f8fd22349', 'jfroggattz@sina.com.cn', 'jevensdenz', '2023-05-31', 'fflitcroftz', '2023-05-29', 'kgoodlifez'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('8b2def28-cc9f-4d8f-b96a-1759963d2c60', 'bmcallister10@flavors.me', 'epeter10', '2023-03-09', 'miacabucci10', '2023-09-06', 'astefi10'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('9b8a5979-7d75-4d5d-ae46-39f1d0412d44', 'asiemon11@house.gov', 'djelfs11', '2023-10-22', 'rdaton11', '2023-10-04', 'mswindall11'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('89328286-19fe-476a-aca1-9693b7a1ad55', 'rhachette12@barnesandnoble.com', 'npellamont12', '2023-08-10', 'mduerden12', '2023-08-14', 'tcana12'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('ad5bc39b-9ac9-4bb2-b77a-b6644a7f590b', 'sclementi13@csmonitor.com', 'gsackey13', '2023-08-12', 'vcowling13', '2023-09-28', 'adryburgh13'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('9ccaa6bd-c0ba-4976-a1b9-2387abde8380', 'ratrill14@howstuffworks.com', 'ahousecraft14', '2023-05-01', 'rcroux14', '2023-07-11', 'dskerritt14'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('2c958db2-ae2e-4ca6-b0cd-1a71c091ad0b', 'cphilcott15@about.com', 'ccarayol15', '2024-01-08', 'cpulman15', '2023-05-15', 'clillecrop15'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('eb118e7a-ad27-4128-bdc2-c637a46633dc', 'mquinney16@facebook.com', 'jwince16', '2023-09-13', 'dabeles16', '2023-08-30', 'kgirardin16'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('ec37c7b8-f3f3-406b-9157-967a35ea6fdc', 'hbasham17@ameblo.jp', 'sgrowy17', '2023-10-04', 'wbiddwell17', '2023-08-05', 'rseys17'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('54ceda83-48b1-48f3-9f38-e2b34d8c59db', 'mhuson18@epa.gov', 'efildery18', '2024-01-03', 'dlippo18', '2023-06-05', 'mcabera18'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('2722e820-c469-4d46-a969-fde5504b88fd', 'gpolycote19@eepurl.com', 'dschaumaker19', '2024-01-03', 'pfronek19', '2023-10-09', 'bcambridge19'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('74efa7b3-0da8-41c5-875a-58a18ce956c6', 'abetchley1a@angelfire.com', 'etoffts1a', '2023-04-07', 'ldrennan1a', '2023-08-25', 'ddomenichini1a'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('130de393-717a-48c6-9831-01506a036df5', 'aselburn1b@webs.com', 'fsauven1b', '2023-12-07', 'drambadt1b', '2023-10-04', 'hcourson1b'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('a1d9193c-ba20-45e3-99e6-87e0dc34f5c2', 'bkohlert1c@blogspot.com', 'twither1c', '2023-09-14', 'mchandlar1c', '2023-10-11', 'pogelsby1c'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('080e8f6c-8eda-43c0-bfbb-8899d340144d', 'rraeside1d@nsw.gov.au', 'bkun1d', '2024-01-08', 'glangland1d', '2023-08-28', 'cjerred1d'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('8a118490-f774-4717-8a0c-6a5dde27d367', 'luren1e@huffingtonpost.com', 'mlohde1e', '2023-09-11', 'ghindrich1e', '2023-04-21', 'sbolver1e'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('3d379ea9-21ef-4068-9415-895a34083a47', 'wcrathorne1f@vkontakte.ru', 'rjerche1f', '2023-04-14', 'tlambole1f', '2023-06-15', 'ydeferrari1f'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('b08e0bae-8097-491e-b05c-229346dd62ff', 'aberthomieu1g@wikispaces.com', 'fcobbledick1g', '2023-09-10', 'rockenden1g', '2023-03-26', 'tlydon1g'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('ff6af734-c83a-4e15-b6eb-280d5c562ee5', 'ovaux1h@dion.ne.jp', 'odeabill1h', '2023-11-03', 'wpirdy1h', '2023-04-16', 'pterrey1h'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('a13c7097-eddb-43eb-adcb-c56109fe81f1', 'rklesse1i@live.com', 'pannell1i', '2023-06-21', 'bhayworth1i', '2023-11-11', 'cmeuse1i'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('cb160413-8694-405a-8317-a927235c4fbf', 'estclair1j@japanpost.jp', 'jcloute1j', '2023-07-19', 'tribbens1j', '2023-03-23', 'bguthrum1j'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('e6fcd0bb-0c6c-4771-98c0-7416abb5953e', 'lbelward1k@jalbum.net', 'laish1k', '2024-02-02', 'icreegan1k', '2023-06-16', 'hmee1k'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('772ed8e1-a0b1-4056-940b-84f453047a84', 'csoame1l@vinaora.com', 'fabbet1l', '2023-09-10', 'gsandham1l', '2023-07-29', 'rreignould1l'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('b11cf6d1-77b0-49ab-bbdf-e4e53ba502e4', 'bbloom1m@exblog.jp', 'mkonrad1m', '2024-02-19', 'ncrumb1m', '2024-01-11', 'csleford1m'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('3d1f565b-b24b-4503-8a92-b3cf5a2b9f81', 'fpepi1n@symantec.com', 'acahillane1n', '2023-07-20', 'jzealy1n', '2024-01-20', 'radamkiewicz1n'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('3b2acf35-8b8d-4ee3-8513-45e7e2396744', 'lcaen1o@plala.or.jp', 'jmccrisken1o', '2023-05-29', 'qguenther1o', '2023-06-30', 'sbohin1o'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('ea8ca9f1-8886-4833-831a-b49c0126ebba', 'zsheere1p@dot.gov', 'ghaining1p', '2023-12-14', 'gvanarsdall1p', '2023-11-08', 'clatter1p'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('9129ff79-69bc-4fae-8a0f-2495b4ad6461', 'kmallia1q@google.co.uk', 'nmcquaker1q', '2023-08-20', 'ajebb1q', '2023-03-29', 'sboulder1q'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('ea6e8b8c-c978-489e-a0f2-dac45c16b8c3', 'mkrier1r@craigslist.org', 'astlouis1r', '2023-09-07', 'vnavarijo1r', '2024-02-08', 'hskellen1r'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('4ea1d1ed-dad5-41b5-9799-aa69c5c19e49', 'mharberer1s@exblog.jp', 'nspreckley1s', '2023-12-16', 'jwapple1s', '2024-02-17', 'tvernall1s'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('3ba02bfb-a4b9-4f0f-867b-ff3bb4fa87fb', 'jtezure1t@nyu.edu', 'efogg1t', '2023-11-26', 'caysik1t', '2024-01-05', 'sroddy1t'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('7dbce579-8c69-4c09-bf04-bc3ff6ff7e07', 'gcotelard1u@domainmarket.com', 'nmcgaughay1u', '2023-10-23', 'brickert1u', '2024-02-29', 'emckniely1u'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('9ab4537c-dfc9-4802-85e5-cbbac46d80f2', 'bgerlack1v@abc.net.au', 'jsparsholt1v', '2024-03-04', 'fchallener1v', '2023-03-18', 'sheads1v'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('6045000a-ebd8-46b8-a2ff-b1cd1353f618', 'troyds1w@washingtonpost.com', 'jrendle1w', '2023-11-03', 'btasseler1w', '2023-12-01', 'yarnal1w'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('c4d824f0-8353-4aef-807f-66da4c074474', 'sdelf1x@berkeley.edu', 'suzielli1x', '2023-04-07', 'wsimonou1x', '2023-04-15', 'hfeely1x'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('d1391a2d-a2a6-4b62-9514-1154c173e3c0', 'cblessed1y@cpanel.net', 'larrington1y', '2023-12-23', 'hlyall1y', '2023-11-23', 'gbwye1y'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('e5b81d33-bf46-46fe-a6f4-cdde78b7a06c', 'mthay1z@jalbum.net', 'kcorrea1z', '2024-01-22', 'apogue1z', '2023-11-06', 'erymer1z'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('8b1ec4a9-436f-4efe-b79b-95ca7db72472', 'lmalim20@statcounter.com', 'tperez20', '2023-12-06', 'niston20', '2023-06-01', 'mbowering20'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('c12ee837-6f11-4f9a-9844-142a5839ca1e', 'svanichev21@ow.ly', 'yvasyukhnov21', '2024-02-04', 'cspooner21', '2023-03-14', 'hmainstone21'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('1c52496c-52a5-4100-a29a-b56a925a802c', 'oelman22@yellowpages.com', 'clorkin22', '2024-02-25', 'dbockman22', '2023-11-09', 'gdesimoni22'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('019215af-281b-480e-8ccc-561744431eb9', 'akulver23@bbc.co.uk', 'bcohr23', '2023-10-13', 'ohebblewhite23', '2023-07-03', 'smarshalleck23'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('92948583-d809-4c7b-8d73-f6d2e01604ed', 'sstorres24@exblog.jp', 'gjancik24', '2023-12-08', 'mdoniso24', '2023-09-12', 'rmonksfield24'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('81e12f2d-c57b-4631-a010-fada90c9ebde', 'teustice25@phpbb.com', 'shaldon25', '2024-03-01', 'kfeaster25', '2023-10-13', 'pdobbings25'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('b1001bab-e5f6-4a22-9069-6b049d0e0441', 'hbarkas26@state.gov', 'bhune26', '2024-03-05', 'jsmiley26', '2024-01-28', 'mheisham26'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('c1d18e29-1109-4b7a-9d19-8d575ca5ea65', 'sportman27@pen.io', 'lkamiyama27', '2023-08-04', 'mvivian27', '2024-01-04', 'mcallacher27'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('e345a026-24b1-4f5d-b0f0-8b92c3749203', 'ojorger28@nps.gov', 'csrawley28', '2023-07-16', 'kcrinson28', '2023-05-12', 'akrzyzowski28'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('48d58531-26ed-4f00-b4ec-838f5e718778', 'btrolley29@senate.gov', 'scavee29', '2023-05-20', 'jtofts29', '2024-02-12', 'gglashby29'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('9c94b0cc-a9d3-4021-8ed6-f55c975737f3', 'glapish2a@stanford.edu', 'hdavidson2a', '2024-02-01', 'ckyle2a', '2023-08-12', 'gbeaty2a'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('57ccbca2-883e-4049-96c4-b7526ba6cb61', 'wwillarton2b@sun.com', 'fspur2b', '2023-10-10', 'taddams2b', '2024-02-13', 'aphilot2b'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('754b5124-de54-4e63-8c49-c8c318ffa5b3', 'jgrantham2c@princeton.edu', 'ecutsforth2c', '2023-12-05', 'lstowe2c', '2023-12-17', 'rreed2c'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('bc71c93e-c023-4635-a930-89d1efcfb51f', 'ygoggins2d@dailymail.co.uk', 'alindsay2d', '2023-12-14', 'lrobertot2d', '2024-02-17', 'gmerriott2d'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('226caed5-9948-4cff-b28a-5076e9dfed27', 'mreisenberg2e@noaa.gov', 'btwitty2e', '2023-07-26', 'ksellars2e', '2023-04-28', 'iespinosa2e'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('6172f151-3521-4fb8-a0f4-74f5797cc6ab', 'jcoils2f@51.la', 'rpfeiffer2f', '2023-10-24', 'umulvihill2f', '2023-05-25', 'itelford2f'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('1edfd20b-1970-42bf-88c0-5221e21e09f5', 'dhatchman2g@wordpress.com', 'sgoranov2g', '2023-05-11', 'hsmullen2g', '2024-01-17', 'ebasilio2g'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('5c747f5c-7436-4b79-b1fa-680da4f2ee3e', 'jlong2h@exblog.jp', 'tgartenfeld2h', '2023-06-21', 'dfrary2h', '2023-04-22', 'hjoskovitch2h'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('407449ef-dc52-4879-963b-058b85807ff5', 'gwreight2i@mlb.com', 'adent2i', '2023-10-03', 'eprestidge2i', '2023-10-23', 'aduhamel2i'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('31cc1fdb-ceba-4a0d-ad87-5f2bec3aa7e3', 'anoel2j@time.com', 'maizik2j', '2023-04-26', 'ldyzart2j', '2023-08-16', 'bwilloughley2j'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('9016ed00-a556-4ee9-93d2-81a2cd2d43f4', 'rmarchington2k@chicagotribune.com', 'cbrowncey2k', '2023-04-21', 'jklaff2k', '2023-06-01', 'aattridge2k'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('d97423f8-6ce7-4a44-b2ae-62740a8d08d6', 'agarret2l@gravatar.com', 'rjurges2l', '2024-02-15', 'cgave2l', '2023-03-26', 'jdenisco2l'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('e4a639e0-d5ef-45d8-b825-176baac4091c', 'sforrest2m@cbslocal.com', 'adeeman2m', '2023-12-06', 'lscurrah2m', '2024-01-03', 'owatsham2m'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('2247f362-b17c-4648-a9b7-02e87271ccae', 'tlalonde2n@amazonaws.com', 'bcrayton2n', '2023-12-23', 'dmalt2n', '2024-01-28', 'cmccullouch2n'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('8771c1ac-a0f1-484f-9703-504acb776678', 'mbaistow2o@reverbnation.com', 'fdurie2o', '2023-06-10', 'bwason2o', '2023-07-29', 'mpobjoy2o'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('88b5b5b3-6d60-43f1-b7ec-aa4cab2140b5', 'asakins2p@cargocollective.com', 'gsheahan2p', '2023-10-27', 'hbriars2p', '2024-02-09', 'jcaple2p'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('c6eaccf8-ae15-4952-82a2-47a420fc5bd4', 'chalahan2q@unesco.org', 'pswynley2q', '2023-06-10', 'spiegrome2q', '2024-02-08', 'dderell2q'); +insert into MEMBER_ACCOUNT (member_id, email, nickname, created_at, created_by, modified_at, modified_by) values ('ed963523-93d9-4dc3-8ba9-27267d3e8e6d', 'fternott2r@walmart.com', 'cputtock2r', '2023-05-30', 'lwakerley2r', '2023-10-26', 'hmccrae2r'); diff --git a/src/test/java/org/capstone/maru/repository/JpaRepositoryTest.java b/src/test/java/org/capstone/maru/repository/JpaRepositoryTest.java new file mode 100644 index 0000000000..98c9651c37 --- /dev/null +++ b/src/test/java/org/capstone/maru/repository/JpaRepositoryTest.java @@ -0,0 +1,69 @@ +package org.capstone.maru.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Optional; +import org.assertj.core.api.Assertions; +import org.capstone.maru.config.JpaConfig; +import org.capstone.maru.domain.MemberAccount; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.data.domain.AuditorAware; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@DisplayName("JPA 연결 테스트") +@Import(JpaRepositoryTest.TestJpaConfig.class) +@DataJpaTest +public class JpaRepositoryTest { + + private final MemberAccountRepository memberAccountRepository; + + public JpaRepositoryTest(@Autowired MemberAccountRepository memberAccountRepository) { + this.memberAccountRepository = memberAccountRepository; + } + + @DisplayName("[MemberAccount] select 테스트") + @Test + void givenNothing_whenQueryingSelect_thenReturnMembers() throws Exception { + // given + int expected = 100; + + // when + List members = memberAccountRepository.findAll(); + + // then + assertThat(members) + .isNotNull() + .hasSize(expected); + } + + @DisplayName("[MemberAccount] insert 테스트") + @Test + void givenNothing_whenQueryingInsert_thenReturnNothing() throws Exception { + // given + long previousCount = memberAccountRepository.count(); + + // when + memberAccountRepository.save(MemberAccount.of("tester", "test@email.com", "test123")); + + // then + assertThat(memberAccountRepository.count()) + .isEqualTo(previousCount + 1); + } + + @EnableJpaAuditing + @TestConfiguration + static class TestJpaConfig { + + @Bean + AuditorAware auditorAware() { + return () -> Optional.of("tester"); + } + } +} diff --git a/src/test/java/org/capstone/maru/service/MemberAccountServiceTest.java b/src/test/java/org/capstone/maru/service/MemberAccountServiceTest.java new file mode 100644 index 0000000000..70de4939ad --- /dev/null +++ b/src/test/java/org/capstone/maru/service/MemberAccountServiceTest.java @@ -0,0 +1,101 @@ +package org.capstone.maru.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; + +import java.util.Optional; +import org.capstone.maru.domain.MemberAccount; +import org.capstone.maru.dto.MemberAccountDto; +import org.capstone.maru.repository.MemberAccountRepository; +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.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@DisplayName("Service - 회원") +@ExtendWith(MockitoExtension.class) +class MemberAccountServiceTest { + + @InjectMocks + private MemberAccountService sut; + + @Mock + private MemberAccountRepository memberAccountRepository; + + @DisplayName("존재하는 회원 ID를 검색하면, 회원 데이터를 Optional 로 반환한다.") + @Test + void givenExistentMemberId_whenSearching_thenReturnsOptionalMemberData() throws Exception { + // given + String memberId = "testId"; + given(memberAccountRepository.findById(memberId)) + .willReturn(Optional.of(createMemberAccount(memberId))); + + // when + Optional result = sut.searchMember(memberId); + + // then + assertThat(result).isPresent(); + then(memberAccountRepository).should().findById(memberId); + } + + @DisplayName("존재하지 않는 회원 ID를 검색하면, 비어있는 Optional 을 반환한다.") + @Test + void givenNonexistentMemberId_whenSearching_thenReturnsOptionalMemberData() throws Exception { + // given + String wrongMemberId = "wrongId"; + given(memberAccountRepository.findById(wrongMemberId)) + .willReturn(Optional.empty()); + + // when + Optional result = sut.searchMember(wrongMemberId); + + // then + assertThat(result).isEmpty(); + then(memberAccountRepository).should().findById(wrongMemberId); + } + + @DisplayName("회원 정보를 입력하면, 새로운 회원 정보를 저장하여 가입시키고 해당 회원 데이터를 반환한다.") + @Test + void givenMemberParams_whenSaving_thenReturnsMemberAccount() throws Exception { + // given + MemberAccount memberAccount = createMemberAccount("testId"); + MemberAccount savedMemberAccount = createSigningUpMemberAccount("testId"); + given(memberAccountRepository.save(memberAccount)).willReturn(savedMemberAccount); + + // when + MemberAccountDto result = sut.saveUser( + memberAccount.getMemberId(), + memberAccount.getEmail(), + memberAccount.getNickname() + ); + + // then + assertThat(result) + .hasFieldOrPropertyWithValue("memberId", memberAccount.getMemberId()) + .hasFieldOrPropertyWithValue("email", memberAccount.getEmail()) + .hasFieldOrPropertyWithValue("nickname", memberAccount.getNickname()) + .hasFieldOrPropertyWithValue("createdBy", memberAccount.getMemberId()) + .hasFieldOrPropertyWithValue("modifiedBy", memberAccount.getMemberId()); + then(memberAccountRepository).should().save(memberAccount); + } + + private MemberAccount createMemberAccount(String memberId) { + return createMemberAccount(memberId, null); + } + + private MemberAccount createSigningUpMemberAccount(String memberId) { + return createMemberAccount(memberId, memberId); + } + + private MemberAccount createMemberAccount(String memberId, String createdBy) { + return MemberAccount.of( + memberId, + "test@mail.com", + "nickname", + createdBy + ); + } +} \ No newline at end of file