Spring/실습 정리

Spring Boot 24강 - 회원가입 기능 구현 (엔티티, 서비스, 컨트롤러, 폼 검증)

코딩하는냥이 2025. 7. 9. 16:57
반응형

스프링 부트와 JPA, Validation, Thymeleaf를 활용해
회원 정보를 안전하게 저장하고
회원가입 폼의 유효성 검증 및
비밀번호 암호화(BCrypt)까지 실제 서비스에 필요한
회원가입 기능의 전체 과정을 구현합니다.


📌 예제 코드 구성

1) 회원 엔티티(SiteUser.java)

package com.mysite.sbb.user;

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 SiteUser {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(unique = true)
    private String username;

    private String password;

    @Column(unique = true)
    private String email;
}
  • @Entity: JPA가 관리하는 회원 테이블
  • @Column(unique = true): 아이디/이메일 중복 방지

2) 리포지토리(UserRepository.java)

package com.mysite.sbb.user;

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

public interface UserRepository extends JpaRepository<SiteUser, Integer> {

}
  • 회원 데이터 접근용 JPA 인터페이스

3) 서비스(UserService.java)

package com.mysite.sbb.user;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public SiteUser create(String username, String email, String password) {
        SiteUser user = new SiteUser();
        user.setUsername(username);
        user.setEmail(email);
        user.setPassword(passwordEncoder.encode(password)); // 비밀번호 암호화
        userRepository.save(user);
        return user;
    }
}
  • 비밀번호를 BCrypt로 암호화 후 DB에 저장

4) 컨트롤러(UserController.java)

package com.mysite.sbb.user;

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 org.springframework.web.bind.annotation.RequestMapping;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@RequestMapping("/user")
@Controller
public class UserController {
    private final UserService userService;

    @GetMapping("/signup")
    public String signup(UserForm userForm) {
        return "signup_form";
    }

    @PostMapping("/signup")
    public String signup(@Valid UserForm userForm, BindingResult bindingResult) {
        if (bindingResult.hasErrors())
            return "signup_form";
        if (!userForm.getPassword().equals(userForm.getPasswordCheck())) {
            bindingResult.rejectValue("passwordCheck", "passwordincorrect", "비밀번호가 서로 다릅니다.");
            return "signup_form";
        }
        this.userService.create(userForm.getUsername(), userForm.getEmail(), userForm.getPassword());
        return "redirect:/";
    }
}
  • GET: 회원가입 폼 화면
  • POST:
    • 폼 검증 후 오류 있으면 폼 재출력
    • 비밀번호/확인 일치 여부도 추가로 검증
    • 모든 값이 올바르면 회원 생성

5) 폼 데이터 클래스(UserForm.java)

package com.mysite.sbb.user;

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(min = 3, max = 15)
    @NotEmpty(message = "아이디가 비어있습니다.")
    private String username;

    @NotEmpty(message = "비밀번호가 비어있습니다.")
    private String password;
    private String passwordCheck;

    @Email
    @NotEmpty(message = "이메일이 비어있습니다.")
    private String email;
}
  • @NotEmpty, @Email, @Size 등으로
    아이디, 비밀번호, 이메일 유효성 검사

6) 회원가입 폼(signup_form.html)

<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3 mx-3">
    <!-- 제목 -->
    <h2 class="border-bottom">회원가입</h2>

    <form th:action="@{/user/signup}" method="post" th:object="${userForm}">
        <div th:replace="~{form_errors :: formErrorsFragment}"></div>
        <!-- 아이디 -->
        <div class="mb-3 row">
            <label for="username" class="col-sm-2 col-form-label">아이디</label>
            <div class="col-sm-10">
                <input type="text" class="form-control" th:field="*{username}">
            </div>
        </div>
        <!-- 비밀번호 -->
        <div class="mb-3 row">
            <label for="password" class="col-sm-2 col-form-label">비밀번호</label>
            <div class="col-sm-10">
                <input type="password" class="form-control" th:field="*{password}">
            </div>
        </div>
        <!-- 비밀번호 확인 -->
        <div class="mb-3 row">
            <label for="passwordCheck" class="col-sm-2 col-form-label">비밀번호 확인</label>
            <div class="col-sm-10">
                <input type="password" class="form-control" th:field="*{passwordCheck}">
            </div>
        </div>
        <!-- 이메일 -->
        <div class="mb-3 row">
            <label for="email" class="col-sm-2 col-form-label">이메일</label>
            <div class="col-sm-10">
                <input type="email" class="form-control" th:field="*{email}">
            </div>
        </div>
        <input type="submit" class="btn btn-primary" value="회원 가입">
    </form>
</div>
</html>
  • 에러 메시지는 fragment로 공통 처리
  • 각 필드는 th:field로 UserForm에 바인딩

💡포인트 정리

  • 비밀번호는 반드시 암호화(BCrypt 등)해서 저장
  • @Valid/@NotEmpty/@Email 등으로 서버측 폼 검증
  • 비밀번호 확인은 bindingResult.rejectValue()로 직접 검증
  • JPA의 @Column(unique = true)로 아이디/이메일 중복방지(실제 서비스에서는 중복 검사 로직도 추가 필요)
  • 입력 오류는 fragment로 공통 처리

📌정리하자면, 엔티티-서비스-컨트롤러-폼-뷰를 모두 아우르는
완성도 높은 회원가입 기능을 구현했습니다.
이제 누구나 안전하게 회원가입할 수 있습니다!