이전 게시글에서는 스프링 부트를 이용해서 포트폴리오를 만들기 위하여 기본 프로젝트를 생성하였고, 부트스트랩을 적용하여 뷰에 대한 템플레이트 레이아웃을 적용하였다. 또한 기본 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 세상을 살아가는 사람
,

자바 접근 제어자

자바 2017. 4. 5. 12:15
- 클래스, 멤버변수, 매소드에 대한 접근 권한 설정
- 종류 : public, protected, default, private
- public : 어디서든지 접근이 가능
- protected : 외부 클래스에서는 접근이 허용되지 않으며, 상속받은 클래스에서 접근 가능. 멤버변수와 매소드에서 지원.
- private : 상속받은 클래스에서도 접근이 허용되지 않음. 선언된 클래스내에서만 접근 가능.
- default : 접근 제어자를 사용하지 않을 경우 적용되는 접근 권한. 같은 패키지내에서는 접근이 가능하나 외부 클래스에서는 접근이 허용되지 않음.

private으로 선언하면 외부 클래스에서 접근이 허용되지 않으므로, "정보 은닉"과 같은 객체지향의 하나의 특성을 가진다. 보통 멤버는 private로 선언하여, 외부에서 직접 접근하지 못하게 은닉하고, 멤버를 변경하기 위한 매소드를 public으로 선언하여 간접적으로 값을 변경할 수 있다. 또한 매소드를 통한 접근은 다양한 부가 기능을 추가할수 있어 효과적이다.

'자바' 카테고리의 다른 글

자바 protected 접근 제어자  (0) 2017.05.18
자바 라벨 제어문  (0) 2017.05.12
utf-8로 저장된 자바 파일 에러 방지  (0) 2017.05.10
자바 super와 this  (0) 2017.04.06
자바 매소드 오버라이딩  (0) 2017.04.05
Posted by 세상을 살아가는 사람
,

홈 페이지를 만들다보면 게시판을 여러 개를 만들 필요가 있다. 또한 게시판 마다 게시글의 읽기, 쓰기, 댓글달기, 첨부파일 달기 등의 권한을 달리 설정해주어야 하는 경우가 발생한다.

여기서는 그런 경우에 고려하여야 할 사항과 구현방법에 대하여 기술한다.

1. 요구사항

- 여러 개의 게시판을 운영한다. : 게시판 생성, 게시판 정보 변경, 게시판 삭제, 게시판 목록 보기, 게시판 정보 보기 등의 기능이 필요

- 게시판에 대한 속성 설정 : 읽기, 쓰기, 댓글달기, 비밀글 여부, 첨부파일 허용여부, 첨부파일 허용용량, 게시글 수정, 게시글 삭제

- 게시판 종류 : 기본, 갤러리, 자료실, Q&A, 공지사항, 방명록

  + 기본 게시판 : 제목, 작성자, 비밀번호, 내용, 첨부 1개

  + 갤러리 : 이미지 첨부만 가능, 첨부는 thumbnail로 처리

  + 자료실 : 첨부 파일은 5개까지 가능

  + Q&A : 비밀글 표시 및 비밀번호 검사 후 내용 표시

  + 공지사항 : 글쓰기 버튼 비활성화, 관리자만 쓰기 가능

  + 방명록 : 내용과 첨부 사진 올리기만 가능, 비밀글(쓰기, 답변)

여기에 기술된 요구사항은 대략적인 것으로 개발을 위해서는 구체적인 요구사항이 세분화 되어져야 한다.

2. 데이터베이스 테이블

2.1 board table

- board_id : 게시판을 식별하는 ID (정수)

- board_name : 게시판 이름

- board_type : 게시판 종류 (기본 게시판, 갤러리, 자료실, Q&A, FAQ, 공지사항, 방명록)

- url : 게시판에 접속하기 위한 URL 주소

- secret : 비밀글 여부

- read_allow : 읽기 허용 여부 (사용자 유형에 따라 제어 : 로그인 안했을 때, 로그인 했을 때, 관리자)

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- write_allow : 쓰기 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 사용자만 허용 : 별도의 쓰기 허용자 정보 저장 필요 (추후 고려)

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- reply_allow : 댓글 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- modify_allow : 수정 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 작성자만 허용

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- delete_allow : 삭제 허용 여부

  + 작성자만 허용

  + 관리자만 허용

- download : 첨부파일 다운로드 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- upload : 첨부파일 업로드 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 사용자만 허용 (추후 고려)

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- nAttach : 첨부 가능한 파일의 수 (0~최대 5개)

- aSize : 첨부 가능한 파일의 용량 (최대 100MB까지)

  + 0, 50KB, 200KB, 1MB, 10MB, 100MB

- display_format : 본문 표시 여부

  + 1 : 제목만 표시

  + 2 : 본문(내용) 일부 표시

  + 3 : 본문(내용) 전부 표시

- board_desc : 게시판 설명

2.2 article table

- article_id : 게시글 id

- boardId : 게시판 id (하나의 테이블에 모든 게시판의 게시글을 저장하므로 게시판을 구분해 주어야 함)

- writer_name : 게시글 작성자 이름(O) -> 불필요할 수 있음(원 저자)

- email : 게시글 작성자 id (이메일 형식)

- title : 제목(O)

- author : 시와 같은 창작물을 게시할 때 원 저자를 입력(O)

- password : 비밀번호 (로그인 하지 않은 사용자가 게시글을 작성시, 추후 게시글 삭제 때 필요)

- read_count : 게시글을 읽은 횟수

- comment_count : 게시글에 대한 댓글 수

- like_count : 좋아요 수

- ref : 게시글과 댓글에 대한 정렬에 사용

- step : 게시글과 댓글에 대한 정렬에 사용

- depth : 게시글과 댓글에 대한 정렬에 사용

- createdAt : 게시글 작성 시간

- modifiedAt : 게시글 수정 시간

- content : 게시글 본문 내용

- ip : 사용자가 접속한 IP address

- secret : 비밀글 여부 (default false)

3. 추가로 고려되어야 할 사항

- 게시판 생성 후, 메뉴와의 연계방법 입니다. 동적으로 게시판을 생성하였더라도 그 게시판을 사용하려면 홈 페이지의 메뉴 체계와 동적으로 연계가 되어야 하는데 그 부분은 상당히 난이도가 있는 부분 같습니다. 전체 구조와도 연계가 되기 때문 입니다. 그 부분은 나중에 시간이 있을 때 고려하기로 합니다.


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