Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ dependencies {
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE'

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}

sourceSets {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package javalab.umc7th_mission.config.security;

import javalab.umc7th_mission.domain.Member;
import javalab.umc7th_mission.domain.enums.Gender;
import javalab.umc7th_mission.domain.enums.Role;
import javalab.umc7th_mission.repository.MemberRepository.MemberRepository;
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 java.time.LocalDate;
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<String, Object> attributes = oAuth2User.getAttributes();
Map<String, Object> properties = (Map<String, Object>) attributes.get("properties");

String nickname = (String) properties.get("nickname");
String email = nickname + "@kakao.com"; // 임시 이메일 생성

// 사용자 정보 저장 또는 업데이트
Member member = saveOrUpdateUser(email, nickname);

// 이메일을 Principal로 사용하기 위해 attributes 수정
Map<String, Object> 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("소셜로그인") // 기본값 설정
.birth(LocalDate.now())
.role(Role.USER)
.build());

return memberRepository.save(member);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package javalab.umc7th_mission.config.security;

import javalab.umc7th_mission.domain.Member;
import javalab.umc7th_mission.repository.MemberRepository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.User;
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;

@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 User.withUsername(member.getEmail())
.password(member.getPassword())
.roles(member.getRole().name())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package javalab.umc7th_mission.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()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.permitAll()
);

return http.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package javalab.umc7th_mission.converter;

import javalab.umc7th_mission.domain.Member;
import javalab.umc7th_mission.domain.enums.Gender;
import javalab.umc7th_mission.web.dto.Member.MemberRequestDTO;

import java.util.ArrayList;

public class MemberConverter {
public static Member toMember(MemberRequestDTO.JoinDto request) {
Gender gender = null;
switch (request.getGender()) {
case 1: gender = Gender.MALE; break;
case 2: gender = Gender.FEMALE; break;
case 3: gender = Gender.NONE; break;
}

return Member.builder()
.name(request.getName())
.email(request.getEmail()) // 추가된 코드
.password(request.getPassword()) // 추가된 코드
.gender(gender)
.birth(request.getBirth())
.address(request.getAddress())
.specAddress(request.getSpecAddress())
.role(request.getRole()) // 추가된 코드
.memberFoodList(new ArrayList<>())
.build();
}
}
18 changes: 15 additions & 3 deletions src/main/java/javalab/umc7th_mission/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import javalab.umc7th_mission.domain.common.BaseEntity;
import javalab.umc7th_mission.domain.enums.Gender;
import javalab.umc7th_mission.domain.enums.MemberStatus;
import javalab.umc7th_mission.domain.enums.Role;
import javalab.umc7th_mission.domain.enums.SocialType;
import javalab.umc7th_mission.domain.mapping.MemberFood;
import javalab.umc7th_mission.domain.mapping.MemberTerm;
Expand Down Expand Up @@ -36,6 +37,12 @@ public class Member extends BaseEntity {
@Size(max = 30)
private String email;

@Column(nullable = false)
private String password;

@Enumerated(EnumType.STRING)
private Role role;

@NotNull
@Size(max = 20)
private String name;
Expand All @@ -47,18 +54,19 @@ public class Member extends BaseEntity {
@Size(max = 50)
private String address;

@NotNull
@Size(max = 50)
private String specAddress;

private Integer point;

@Enumerated(EnumType.STRING)
@Size(max = 10)
private Gender gender;

@Enumerated(EnumType.STRING)
@Size(max = 10)
private SocialType socialType;

@Enumerated(EnumType.STRING)
@Size(max = 10)
@ColumnDefault("'ACTIVE'")
private MemberStatus status;

Expand All @@ -79,4 +87,8 @@ public class Member extends BaseEntity {
@OneToMany(mappedBy = "member")
@Builder.Default
private List<MemberFood> memberFoodList = new ArrayList<>();

public void encodePassword(String password) {
this.password = password;
}
}
5 changes: 5 additions & 0 deletions src/main/java/javalab/umc7th_mission/domain/enums/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package javalab.umc7th_mission.domain.enums;

public enum Role {
ADMIN, USER
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ public class MemberFood extends BaseEntity {
@ManyToOne
@JoinColumn(name = "food_id")
private FoodCategory foodCategory;

public void setMember(Member member) {
this.member = member;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package javalab.umc7th_mission.service.MemberService;

import javalab.umc7th_mission.domain.Member;
import javalab.umc7th_mission.web.dto.Member.MemberRequestDTO;

public interface MemberCommandService {
Member joinMember(MemberRequestDTO.JoinDto request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package javalab.umc7th_mission.service.MemberService;

import javalab.umc7th_mission.apiPayload.code.status.ErrorStatus;
import javalab.umc7th_mission.apiPayload.exception.GeneralException;
import javalab.umc7th_mission.converter.MemberConverter;
import javalab.umc7th_mission.converter.MemberFoodConverter;
import javalab.umc7th_mission.domain.FoodCategory;
import javalab.umc7th_mission.domain.Member;
import javalab.umc7th_mission.domain.mapping.MemberFood;
import javalab.umc7th_mission.repository.FoodCategoryRepository.FoodCategoryRepository;
import javalab.umc7th_mission.repository.MemberRepository.MemberRepository;
import javalab.umc7th_mission.web.dto.Member.MemberRequestDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class MemberCommandServiceImpl implements MemberCommandService {

private final MemberRepository memberRepository;
private final FoodCategoryRepository foodCategoryRepository;
private final PasswordEncoder passwordEncoder;

@Override
public Member joinMember(MemberRequestDTO.JoinDto request) {
Member newMember = MemberConverter.toMember(request);
newMember.encodePassword(passwordEncoder.encode(request.getPassword()));

List<FoodCategory> foodCategoryList = request.getPreferCategory().stream()
.map(categoryId -> {
return foodCategoryRepository.findById(categoryId).orElseThrow(() -> new GeneralException(ErrorStatus.FOOD_CATEGORY_NOT_FOUND));
}).collect(Collectors.toList());

List<MemberFood> memberPreferList = MemberFoodConverter.toMemberPreferList(foodCategoryList);
memberPreferList.forEach(m -> m.setMember(newMember));

return memberRepository.save(newMember);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package javalab.umc7th_mission.web.controller;

import javalab.umc7th_mission.service.MemberService.MemberCommandService;
import javalab.umc7th_mission.web.dto.Member.MemberRequestDTO;
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;

@Controller
@RequiredArgsConstructor
public class MemberViewController {

private final MemberCommandService memberCommandService;

@PostMapping("/members/signup")
public String joinMember(@ModelAttribute("memberJoinDto") MemberRequestDTO.JoinDto request, // 협업시에는 기존 RequestBody 어노테이션을 붙여주시면 됩니다!
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
// 뷰에 데이터 바인딩이 실패할 경우 signup 페이지를 유지합니다.
return "signup";
}

try {
memberCommandService.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";
}
}
Loading