H2에서 Oracle로 변경한 후 회원가입 기능을 구현해보려고 한다.
/entity/Member.java 생성
package com.eunji.backboard.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Builder;
import lombok.Data;
@Data
@Entity
@Builder
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long mid;
@Column(unique = true, length = 100)
private String username;
@Column(unique = true, length = 150)
private String email;
private String password;
}
/repository/MemberRepository.java 인터페이스 생성
package com.eunji.backboard.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.eunji.backboard.entity.Member;
@Repository
public interface MemberRepository extends JpaRepository<Member, Long>{
}
/service/MemberService.java 생성
- setMember() 메서드 작성
package com.eunji.backboard.service;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import com.eunji.backboard.entity.Member;
import com.eunji.backboard.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
public Member setMember(String username, String email, String password) {
Member member = Member.builder().username(username).email(email).build();
BCryptPasswordEncoder pwdEncoder = new BCryptPasswordEncoder();
member.setPassword(pwdEncoder.encode(password)); // 암호화한 값을 DB에 저장
this.memberRepository.save(member);
return member;
}
}
💥 MemberService에 BcryptPasswordEncoder() 객체를 매번 새롭게 생성했으나, 이것보다는 Bean 등록해놓고 쓰는 게 유지보수를 위해 더 좋을 것 같아서 /security/SecurityConfig.java에 Bean을 생성해주겠다!
/security/SecurityConfig.java
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 암호화 빈으로 생성
}
변경된 MemberService.java
package com.eunji.backboard.service;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.eunji.backboard.entity.Member;
import com.eunji.backboard.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
public Member setMember(String username, String email, String password) {
Member member = Member.builder().username(username).email(email).build();
// BCryptPasswordEncoder 매번 새롭게 객체를 생성한다.
// 이것보다는 Bean 등록해놓고 쓰는 게 유지보수를 위해서 더 좋음
// BCryptPasswordEncoder pwdEncoder = new BCryptPasswordEncoder();
member.setPassword(passwordEncoder.encode(password)); // 암호화한 값을 DB에 저장
this.memberRepository.save(member);
return member;
}
}
회원가입시 빈 값이 들어가지 않도록 유효성 검사를 해야 함
/entity/Member.java 수정
package com.eunji.backboard.entity;
import java.time.LocalDateTime;
import org.springframework.data.annotation.CreatedDate;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Builder;
import lombok.Data;
@Data
@Entity
@Builder
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long mid;
@Column(unique = true, length = 100)
private String username;
@Column(unique = true, length = 150)
private String email;
private String password;
@CreatedDate
@Column(name = "regDate", updatable = false)
private LocalDateTime regDate; // 회원가입일
}
유효성 검사를 하기 위한 /validation/MemberForm.java 생성
package com.eunji.backboard.validation;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class MemberForm {
@Size(min = 4, max = 40)
@NotBlank(message = "사용자 이름은 필수입니다.")
private String username;
@Email
@NotBlank(message = "이메일은 필수입니다.")
private String email;
@NotBlank(message = "비밀번호는 필수입니다.")
private String password1;
@NotBlank(message = "비밀번호 확인은 필수입니다.")
private String password2;
}
/controller/MemberController.java
@GetMapping("/register")
public String register(MemberForm memberForm) {
return "member/register"; // templates/member/register.html
}
@PostMapping("/register")
public String register(@Valid MemberForm memberForm, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "member/register";
}
if(!memberForm.getPassword1().equals(memberForm.getPassword2())) {
bindingResult.rejectValue("password2", "passwordInCorrect", "패스워드가 일치하지 않습니다.");
return "member/register";
}
this.memberService.setMember(memberForm.getUsername(), memberForm.getEmail(), memberForm.getPassword1());
return "redirect:/";
}
- 유효성 검사를 위해 @GetMapping 메서드에서 MemberForm을 파라미터로 보낸다.
- 이후 @PostMapping 메서드에서 빈 값이 있는지 비교
- 다음으로 password1과 password2가 일치하는지 여부 확인
- 모든 if문을 통과하면 회원가입 성공
/templates/member/register.html 생성
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" layout:decorate="~{layout}">
<div layout:fragment="main-content" class="card container my-3 form-register">
<div class="my-3 border-bottom">
<h4>회원가입</h4>
</div>
<form th:action="@{/member/register}" th:object="${memberForm}" method="post" >
<!-- /templates/errors.html -->
<div th:replace="~{errors :: formErrorFragment}"></div>
<div class="mb-3">
<label for="username" class="form-lable">사용자이름</label>
<input type="text" th:field="*{username}" class="form-control">
</div>
<div class="mb-3">
<label for="email" class="form-lable">이메일</label>
<input type="text" th:field="*{email}" class="form-control">
</div>
<div class="mb-3">
<label for="password1" class="form-lable">비밀번호</label>
<input type="password" th:field="*{password1}" class="form-control">
</div>
<div class="mb-3">
<label for="password2" class="form-lable">비밀번호 확인</label>
<input type="password" th:field="*{password2}" class="form-control">
</div>
<div class="d-flex justify-content-center">
<button type="submit" class="btn btn-sm btn-primary mx-2">회원가입</button>
<a href="/" class="btn btn-sm btn-secondary">취소</a>
</div>
</form>
</div>
</html>
- @Getmapping에서 보낸 memberForm을 th:object=${ memberForm}에 넣어준다.
- 유효성 검사시 /validation/MemberForm.java에 message로 적어준 에러메시지를 띄우기 위해 th:replace="~{errors::formErrorFragment}" 추가
회원가입 기능이 되는지 확인
- DB 확인 결과 값이 잘 들어오는 것을 확인할 수 있다.
회원가입이 중복으로 되는 것을 막기 위해 유효성 검사를 하겠다.
/controller/MemberController.java @PostMapping register()에 중복회원가입 방지 추가
// 중복 사용자 처리
try{
this.memberService.setMember(memberForm.getUsername(), memberForm.getEmail(), memberForm.getPassword1());
} catch(DataIntegrityViolationException e) {
e.printStackTrace();
bindingResult.reject("registerFailed", "이미 등록된 사용자입니다.");
return "member/register";
} catch(Exception e) {
e.printStackTrace();
bindingResult.reject("registerFailed", e.getMessage());
return "member/register";
}
- db에 select 하지 않고 등록할 때 insert가 되지 않도록 함
전체 / controller/MemberController.java 코드
package com.eunji.backboard.controller;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import com.eunji.backboard.service.MemberService;
import com.eunji.backboard.validation.MemberForm;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@RequestMapping("/member")
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@GetMapping("/register")
public String register(MemberForm memberForm) {
return "member/register"; // templates/member/register.html
}
@PostMapping("/register")
public String register(@Valid MemberForm memberForm, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "member/register";
}
if(!memberForm.getPassword1().equals(memberForm.getPassword2())) {
bindingResult.rejectValue("password2", "passwordInCorrect", "패스워드가 일치하지 않습니다.");
return "member/register";
}
// 중복 사용자 처리
try{
this.memberService.setMember(memberForm.getUsername(), memberForm.getEmail(), memberForm.getPassword1());
} catch(DataIntegrityViolationException e) {
e.printStackTrace();
bindingResult.reject("registerFailed", "이미 등록된 사용자입니다.");
return "member/register";
} catch(Exception e) {
e.printStackTrace();
bindingResult.reject("registerFailed", e.getMessage());
return "member/register";
}
return "redirect:/";
}
}
결과화면
- 동일한 사용자 이름, 이메일을 입력하게 되면 위 메세지를 띄운다.
'Spring Boot > STUDY' 카테고리의 다른 글
[Spring Boot] JPA 프로젝트 - 게시글 등록 시 작성자 추가 (0) | 2024.06.21 |
---|---|
[Spring Boot] JPA 프로젝트 - 로그인, 로그아웃 기능 구현 (1) | 2024.06.21 |
[Spring Boot] JPA 프로젝트 - Spring Security (0) | 2024.06.20 |
[Spring Boot] JPA 프로젝트 - 페이징 기능 구현 (0) | 2024.06.20 |
[Spring Boot] JPA 프로젝트 - @Test를 통해 게시판 데이터 등록 (0) | 2024.06.19 |