이전 게시글에서는 스프링 부트를 이용해서 포트폴리오를 만들기 위하여 기본 프로젝트를 생성하였고, 부트스트랩을 적용하여 뷰에 대한 템플레이트 레이아웃을 적용하였다. 또한 기본 security를 적용하여 로그인까지 적용하였다. 그러나 아직 회원관리 기능이나 다른 기능이 적용되지 않은 상태이다.

이번 게시글에서는 회원관리 기능을 구현하기 위하여 사용자의 프로파일 정보를 구성하고, 데이터베이스 테이블을 생성하고 시험용 사용자를 만들고자 한다. security에서는 비밀번호를 암호화하여 저장하므로 역시 PasswordEncoder도 적용한다.

우선 사용자 프로파일 정보를 정의해 본다.

- email : 30자 이하

- password : 64 바이트

- name : 이름, 30바이트

- nickname : 별명, 30바이트 이하

- phone : 전화번호, 15바이트 이하

- address : 주소, 255바이트 이하

- birthday : 생년월일, 15바이트 이하 - s/l.2000.12.12 (s:양력, l:음력)

- fromSocial : SNS 회원가입 여부 (0: 기본, 1: SNS가입)

- regdate : 가입일

- moddate : 정보 변경일

스프링 부트에서는 JPA를 사용하고, Entity를 사용하여 데이터베이스 테이블을 조작한다. 모든 Entity는 기본적으로 regdate와 moddate가 기본적으로 사용되는 편이므로 BaseEntity라는 것을 정의하여 regdate와 moddate를 저장한다.

com.example.sboot.common.entity 패키지에 다음과 같이 BaseEntity.java를 추가한다.

package com.example.sboot.common.entity;

import java.time.LocalDateTime;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import lombok.Getter;

@MappedSuperclass		// 테이블로 생성되지 않음
@EntityListeners(value={AuditingEntityListener.class})	// Entity 객체가 생성/변경되는 것을 감지
@Getter
public abstract class BaseEntity {
	@CreatedDate		// JPA에서 엔티티의 생성시간을 자동으로 처리
	@Column(name="regdate", updatable=false)
	private LocalDateTime regDate;
	
	@LastModifiedDate	// JPA에서 엔티티의 수정시간을 자동으로 처리
	@Column(name="moddate")
	private LocalDateTime modDate;
}

BaseEntity.java는 객체화하지 못하도록 추상 클래스로 정의되며, @MappedSuperclass 어노테이션을 추가하여 JPA가 테이블로 생성하지 않토록한다. 또한 BaseEntity를 상속하여 만든 엔티티의 변경이 있을 경우 JPA에 의하여 자동으로 테이블이 변경되도록 하기 위하여 @EntityListeners(value={AuditingEntityListener.clss})를 추가한다. 또한 main() 메소드가 있는 실행 클래스인 SbootApplication.java에 @EnableJpaAuditing 어노테이션을 추가한다.

package com.example.sboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class SbootApplication {
	public static void main(String[] args) {
		SpringApplication.run(SbootApplication.class, args);
	}
}

regDate와 modDate는 자바 8에서 추가된 LocalDateTime 클래스를 사용한다. regDate는 열이 추가될 때 자동으로 생성이 될 수 있도록 @CreatedDate 어노테이션을 추가하고, updatable=false로 하여 열 정보의 변경시 값이 변경이 되지 않토록한다. modDate는 열의 생성 및 열의 정보가 변경이 될 때마다 변경이 될 수 있도록 @LastModifiedDate 어노테이션을 추가한다.

회원정보를 저장하기 위한 entity로 com.example.sboot.member.entity 패키지에 Member.java를 다음과 같이 추가한다.

package com.example.sboot.member.entity;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;

import com.example.sboot.common.entity.BaseEntity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class Member extends BaseEntity {
	@Id
	@Column(length=30, nullable=false)
	private String email;
	@Column(length=64, nullable=false)
	private String password;
	@Column(length=30, nullable=false)
	private String name;
	@Column(length=30)
	private String nickname;
	@Column(length=15)
	private String phone;
	private String address;
	@Column(length=15)
	private String birthday;	// s/l.2000.12.12
	private boolean fromSocial;
	
	@ElementCollection(fetch = FetchType.LAZY)
    @Builder.Default
    private Set<MemberRole> roleSet = new HashSet<>();

    public void addMemberRole(MemberRole clubMemberRole){
        roleSet.add(clubMemberRole);
    }
}

security를 위해 사용자의 권한 정보를 저장하여야 하는데, 이를 위해 MemberRole enum을 다음과 같이 추가한다.

package com.example.sboot.member.entity;

public enum MemberRole {
	USER, MANAGER, ADMIN
}

권한 정보인 MemberRole은 USER, MANAGER, ADMIN과 같이 3가지 권한을 가지며, 사용자마다 여러 개의 권한을 부여할 수 있다. 이를 Member entity에서 접근할 수 있도록 Set<MemberRole>을 추가하고, 권한을 추가할 수 있는 메소드 addMemberRole()를 추가한다.

member 테이블과 member_role_set 테이블은 부모와 자식의 관계를 가지며, member 테이블의 아이디인 email을 PK/FK로 관계한다.

앱을 구동하면 다음과 같이 관련 테이블이 생성됨을 알 수 있다.

Hibernate: 
    
    create table member (
       email varchar(30) not null,
        moddate datetime(6),
        regdate datetime(6),
        address varchar(255),
        birthday varchar(15),
        from_social bit not null,
        name varchar(30) not null,
        nickname varchar(30),
        password varchar(64) not null,
        phone varchar(15),
        primary key (email)
    ) engine=InnoDB
Hibernate: 
    
    create table member_role_set (
       member_email varchar(30) not null,
        role_set integer
    ) engine=InnoDB
Hibernate: 
    
    alter table member_role_set 
       add constraint FKd46gobgko9rkxodwx1m14qit6 
       foreign key (member_email) 
       references member (email)

MySQL workbench를 사용하여 데이터베이스 테이블의 생성을 확인할 수 있다.

security를 적용하기 위하여 security 설정이 필요하다. 다음과 같이 com.example.sboot.config 폴더를 생성하고 SecurityConfig 클래스를 생성한다. 물론 WebSecurityConfigurerAdapter 클래스를 상속을 받아야 한다.

package com.example.sboot.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import lombok.extern.log4j.Log4j2;

@Configuration
@Log4j2
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.formLogin();
		http.logout();
	}
}

설정파일임을 나타내기 위하여 @Configuration 어노테이션을 추가하고, 어노테이션에 의한 시큐리티 설정을 위해 @EnabledGlobalMethodSecurity 어노테이션을 추가한다.

security에서 제공하는 로그인 페이지와 로그아웃 페이지를 적용하기 위하여 http.formLogin()과 http.logout()을 적용한다.

지금까지는 security에 의해서 default로 정의된 Username user와 발급된 비밀번호를 사용하여 인증이 이루어졌다. 그러나 회원정보를 사용하여 로그인이 이루어져야 하므로 우선 시험적으로 테스트 사용자를 생성하여야 한다. 이를 위해 member 테이블을 조작하기 위한 MemberRepository가 필요하다.

com.example.sboot.member.repository 패키지에 MemberRepository 인터페이스를 생성하며, JpaRepository<Member, String>을 상속한다. 기본적으로 JpaRepository는 모든 JPA 기능을 가지고 있으므로 어느 정도 데이터를 조작할 수 있는 메소드들을 제공하고 있다. 아래 그림의 상속관계를 살펴보면 일반적인 기능만을 가지는 CrudRepository를 가지며, PagingAndSortRepository 기능을 가지며, 그 보다 많은 기능을 가지는 것을 알 수 있다.

package com.example.sboot.member.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.sboot.member.entity.Member;

public interface MemberRepository extends JpaRepository<Member, String> {

}

테스트 코드를 사용하여 시험적 사용자를 생성하기 위하여 src/test/java 폴더에 com.example.sboot.member.repository 패키지를 생성하고, 다음과 같이 MemberRepositoryTests.java 클래스를 추가한다.

package com.example.sboot.member.repository;

import java.util.HashSet;
import java.util.stream.IntStream;

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 com.example.sboot.member.entity.Member;
import com.example.sboot.member.entity.MemberRole;

@SpringBootTest
public class MemberRepositoryTests {
	@Autowired
    private MemberRepository repository;

    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Test
    public void insertDummies() {
        //1 - 6까지는 USER만 지정
        //7 - 8까지는 USER,MANAGER
        //9 - 10까지는 USER,MANAGER,ADMIN

        IntStream.rangeClosed(1,10).forEach(i -> {
            Member member = Member.builder()
                    .email("user"+i+"@zerock.org")
            		.password(passwordEncoder.encode("1111"))
                    .name("사용자"+i)
                    .nickname("똘이" + i)
                    .fromSocial(false)
                    .roleSet(new HashSet<MemberRole>())
                    .build();
    
            //default role
            member.addMemberRole(MemberRole.USER);
            if(i > 6){
                member.addMemberRole(MemberRole.MANAGER);
            }
            if(i > 8){
                member.addMemberRole(MemberRole.ADMIN);
            }
            repository.save(member);
        });
    }
}

10명의 사용자를 추가하며, 비밀번호를 암호화하여 저장한다. 이를 위해 PasswordEncoder를 사용하며, 위에서 정의한 MemberRepository를 사용한다.

- user1@zerock.org ~ user6@zerock.org는 USER 권한을 가지고

- user7@zerock.org ~ user8@zerock.org : MANAGER 권한

- user9@zerock.org ~ user10@zerock.org : ADMIN 권한

테스트 코드를 사용하여 JUnit 테이트를 실행한다. 테스트가 성공적으로 수행이 되며, MySQL workbench를 사용하여 확인하면 member 테이블에 사용자가 생성됨을 확인할 수 있다.

또한 member_role_set 테이블에는 사용자의 권한이 추가됨을 확인할 수 있다. user10@zerock.org는 USER, MANAGER, ADMIN 권한을 가짐을 알 수 있다.

소스코드는 다음과 같다.

sboot.zip
0.16MB

Posted by 세상을 살아가는 사람
,