Old Branch

스프링 부트(Spring Boot)와 Security, MySQL, React를 사용한 Spring Polling App (1)

woolbro 2019. 7. 17. 13:11
반응형

이번 포스팅에서는 스프링 부트(Spring Boot )와 스프링 시큐리티(Spring Security)를 활용해서 Spring Polling App을 만들어 보도록 하겠습니다!

(원본글은 https://www.callicoder.com/spring-boot-spring-security-jwt-mysql-react-app-part-1/)

 

데이터베이스로는 MySQL를 사용 할 것이고, 프론트엔드로는 React를 사용 해 보겠습니다.

 

본 포스팅의 코드는 Github 에 업로드 되어있습니다.

 


스프링 부트를 사용해 백엔드 어플리케이션 생성하기

우선 프로젝트 이름과, pom.xml을 starter를 통해 생성 해 주도록 하겠습니다.

 

 

프로젝트 구조는 위와 같습니다.

프로젝트 이름 : woolbro_polling 

프로젝트 기본 패키지 : com.woolbro

pom.xml dependency : DevTools, Lombok, Spring Data JPA, Spring Security, Spring Web Starter, Spring Web Services, MySQL

 

그리고, 아래의 항목들을 pom.xml에 추가로 넣어 준 후에, Maven Update - Build 를 하겠습니다.

<!-- For Working with Json Web Tokens (JWT) -->
	<dependency>
		<groupId>io.jsonwebtoken</groupId>
		<artifactId>jjwt</artifactId>
		<version>0.9.0</version>
	</dependency>
<!-- For Java 8 Date/Time Support -->
	<dependency>
		<groupId>com.fasterxml.jackson.datatype</groupId>
		<artifactId>jackson-datatype-jsr310</artifactId>
	</dependency>

 

application.properties 설정

src/main/resource의 applicatio.properties 에 설정을 추가 해 주 도록 하겠습니다.

server.port를 통해서 서버 포트를 바꿔 줄 수 있습니다. default 는 8080이지만, 이번 포스팅에서는 괜히 8000으로 한번 바꿔주겠습니다 ㅋㅋㅋ

 

데이터베이스는 위에서 말씀드렸듯이 MySQL을 사용 할 것이구요, 접속을 위해 id와 pw를 적어줍니다.

## Server Properties
server.port= 8000

## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url= jdbc:mysql://localhost:3306/polling_app?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username= root
spring.datasource.password= 1234

## Hibernate Properties

# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto = update

## Hibernate Logging
logging.level.org.hibernate.SQL= DEBUG

# Initialize the datasource with available DDL and DML scripts
spring.datasource.initialization-mode=always

## Jackson Properties
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS= false
spring.jackson.time-zone= UTC

 

위에 잘 보시면

spring.jpa.hibernate.ddl-auto = update

라는 옵션이 있는데,  응용 프로그램의 엔티티에 따라 데이터베이스의 테이블이 자동으로 작성 / 업데이트 되는 옵션입니다.

 

WoolbroPollingApplication 설정하기 - JAVA 8 날짜 / 시간 변환기 및 UTC 사용하기

 

본 포스팅에서는 도메인 모델에서 Java 8 Data / Time 클래스를 사용하려고 합니다. JPA 2.1 변환기를 등록하면 도메인 모델의 모든 Java 8 Date / Time 필드가 데이터베이스에 유지 될 때 자동으로 SQL 형식으로 변환됩니다. 또한 응용 프로그램의 기본 시간대를 UTC로 설정합니다.

 

WoolbroPollingApplication.java (기본으로 만들어지는 최초의 파일)을 수정 해보도록 하겠습니다.

package com.woolbro;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.annotation.PostConstruct;
import java.util.TimeZone;

@SpringBootApplication
@EntityScan(basePackageClasses = { WoolbroPollingApplication.class, Jsr310JpaConverters.class })
public class WoolbroPollingApplication {
	
	@PostConstruct
	void init() {
		TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
	}

	public static void main(String[] args) {
		SpringApplication.run(WoolbroPollingApplication.class, args);
	}

}

 


이제 설정을 모두 마쳤으니, 모델을 추가 해 보도록 하겠습니다.

추가 할 모델은 User, Role 을 추가 하고, UserRepository, RoleRpository 까지 추가 해 주도록 하겠습니다.

 

추가 할 프로젝트 파일 구조

 

User.java

 User모델에는 다음 필드가 포함되어 있습니다.

  1. id: 아이디
  2. username:사용자 이름
  3. email: 이메일
  4. password: 암호로 암호화 된 형식으로 저장됩니다.
  5. roles: role. ( Role엔티티 와 다 대 다 관계 )
package com.woolbro.model;

import org.hibernate.annotations.NaturalId;

import com.woolbro.model.audit.DateAudit;

import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "users", uniqueConstraints = { @UniqueConstraint(columnNames = { "username" }),
		@UniqueConstraint(columnNames = { "email" }) })
public class User extends DateAudit {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	@NotBlank
	@Size(max = 40)
	private String name;

	@NotBlank
	@Size(max = 15)
	private String username;

	@NaturalId
	@NotBlank
	@Size(max = 40)
	@Email
	private String email;

	@NotBlank
	@Size(max = 100)
	private String password;

	@ManyToMany(fetch = FetchType.LAZY)
	@JoinTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
	private Set<Role> roles = new HashSet<>();

	public User() {

	}

	public User(String name, String username, String email, String password) {
		this.name = name;
		this.username = username;
		this.email = email;
		this.password = password;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public Set<Role> getRoles() {
		return roles;
	}

	public void setRoles(Set<Role> roles) {
		this.roles = roles;
	}
}

 

여기서 작성한 User클래스는 DateAudit을 extends로 가지고 있는데, 이 DateAudit 클래스는 데이터의 Create,Update를 감시하고 이벤트를 감지하기 위해 작성 해 줄 클래스입니다.

 

DateAudit.java

DateAudit 모델을 정의 해 보겠습니다. createdAt 및 updatedAt 필드가 있습니다. 이러한 감시 필드가 필요한 다른 도메인 모델은 지금 작성한 DateAudit 클래스를 extends로 가지면 됩니다.

엔터티를 유지할 때 JPA의 AuditingEntityListener를 사용하여 createdAt 및 updatedAt 값을 자동으로 채 웁니다.

다음은 DateAudit.java 클래스입니다.

package com.woolbro.model.audit;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.time.Instant;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(
        value = {"createdAt", "updatedAt"},
        allowGetters = true
)
public abstract class DateAudit implements Serializable {

    @CreatedDate
    @Column(nullable = false, updatable = false)
    private Instant createdAt;

    @LastModifiedDate
    @Column(nullable = false)
    private Instant updatedAt;

    public Instant getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Instant createdAt) {
        this.createdAt = createdAt;
    }

    public Instant getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(Instant updatedAt) {
        this.updatedAt = updatedAt;
    }
}

 

AuditingConfig.java

JPA 감사를 사용하려면 @EnableJpaAuditing 주석을 주 클래스 나 다른 구성 클래스에 추가해야합니다.

AuditingConfig 구성 클래스를 만들고 @EnableJpaAuditing 주석을 추가하겠습니다.

나중에 감사 관련 설정을 추가 할 수 있게 별도의 클래스를 만들어 작성 해 주도록 하겠습니다.

모든 구성 클래스는 config라는 패키지 안에 보관할 것입니다. com.woolbro에 config 패키지를 만든 다음 config 패키지 내에 AuditingConfig 클래스를 만듭니다.

package com.woolbro.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
public class AuditingConfig {
    // 우선 추가 먼저 해주고, 이후에 작성 하도록 하겠습니다.
}

 

 

Role.java

다음은 Role관련 모델을 작성 하도록 하겠습니다.

작성한 Role 은 RoleName.java의 enum을 참조하도록 작성하겠습니다.

package com.woolbro.model;

import org.hibernate.annotations.NaturalId;
import javax.persistence.*;

@Entity
@Table(name = "roles")
public class Role {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	@Enumerated(EnumType.STRING)
	@NaturalId
	@Column(length = 60)
	private RoleName name;

	public Role() {

	}

	public Role(RoleName name) {
		this.name = name;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public RoleName getName() {
		return name;
	}

	public void setName(RoleName name) {
		this.name = name;
	}
}

 

 

RoleName.java (ENUM)

package com.woolbro.model;

public enum  RoleName {
    ROLE_USER,
    ROLE_ADMIN
}

 

DB의 role에 ROLE_USER, ROLE_ADMIN을 생성 해 주도록 하겠습니다 :)


INSERT IGNORE INTO polling_app.roles(name) VALUES('ROLE_USER')

INSERT IGNORE INTO polling_app.roles(name) VALUES('ROLE_ADMIN')

 

User, Role 데이터에 접근하기위한 Repository 작성

UserRepository.java

package com.woolbro.repository;

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

import com.woolbro.model.User;

import java.util.List;
import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);

    Optional<User> findByUsernameOrEmail(String username, String email);

    List<User> findByIdIn(List<Long> userIds);

    Optional<User> findByUsername(String username);

    Boolean existsByUsername(String username);

    Boolean existsByEmail(String email);
}

 

RoleRepository.java

package com.woolbro.repository;

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

import com.woolbro.model.Role;
import com.woolbro.model.RoleName;

import java.util.Optional;

@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
    Optional<Role> findByName(RoleName roleName);
}

 


기본적인 모델을 작성 했으니, 이제 실행을 해서 정상적으로 작동하는지 확인 해야겠죠~?

 

SpringBoot App으로 실행 시켜주시고, console창에 아무런 에러없이 아래와같이 실행이 된다면, Database 설정과 Model 작성이 잘 이루어진 것으로 볼 수 있습니다.