From ab43a655db7f86dda3f1b202a50460f300c9d25d Mon Sep 17 00:00:00 2001 From: bingseok Date: Thu, 5 Dec 2024 19:54:16 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[feat]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 9 +++ .../generated/umc/spring/domain/QMember.java | 4 ++ .../spring/domain/mapping/QMemberMission.java | 2 +- .../security/CustomUserDetailsService.java | 29 ++++++++ .../config/security/SecurityConfig.java | 41 +++++++++++ .../umc/spring/converter/MemberConverter.java | 3 + .../converter/MemberMissionConverter.java | 2 +- .../umc/spring/converter/StoreConverter.java | 2 + src/main/java/umc/spring/domain/Member.java | 13 +++- .../{mapping => enums}/MissionStatus.java | 2 +- .../java/umc/spring/domain/enums/Role.java | 5 ++ .../spring/domain/mapping/MemberMission.java | 2 +- .../MemberMissionRepository.java | 2 +- .../MemberRepository/MemberRepository.java | 3 + .../MemberMissionServiceImpl.java | 3 +- .../MemberSevice/MemberServiceImpl.java | 5 ++ .../web/controller/MemberViewController.java | 59 +++++++++++++++ .../web/controller/StoreController.java | 2 +- .../web/dto/MemberDTO/MemberRequestDTO.java | 11 ++- src/main/resources/templates/admin.html | 10 +++ src/main/resources/templates/home.html | 17 +++++ src/main/resources/templates/login.html | 24 +++++++ src/main/resources/templates/signup.html | 71 +++++++++++++++++++ 23 files changed, 311 insertions(+), 10 deletions(-) create mode 100644 src/main/java/umc/spring/config/security/CustomUserDetailsService.java create mode 100644 src/main/java/umc/spring/config/security/SecurityConfig.java rename src/main/java/umc/spring/domain/{mapping => enums}/MissionStatus.java (61%) create mode 100644 src/main/java/umc/spring/domain/enums/Role.java create mode 100644 src/main/java/umc/spring/web/controller/MemberViewController.java create mode 100644 src/main/resources/templates/admin.html create mode 100644 src/main/resources/templates/home.html create mode 100644 src/main/resources/templates/login.html create mode 100644 src/main/resources/templates/signup.html diff --git a/build.gradle b/build.gradle index 95db1c1..ac9ebda 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,15 @@ dependencies { // Testing testImplementation 'org.springframework.book:spring-boo-starter-test' + + // thymeleaf + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE' + + // spring security + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' + } sourceSets { diff --git a/src/main/generated/umc/spring/domain/QMember.java b/src/main/generated/umc/spring/domain/QMember.java index ff8e19e..988dfd0 100644 --- a/src/main/generated/umc/spring/domain/QMember.java +++ b/src/main/generated/umc/spring/domain/QMember.java @@ -45,12 +45,16 @@ public class QMember extends EntityPathBase { public final StringPath name = createString("name"); + public final StringPath password = createString("password"); + public final StringPath phoneNumber = createString("phoneNumber"); public final NumberPath point = createNumber("point", Integer.class); public final ListPath reviewList = this.createList("reviewList", Review.class, QReview.class, PathInits.DIRECT2); + public final EnumPath role = createEnum("role", umc.spring.domain.enums.Role.class); + public final EnumPath socialType = createEnum("socialType", umc.spring.domain.enums.SocialType.class); public final StringPath specAddress = createString("specAddress"); diff --git a/src/main/generated/umc/spring/domain/mapping/QMemberMission.java b/src/main/generated/umc/spring/domain/mapping/QMemberMission.java index 101b761..29da571 100644 --- a/src/main/generated/umc/spring/domain/mapping/QMemberMission.java +++ b/src/main/generated/umc/spring/domain/mapping/QMemberMission.java @@ -33,7 +33,7 @@ public class QMemberMission extends EntityPathBase { public final umc.spring.domain.QMission mission; - public final EnumPath status = createEnum("status", MissionStatus.class); + public final EnumPath status = createEnum("status", umc.spring.domain.enums.MissionStatus.class); //inherited public final DateTimePath updatedAt = _super.updatedAt; diff --git a/src/main/java/umc/spring/config/security/CustomUserDetailsService.java b/src/main/java/umc/spring/config/security/CustomUserDetailsService.java new file mode 100644 index 0000000..838f11e --- /dev/null +++ b/src/main/java/umc/spring/config/security/CustomUserDetailsService.java @@ -0,0 +1,29 @@ +package umc.spring.config.security; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import umc.spring.domain.Member; +import umc.spring.repository.MemberRepository.MemberRepository; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final MemberRepository memberRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Member member = memberRepository.findByEmail(username) + .orElseThrow(() -> new UsernameNotFoundException("해당 이메일을 가진 유저가 없습니다.")); + + return org.springframework.security.core.userdetails.User + .withUsername(member.getEmail()) + .password(member.getPassword()) + .roles(member.getRole().name()) + .build(); + + } +} diff --git a/src/main/java/umc/spring/config/security/SecurityConfig.java b/src/main/java/umc/spring/config/security/SecurityConfig.java new file mode 100644 index 0000000..0a73e01 --- /dev/null +++ b/src/main/java/umc/spring/config/security/SecurityConfig.java @@ -0,0 +1,41 @@ +package umc.spring.config.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@EnableWebSecurity +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((requests) -> requests + .requestMatchers("/", "/home", "/signup", "/members/signup", "/css/**").permitAll() + .requestMatchers("/admin/**").hasRole("ADMIN") + .anyRequest().authenticated() + ) + .formLogin((form) -> form + .loginPage("/login") + .defaultSuccessUrl("/home", true) + .permitAll() + ) + .logout((logout) -> logout + .logoutUrl("/logout") + .logoutSuccessUrl("/login?logout") + .permitAll() + ); + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/umc/spring/converter/MemberConverter.java b/src/main/java/umc/spring/converter/MemberConverter.java index 269745a..88bc9e2 100644 --- a/src/main/java/umc/spring/converter/MemberConverter.java +++ b/src/main/java/umc/spring/converter/MemberConverter.java @@ -41,10 +41,13 @@ public static Member toMember(MemberRequestDTO.JoinDTO request) { return Member.builder() .address(request.getAddress()) + .email(request.getEmail()) + .password(request.getPassword()) .specAddress(request.getSpecAddress()) .gender(gender) .name(request.getName()) .memberPreferList(new ArrayList<>()) + .role(request.getRole()) .build(); } diff --git a/src/main/java/umc/spring/converter/MemberMissionConverter.java b/src/main/java/umc/spring/converter/MemberMissionConverter.java index 9788afd..4fc7c97 100644 --- a/src/main/java/umc/spring/converter/MemberMissionConverter.java +++ b/src/main/java/umc/spring/converter/MemberMissionConverter.java @@ -3,7 +3,7 @@ import umc.spring.domain.Member; import umc.spring.domain.Mission; import umc.spring.domain.mapping.MemberMission; -import umc.spring.domain.mapping.MissionStatus; +import umc.spring.domain.enums.MissionStatus; import umc.spring.web.dto.MemberMissionDTO.MemberMissionResponseDTO; import java.time.LocalDateTime; diff --git a/src/main/java/umc/spring/converter/StoreConverter.java b/src/main/java/umc/spring/converter/StoreConverter.java index 5b86e9f..11a5ff6 100644 --- a/src/main/java/umc/spring/converter/StoreConverter.java +++ b/src/main/java/umc/spring/converter/StoreConverter.java @@ -50,6 +50,8 @@ public static MissionResponseDTO.MissionPreviewListDTO missionPreviewListDTO(Pag .totalElements(missionList.getTotalElements()) .listSize(missionPreviewDTOList.size()) .missionList(missionPreviewDTOList) + .build(); + } public static ReviewResponseDTO.ReviewPreviewDTO reviewPreviewDTO(Review review) { return ReviewResponseDTO.ReviewPreviewDTO.builder() .ownerNickname(review.getMember().getName()) diff --git a/src/main/java/umc/spring/domain/Member.java b/src/main/java/umc/spring/domain/Member.java index b56cd6c..0eb59cd 100644 --- a/src/main/java/umc/spring/domain/Member.java +++ b/src/main/java/umc/spring/domain/Member.java @@ -8,6 +8,7 @@ import umc.spring.domain.common.BaseEntity; import umc.spring.domain.enums.Gender; import umc.spring.domain.enums.MemberStatus; +import umc.spring.domain.enums.Role; import umc.spring.domain.enums.SocialType; import umc.spring.domain.mapping.MemberAgree; import umc.spring.domain.mapping.MemberMission; @@ -52,7 +53,7 @@ public class Member extends BaseEntity { private LocalDate inactiveDate; -// @Column(nullable = false, length = 50) + @Column(nullable = false, unique = true, length = 50) private String email; @Column(length = 15) @@ -61,6 +62,12 @@ public class Member extends BaseEntity { @ColumnDefault("0") private Integer point; + @Column(nullable = false) + private String password; + + @Enumerated(EnumType.STRING) + private Role role; + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) private List memberAgreeList = new ArrayList<>(); @@ -73,4 +80,8 @@ public class Member extends BaseEntity { @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) private List memberMissionList = new ArrayList<>(); + public void encodePassword(String password) { + this.password = password; + } + } diff --git a/src/main/java/umc/spring/domain/mapping/MissionStatus.java b/src/main/java/umc/spring/domain/enums/MissionStatus.java similarity index 61% rename from src/main/java/umc/spring/domain/mapping/MissionStatus.java rename to src/main/java/umc/spring/domain/enums/MissionStatus.java index f090fb4..e6182ee 100644 --- a/src/main/java/umc/spring/domain/mapping/MissionStatus.java +++ b/src/main/java/umc/spring/domain/enums/MissionStatus.java @@ -1,4 +1,4 @@ -package umc.spring.domain.mapping; +package umc.spring.domain.enums; public enum MissionStatus { CHALLENGING, COMPLETE diff --git a/src/main/java/umc/spring/domain/enums/Role.java b/src/main/java/umc/spring/domain/enums/Role.java new file mode 100644 index 0000000..61d3b50 --- /dev/null +++ b/src/main/java/umc/spring/domain/enums/Role.java @@ -0,0 +1,5 @@ +package umc.spring.domain.enums; + +public enum Role { + ADMIN, USER +} diff --git a/src/main/java/umc/spring/domain/mapping/MemberMission.java b/src/main/java/umc/spring/domain/mapping/MemberMission.java index 9b4587f..80469fe 100644 --- a/src/main/java/umc/spring/domain/mapping/MemberMission.java +++ b/src/main/java/umc/spring/domain/mapping/MemberMission.java @@ -2,10 +2,10 @@ import jakarta.persistence.*; import lombok.*; -import org.hibernate.annotations.Fetch; import umc.spring.domain.Member; import umc.spring.domain.Mission; import umc.spring.domain.common.BaseEntity; +import umc.spring.domain.enums.MissionStatus; @Entity @Getter diff --git a/src/main/java/umc/spring/repository/MemberMissionRepository/MemberMissionRepository.java b/src/main/java/umc/spring/repository/MemberMissionRepository/MemberMissionRepository.java index 2d897f7..e127c3b 100644 --- a/src/main/java/umc/spring/repository/MemberMissionRepository/MemberMissionRepository.java +++ b/src/main/java/umc/spring/repository/MemberMissionRepository/MemberMissionRepository.java @@ -7,7 +7,7 @@ import org.springframework.data.repository.query.Param; import umc.spring.domain.Mission; import umc.spring.domain.mapping.MemberMission; -import umc.spring.domain.mapping.MissionStatus; +import umc.spring.domain.enums.MissionStatus; import java.util.Optional; diff --git a/src/main/java/umc/spring/repository/MemberRepository/MemberRepository.java b/src/main/java/umc/spring/repository/MemberRepository/MemberRepository.java index aa953ac..3f78a20 100644 --- a/src/main/java/umc/spring/repository/MemberRepository/MemberRepository.java +++ b/src/main/java/umc/spring/repository/MemberRepository/MemberRepository.java @@ -3,5 +3,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import umc.spring.domain.Member; +import java.util.Optional; + public interface MemberRepository extends JpaRepository , MemberRepositoryCustom { + Optional findByEmail(String email); } diff --git a/src/main/java/umc/spring/service/MemberMissionService/MemberMissionServiceImpl.java b/src/main/java/umc/spring/service/MemberMissionService/MemberMissionServiceImpl.java index 5fcf3a5..4c5291d 100644 --- a/src/main/java/umc/spring/service/MemberMissionService/MemberMissionServiceImpl.java +++ b/src/main/java/umc/spring/service/MemberMissionService/MemberMissionServiceImpl.java @@ -4,7 +4,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; -import org.springframework.validation.Errors; import umc.spring.apiPayload.code.status.ErrorStatus; import umc.spring.apiPayload.exception.handler.MemberHandler; import umc.spring.apiPayload.exception.handler.MissionHandler; @@ -12,7 +11,7 @@ import umc.spring.domain.Member; import umc.spring.domain.Mission; import umc.spring.domain.mapping.MemberMission; -import umc.spring.domain.mapping.MissionStatus; +import umc.spring.domain.enums.MissionStatus; import umc.spring.repository.MemberMissionRepository.MemberMissionRepository; import umc.spring.repository.MemberRepository.MemberRepository; import umc.spring.repository.MissionRepository.MissionRepository; diff --git a/src/main/java/umc/spring/service/MemberSevice/MemberServiceImpl.java b/src/main/java/umc/spring/service/MemberSevice/MemberServiceImpl.java index baf13f8..05f93ba 100644 --- a/src/main/java/umc/spring/service/MemberSevice/MemberServiceImpl.java +++ b/src/main/java/umc/spring/service/MemberSevice/MemberServiceImpl.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import umc.spring.apiPayload.code.status.ErrorStatus; import umc.spring.apiPayload.exception.handler.FoodCategoryHandler; @@ -28,12 +29,16 @@ public class MemberServiceImpl implements MemberService { private final MemberRepository memberRepository; private final FoodCategoryRepository foodCategoryRepository; private final ReviewRepository reviewRepository; + private final PasswordEncoder passwordEncoder; @Override @Transactional public Member joinMember(MemberRequestDTO.JoinDTO request) { Member newMember = MemberConverter.toMember(request); + + newMember.encodePassword(passwordEncoder.encode(request.getPassword())); + List foodCategoryList = request.getPreferCategory().stream() .map(category -> { return foodCategoryRepository.findById(category).orElseThrow(() -> new FoodCategoryHandler(ErrorStatus.FOOD_CATEGORY_NOT_FOUND)); diff --git a/src/main/java/umc/spring/web/controller/MemberViewController.java b/src/main/java/umc/spring/web/controller/MemberViewController.java new file mode 100644 index 0000000..c02bf75 --- /dev/null +++ b/src/main/java/umc/spring/web/controller/MemberViewController.java @@ -0,0 +1,59 @@ +package umc.spring.web.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import umc.spring.service.MemberSevice.MemberServiceImpl; +import umc.spring.web.dto.MemberDTO.MemberRequestDTO; + +@Controller +@RequiredArgsConstructor +public class MemberViewController { + + private final MemberServiceImpl memberService; + + + @PostMapping("/members/signup") + public String joinMember(@ModelAttribute("memberJoinDto") MemberRequestDTO.JoinDTO request, // 협업시에는 기존 RequestBody 어노테이션을 붙여주시면 됩니다! + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + // 뷰에 데이터 바인딩이 실패할 경우 signup 페이지를 유지합니다. + return "signup"; + } + + try { + memberService.joinMember(request); + return "redirect:/login"; + } catch (Exception e) { + // 회원가입 과정에서 에러가 발생할 경우 에러 메시지를 보내고, signup 페이디를 유지합니다. + model.addAttribute("error", e.getMessage()); + return "signup"; + } + } + + @GetMapping("/login") + public String loginPage() { + return "login"; + } + + @GetMapping("/signup") + public String signupPage(Model model) { + model.addAttribute("memberJoinDto", new MemberRequestDTO.JoinDTO()); + return "signup"; + } + + @GetMapping("/home") + public String home() { + return "home"; + } + + @GetMapping("/admin") + public String admin() { + return "admin"; + } +} diff --git a/src/main/java/umc/spring/web/controller/StoreController.java b/src/main/java/umc/spring/web/controller/StoreController.java index 20d89b9..3d57694 100644 --- a/src/main/java/umc/spring/web/controller/StoreController.java +++ b/src/main/java/umc/spring/web/controller/StoreController.java @@ -55,7 +55,7 @@ public ApiResponse join(@RequestBody @Valid Stor @Parameters({ @Parameter(name = "storeId", description = "가게의 아이디, path variable 입니다!") }) - public ApiResponse getReviewList(@ExistStores @PathVariable(name = "storeId") Long storeId, @CheckPage @RequestParam(name = "page") Integer page){ + public ApiResponse getMissionList(@ExistStores @PathVariable(name = "storeId") Long storeId, @CheckPage @RequestParam(name = "page") Integer page){ Page missionList = storeService.getMissionList(storeId, page-1); return ApiResponse.onSuccess(StoreConverter.missionPreviewListDTO(missionList)); } diff --git a/src/main/java/umc/spring/web/dto/MemberDTO/MemberRequestDTO.java b/src/main/java/umc/spring/web/dto/MemberDTO/MemberRequestDTO.java index d5dcf72..fd5d9b1 100644 --- a/src/main/java/umc/spring/web/dto/MemberDTO/MemberRequestDTO.java +++ b/src/main/java/umc/spring/web/dto/MemberDTO/MemberRequestDTO.java @@ -1,9 +1,11 @@ package umc.spring.web.dto.MemberDTO; +import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Getter; +import umc.spring.domain.enums.Role; import umc.spring.validation.annotation.ExistCategories; import java.util.List; @@ -14,8 +16,13 @@ public class MemberRequestDTO { public static class JoinDTO { @NotBlank String name; + @NotBlank + @Email + String email; + @NotBlank + String password; @NotNull - Integer gender; + Integer gender = 3; @NotNull Integer birthYear; @NotNull @@ -28,5 +35,7 @@ public static class JoinDTO { String specAddress; @ExistCategories List preferCategory; + @NotNull + Role role; } } diff --git a/src/main/resources/templates/admin.html b/src/main/resources/templates/admin.html new file mode 100644 index 0000000..55dbff1 --- /dev/null +++ b/src/main/resources/templates/admin.html @@ -0,0 +1,10 @@ + + + + Admin Page + + +

Admin Page

+

관리자만 접근할 수 있는 페이지입니다.

+ + \ No newline at end of file diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html new file mode 100644 index 0000000..529b72c --- /dev/null +++ b/src/main/resources/templates/home.html @@ -0,0 +1,17 @@ + + + + Home + + +

Welcome to Home Page!

+

+ + + +
+ +
+ \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..618dc01 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,24 @@ + + + + Login + + +

Login

+
+
+ + +
+
+ + +
+ +
+

사용자 이름 또는 비밀번호가 잘못되었습니다.

+

로그아웃되었습니다.

+ +

계정이 없나요? Sign up

+ + \ No newline at end of file diff --git a/src/main/resources/templates/signup.html b/src/main/resources/templates/signup.html new file mode 100644 index 0000000..ff2a177 --- /dev/null +++ b/src/main/resources/templates/signup.html @@ -0,0 +1,71 @@ + + + + 회원가입 + + + +

회원가입

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + + +
+
+
+ + +
+ +
+ + \ No newline at end of file From c5086e55556835460c82d2c7b03d458c6616a12a Mon Sep 17 00:00:00 2001 From: bingseok Date: Thu, 5 Dec 2024 20:13:57 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[feat]=20kakao=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + .../security/CustomOAuth2UserService.java | 65 +++++++++++++++++++ .../config/security/SecurityConfig.java | 5 ++ src/main/resources/application.yml | 20 +++++- src/main/resources/templates/login.html | 2 + 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/main/java/umc/spring/config/security/CustomOAuth2UserService.java diff --git a/build.gradle b/build.gradle index ac9ebda..ed03004 100644 --- a/build.gradle +++ b/build.gradle @@ -74,6 +74,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.security:spring-security-test' + // oauth2 + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + } sourceSets { diff --git a/src/main/java/umc/spring/config/security/CustomOAuth2UserService.java b/src/main/java/umc/spring/config/security/CustomOAuth2UserService.java new file mode 100644 index 0000000..f993fce --- /dev/null +++ b/src/main/java/umc/spring/config/security/CustomOAuth2UserService.java @@ -0,0 +1,65 @@ +package umc.spring.config.security; + +import lombok.RequiredArgsConstructor; +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.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; +import umc.spring.domain.Member; +import umc.spring.domain.enums.Gender; +import umc.spring.domain.enums.Role; +import umc.spring.repository.MemberRepository.MemberRepository; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); + + Map attributes = oAuth2User.getAttributes(); + Map properties = (Map) attributes.get("properties"); + + String nickname = (String) properties.get("nickname"); + String email = nickname + "@kakao.com"; // 임시 이메일 생성 + + // 사용자 정보 저장 또는 업데이트 + Member member = saveOrUpdateUser(email, nickname); + + // 이메일을 Principal로 사용하기 위해 attributes 수정 + Map modifiedAttributes = new HashMap<>(attributes); + modifiedAttributes.put("email", email); + + return new DefaultOAuth2User( + oAuth2User.getAuthorities(), + modifiedAttributes, + "email" // email Principal로 설정 + ); + } + + private Member saveOrUpdateUser(String email, String nickname) { + Member member = memberRepository.findByEmail(email) + .orElse(Member.builder() + .email(email) + .name(nickname) + .password(passwordEncoder.encode("OAUTH_USER_" + UUID.randomUUID())) + .gender(Gender.NONE) // 기본값 설정 + .address("소셜로그인") // 기본값 설정 + .specAddress("소셜로그인") // 기본값 설정 + .role(Role.USER) + .build()); + + return memberRepository.save(member); + } +} \ No newline at end of file diff --git a/src/main/java/umc/spring/config/security/SecurityConfig.java b/src/main/java/umc/spring/config/security/SecurityConfig.java index 0a73e01..336a8b4 100644 --- a/src/main/java/umc/spring/config/security/SecurityConfig.java +++ b/src/main/java/umc/spring/config/security/SecurityConfig.java @@ -29,6 +29,11 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .logoutUrl("/logout") .logoutSuccessUrl("/login?logout") .permitAll() + ) + .oauth2Login(oauth2 -> oauth2 + .loginPage("/login") + .defaultSuccessUrl("/home", true) + .permitAll() ); return http.build(); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 60a1ba9..f4a8500 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -16,4 +16,22 @@ spring: use_sql_comments: true hbm2ddl: auto: update - default_batch_fetch_size: 1000 \ No newline at end of file + default_batch_fetch_size: 1000 + security: + oauth2: + client: + registration: + kakao: + client-authentication-method: client_secret_post + client-id: 58cc10364c7177fdc5d8e9c610ddbae4 + client-secret: 6b0apTTA4ixPsMHBNgPuG4Kwl5pfHhBv + redirect-uri: http://localhost:8080/login/oauth2/code/kakao + authorization-grant-type: authorization_code + scope: profile_nickname + client-name: Kakao + 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 \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html index 618dc01..687a680 100644 --- a/src/main/resources/templates/login.html +++ b/src/main/resources/templates/login.html @@ -20,5 +20,7 @@

Login

로그아웃되었습니다.

계정이 없나요? Sign up

+ +카카오로 로그인 \ No newline at end of file From 6375c00f7700a5eb0de9394e25eb5a0d77c832b9 Mon Sep 17 00:00:00 2001 From: bingseok Date: Fri, 6 Dec 2024 17:09:13 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[fix]=20dto=EC=97=90=20setter=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/umc/spring/web/dto/MemberDTO/MemberRequestDTO.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/umc/spring/web/dto/MemberDTO/MemberRequestDTO.java b/src/main/java/umc/spring/web/dto/MemberDTO/MemberRequestDTO.java index fd5d9b1..b7de8a3 100644 --- a/src/main/java/umc/spring/web/dto/MemberDTO/MemberRequestDTO.java +++ b/src/main/java/umc/spring/web/dto/MemberDTO/MemberRequestDTO.java @@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Getter; +import lombok.Setter; import umc.spring.domain.enums.Role; import umc.spring.validation.annotation.ExistCategories; @@ -13,6 +14,7 @@ public class MemberRequestDTO { @Getter + @Setter public static class JoinDTO { @NotBlank String name; @@ -22,7 +24,7 @@ public static class JoinDTO { @NotBlank String password; @NotNull - Integer gender = 3; + Integer gender; @NotNull Integer birthYear; @NotNull From 1661f9e7cea4dd32e270856cc15b87d735075a4b Mon Sep 17 00:00:00 2001 From: bingseok Date: Fri, 6 Dec 2024 17:51:07 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[fix]=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f4a8500..136ddbe 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -23,8 +23,8 @@ spring: registration: kakao: client-authentication-method: client_secret_post - client-id: 58cc10364c7177fdc5d8e9c610ddbae4 - client-secret: 6b0apTTA4ixPsMHBNgPuG4Kwl5pfHhBv + client-id: ${CLIENT_ID} + client-secret: ${CLIENT_SECRET} redirect-uri: http://localhost:8080/login/oauth2/code/kakao authorization-grant-type: authorization_code scope: profile_nickname