하나씩 차근차근
article thumbnail

앞에서 스프링 시큐리티 설정을 끝냈으므로 이번에는 회원가입 기능을 추가하겠습니다.

 

User 엔티티

먼저 User 엔티티를 작성하고 데이터베이스에 등록을 합니다.

package com.crud.model;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
public class User {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(unique = true)
	private String username;
	
	private String password;
	
	@Column(unique = true)
	private String email;
}

User 테이블에는 id, username, password, email 4개의 칼럼으로 구성되어 있으며,

username 과 email 은 동일한 값을 저장할 수 없도록 @Column(unique = true ) 애너테이션을 적용했습니다.

MariaDB [springboot_crud]> show tables;
+---------------------------+
| Tables_in_springboot_crud |
+---------------------------+
| answer                    |
| question                  |
| user                      |
+---------------------------+
3 rows in set (0.001 sec)

MariaDB [springboot_crud]> desc user;
+----------+--------------+------+-----+---------+----------------+
| Field    | Type         | Null | Key | Default | Extra          |
+----------+--------------+------+-----+---------+----------------+
| id       | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| email    | varchar(255) | YES  | UNI | NULL    |                |
| password | varchar(255) | YES  |     | NULL    |                |
| username | varchar(255) | YES  | UNI | NULL    |                |
+----------+--------------+------+-----+---------+----------------+
4 rows in set (0.012 sec)

데이터베이스에 접속해서 확인을 해보면 User 엔티티가 반영된것을 볼 수 있습니다.

 

UserRepository

다음으로 UserRepository 를 작성하겠습니다.

package com.crud.repository;

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

import com.crud.model.User;

public interface UserRepository extends JpaRepository<User, Long>{

}

UserRepository 는 접근할 대상이 User 엔티티며, User 엔티티 기본키의 속성 타입은 Long 이므로

JpaRepository <User, Long> 을 사용합니다.

 

UserService

다음으로 위에서 만든 UserRepository 를 사용해서 user 를 저장하는 UserService 를 생성합니다.

package com.crud.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.crud.model.User;
import com.crud.repository.UserRepository;

@Service
public class UserService {

	@Autowired
	private UserRepository userRepository;
	
	public User create(String username, String email, String password) {
		User user = new User();
		user.setUsername(username);
		user.setEmail(email);
		user.setPassword(password);
		
		userRepository.save(user);
		return user;
	}	
}

UserService 의 create 메서드는 username, email, password 를 입력받아

새로운 user 객체를 하나 생성해서 3개의 값을 저장하고

UserRepository 를 사용해서 해당 객체를 저장합니다.

이때, password 는 보안을 위해 암호화하여 저장할 수 있도록  BCryptPasswordEncoder 클래스를 사용해 암호화합니다.

BCryptPasswordEncoder 는 해싱 함수를 사용해서 비밀번호를 암호화
package com.crud.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import com.crud.model.User;
import com.crud.repository.UserRepository;

@Service
public class UserService {

	@Autowired
	private UserRepository userRepository;
	
	public User create(String username, String email, String password) {
		User user = new User();
		BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
		
		user.setUsername(username);
		user.setEmail(email);
		user.setPassword(bCryptPasswordEncoder.encode(password));
		
		userRepository.save(user);
		return user;
	}	
}

 

UserForm

다음으로 UserForm 을 사용해서 회원가입에 필요한 항목들이 제대로 입력되었는지 확인합니다.

  • 필수 입력사항 : username, email, password, passwordAgain
  • 중복된 값 : 아이디와 이메일이 중복된 값이 있는지
  • 아이디 : 25글자 미만으로 설정
  • 비밀번호 확인 : password 와 passwordAgain 이 동일한 값인지
  • 이메일 : 이메일 형식에 맞게 입력이 되어있는지
package com.crud.model;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserForm {

	@Size(max = 25)
	@NotEmpty(message = "사용자 아이디를 입력해주세요.")
	private String username;
	
	@NotEmpty(message = "비밀번호를 입력해주세요.")
	private String password;
	
	@NotEmpty(message = "비밀번호를 한번 더 입력해주세요.")
	private String passwordAgain;
	
	@NotEmpty(message = "이메일을 입력해주세요.")
	@Email
	private String email;
}

필수입력사항과 아이디, 이메일의 검증은 각각의 애너테이션을 통해 확인하고 있으며,

password 와 passwordAgain 의 값이 동일한지는 UserController 에서 값을 넘겨받아 확인하겠습니다.

 

UserController

다음으로 UserController 를 작성하겠습니다.

localhost:8080/user/signup 에 get 방식으로 접속을 하면 회원가입 폼이 화면에 나오게 되고,

post 방식으로 폼을 제출하면 컨트롤러에서 에러메세지를 보내거나 혹은 UserService 를 통해 저장합니다. 

package com.crud.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.crud.model.UserForm;
import com.crud.service.UserService;

import jakarta.validation.Valid;

@Controller
public class UserController {

	@Autowired
	private UserService userService;
	
	@GetMapping("/user/signup")
	public String signup(UserForm userForm) {
		return "signup_form";
	}
	
	@PostMapping("/user/signup")
	public String signup(@Valid UserForm userForm, BindingResult bingBindingResult) {
		
		if(bingBindingResult.hasErrors()) {
			return "signup_form";
		}
		
		if(!userForm.getPassword().equals(userForm.getPasswordAgain())) {
			bingBindingResult.rejectValue("passwordAgain", "passwordInCorrect", "비밀번호가 일치하지 않습니다.");
			return "signup_form";
		}
		
		try {
			userService.create(userForm.getUsername(), userForm.getEmail(), userForm.getPassword());
		} catch (DataIntegrityViolationException e) {
			// TODO: handle exception
			bingBindingResult.reject("signupFailed", "이미 등록된 아이디입니다.");
			return "signup_form";
		} catch(Exception e) {
			bingBindingResult.reject("signupFailed", e.getMessage());
			return "signup_form";
		}
		
		return "redirect:/";
	}
}

username 혹은 email 이 동일한 경우 DataIntegrityViolationException 를 통해 예외처리를 하였습니다.

 

회원가입 페이지

다음으로 회원가입 페이지인 signup_form 을 작성합니다.

<html layout:decorate="~{layout}">
	<div layout:Fragment="content" class="container my-3">
		<div class="my-3 border-bottom">
			<div>
				<h4>회원가입</h4>
			</div>
		</div>
		<form th:action="@{/user/signup}" th:object="${userForm}" method="post">
			<div class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}">
				<div th:each="err : ${#fields.allErrors()}" th:text="${err}" />
			</div>
			<div class="mb-3">
				<label for="username" class="form-label">ID</label>
				<input type="text" th:field="*{username}" class="form-control">
			</div>
			<div class="mb-3">
				<label for="password" class="form-label">비밀번호</label>
				<input type="password" th:field="*{password}" class="form-control">
			</div>
			<div class="mb-3">
				<label for="passwordAgain" class="form-label">비밀번호 확인</label>
				<input type="password" th:field="*{passwordAgain}" class="form-control">
			</div>
			<div class="mb-3">
				<label for="email" class="form-label">이메일</label>
				<input type="email" th:field="*{email}" class="form-control">
			</div>
			<button type="submit" class="btn btn-primary">회원가입</button>
		</form>
	</div>
</html>

회원가입 버튼을 클릭하면 post 방식으로 userForm 에 값을 저장해서 localhost:8080/user/signup 으로 보내게됩니다.

네비게이션바에서도 회원가입 페이지로 이동할 수 있도록 회원가입 메뉴를 추가합니다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
	...
</head>
<body>
	...
	            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
	                <li class="nav-item">
	                    <a class="nav-link" href="#">로그인</a>
	                </li>
	                <li class="nav-item">
	                    <a class="nav-link" href="/user/signup">회원가입</a>
	                </li>
	            </ul>
	...
</body>
</html>

 

결과

다음과 같이 회원가입 폼에 맞춰 가입을 합니다.

mysql 을 통해 확인해보면 비밀번호가 암호화되어 회원 데이터가 저장이 잘 된것을 알 수 있습니다.

MariaDB [springboot_crud]> select * from user;
+----+-------------------+--------------------------------------------------------------+----------+
| id | email             | password                                                     | username |
+----+-------------------+--------------------------------------------------------------+----------+
|  1 | tjtojan@naver.com | $2a$10$Y73FGw/XodKjfeOsFNyyTuoyV9HyvkIIQ6760YK5T/Q0OxW.0wFA6 | jeehwan  |
+----+-------------------+--------------------------------------------------------------+----------+
1 row in set (0.001 sec)
profile

하나씩 차근차근

@jeehwan_lee

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!