반응형
요구사항
- Spring Data JPA로 회원 데이터 구성
- UserDetailsService를 이용해 사용자 정보 로딩
- @ElementCollection을 이용해 여러 개의 권한을 갖는 회원 엔티티 구성
회원 데이터의 구성
- mid 회원 아이디
- del 탈퇴여부
- mpw 패스워드
- regDate, modDate 등록일/수정일
- email 이메일
- social 소셜 로그인 자동 회원 가입 여부
1. Spring Data JPA로 회원 데이터 구성
1. MemberRole enum 클래스로 이용해 사용자의 두 가지 권한 설정
package org.zerock.b01.domain;
public enum MemberRole {
USER, ADMIN;
}
2. Member 엔티티 작성
- 엔티티 명 : Member
- 멤버 변수 : @Id mid, mpw, email, del, social, @ElementCollection Set<Role> roleSet
- 메서드 : change={Password, Email ,Del, Social}, add={Role}, clear ={Role}
package org.zerock.b01.domain;
import lombok.*;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import java.util.HashSet;
import java.util.Set;
@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString(exclude = "roleSet")
public class Member extends BaseEntity{
@Id
private String mid;
private String mpw;
private String email;
private boolean del;
private boolean social;
//enum 클래스를 이용해 권한을 가지는 자료구조 생성
@ElementCollection(fetch = FetchType.LAZY)
@Builder.Default
private Set<MemberRole> roleSet= new HashSet<>();
//변경 가능한 정보들 메서드로 생성
public void changePassword(String mpw){
this.mpw=mpw;
}
public void changeEmail(String email){
this.email=email;
}
public void changeDel(boolean del){
this.del=del;
}
public void changeSocial(boolean social){
this.social=social;
}
//추가 가능한 정보 메서드로 생성
public void addRole(MemberRole memberRole){
this.roleSet.add(memberRole);
}
//초기화 가능한 정보들 메서드로 생성
public void clearRoles(){
this.roleSet.clear();
}
}
3. MemberRepository와 테스트 코드 작성
(1) MemberRepository 선언
-MemberRepository에 로그인 시에 MemberRole을 로딩하는 메서드 작성
package org.zerock.b01.repository;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.zerock.b01.domain.Member;
import javax.persistence.Entity;
import java.util.Optional;
public interface MemberRepository extends JpaRepository<Member, String> {
@EntityGraph(attributePaths = "roleSet")
@Query("select m from Member m where m.mid= :mid and m.social = false")
Optional<Member> getWithRoles(String mid);
}
(2) 일반 회원 추가 테스트 코드 작성
-MemeberRepositoryTests 클래스 작성
-PasswordEncoder로 mpw 처리한 일반 회원 데이터 추가하는 테스트
package org.zerock.b01.repository;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.zerock.b01.domain.Member;
import org.zerock.b01.domain.MemberRole;
import java.util.stream.IntStream;
@SpringBootTest
@Log4j2
public class MemberRepositoryTests {
@Autowired
private MemberRepository memberRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Test
public void insertMembers(){
IntStream.rangeClosed(1, 100).forEach(i->{
Member member=Member.builder()
.mid("member"+i)
.mpw(passwordEncoder.encode("1111"))
.email("email"+i+"@aaa.bbb")
.build();
member.addRole(MemberRole.USER);
if(i>=90){
member.addRole(MemberRole.ADMIN);
}
memberRepository.save(member);
});
}
}
(3) 회원 조회 테스트
-MemberRole과 함께 로딩하는지 확인
//회원 조회시 멤버의 권한도 조회하는지 확이하는 테스트
@Test
public void testRead(){
Optional<Member> result = memberRepository.getWithRoles("member100");
Member member=result.orElseThrow();
log.info(member);
log.info(member.getRoleSet());
member.getRoleSet().forEach(memberRole -> log.info(memberRole.name()));
}
Member(mid=member100, mpw=$2a$10$IY45qFsPN9FnIkN2EwN/Z.akWdpdbnXNSEMV4seAYq219YMKFgLom, email=email100@aaa.bbb, del=false, social=false) [ADMIN, USER] ADMIN USER |
2. 회원 서비스와 DTO처리
스프링 시큐리티에서 회원 DTO는 해당 API인 UserDetails라는 타입에 맞게 작성되어야 한다.
따라서, /security/dto/MemberSecurityDTO 클래스를 정의한다.
(1) MemberSecurityDTO 작성
-상속한 User 클래스를 이용한 생성자를 통해 UserDetails 타입으로 객체를 생성한다.
- DTO 명 : MemberSecurityDTO
- 멤버 변수 : mid, mpw, email, del, social
- 상속 : UserDetails 인테페이스를 구현한 User 클래스
- 생성자 : 상속한 User 클래스의 생성자 호출(username, password, authorities)
package org.zerock.b01.security.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
@Getter
@Setter
@ToString
public class MemberSecurityDTO extends User {
private String mid;
private String mpw;
private String email;
private boolean del;
private boolean social;
public MemberSecurityDTO(String username, String password, String email, boolean del, boolean social, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
this.mid=username;
this.mpw=password;
this.email= email;
this.del=del;
this.social=social;
}
}
(2) CustomUserDetailsService 수정
-실제 로그인 처리 담당하는 CustomUserDetailsService
-MemberRepository 주입받아 MemberSecurityDTO 반환
변경 전, CustomUserDetailsService
package org.zerock.b01.security;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.zerock.b01.domain.Member;
import org.zerock.b01.repository.MemberRepository;
@Log4j2
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
//패스워드 인코더 의존성 주입
private final PasswordEncoder passwordEncoder;
//로그인 처리를 위한 의존성 주입
private final MemberRepository memberRepository;
public CustomUserDetailsService(){
this.passwordEncoder=new BCryptPasswordEncoder();
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("loadUserByUsername: " + username);
// return null;
UserDetails userDetails = User.builder()
.username("user1")
//.password("1111")
.password(passwordEncoder.encode("1111"))
.authorities("ROLE_USER")
.build();
return userDetails;
}
}
변경 후, CustomUserDetailsService
package org.zerock.b01.security;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.zerock.b01.domain.Member;
import org.zerock.b01.repository.MemberRepository;
import org.zerock.b01.security.dto.MemberSecurityDTO;
import javax.swing.text.html.Option;
import java.util.Optional;
import java.util.stream.Collectors;
@Log4j2
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
//패스워드 인코더 의존성 주입
// private final PasswordEncoder passwordEncoder;
// public CustomUserDetailsService(){
// this.passwordEncoder=new BCryptPasswordEncoder();
// }
// @Override
// public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//
// log.info("loadUserByUsername: " + username);
//// return null;
//
// //사용자 확인
// Optional<Member> result = memberRepository.getWithRoles(username);
//
//
// UserDetails userDetails = User.builder()
// .username("user1")
// //.password("1111")
// .password(passwordEncoder.encode("1111"))
// .authorities("ROLE_USER")
// .build();
//
// return userDetails;
// }
//로그인 처리를 위한 의존성 주입
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("loadUserByUsername: " + username);
Optional<Member> result = memberRepository.getWithRoles(username);
if (result.isEmpty()) { //해당 아이디를 가진 사용자가 없다면
throw new UsernameNotFoundException("username not found...");
}
Member member = result.get();
MemberSecurityDTO memberSecurityDTO =
new MemberSecurityDTO(
member.getMid(),
member.getMpw(),
member.getEmail(),
member.isDel(),
false,
member.getRoleSet()
.stream().map(memberRole -> new SimpleGrantedAuthority("ROLE_" + memberRole.name()))
.collect(Collectors.toList())
);
log.info("memberSecurityDTO");
log.info(memberSecurityDTO);
return memberSecurityDTO;
}
}
회원 도메인과 연관관계
- 모놀리틱
- 하나의 서비스에 회원과 주문이 모두 같은 컨텍스트로 구성
- Member 도메인을 Board나 Reply에 연결해 다대일 연관관계 구성(@ManyToOne)
- Member 데이터를 모든 서비스에 참조해서 사용하는 구조
- 하나의 서비스에 각각의 소규모 서비스가 같은 컨텍스트로 구성
- MSA
- 여러 개의 독립 서비스를 연계해 하나의 큰 서비스 구성
- 연관관계 없이 각각의 서비스가 다른 컨텍스트로 구성돼 필요하다면 서비스를 연계해서 구성
반응형
'Server Programming > Spring Boot Backend Programming' 카테고리의 다른 글
8장 -5. 소셜 로그인 처리 (+ OAuth2, 토큰, REST API 키, 인가코드) (0) | 2022.12.15 |
---|---|
8장 -4. 회원 가입 처리 (0) | 2022.12.15 |
8장-2. 로그아웃과 자동 로그인 처리 (+ 인증된 사용자 처리, currentUser, AccessDeniedHandler) (0) | 2022.12.14 |
8장-1. 스프링 시큐리티 (+ SecurityFilterChain, webSecurityCustomizer(), @EnableGlobalMethodSecurity, CSRF 토큰) (0) | 2022.12.13 |
7장-5. 이미지 추가를 위한 컨트롤러와 화면 처리 (+ 파일명에 언더바가 들어간 경우 에러 발생) (0) | 2022.12.11 |