출처 : 점프 투 스프링부트
속성 | 설명 |
---|---|
username | 사용자 이름 |
password | 비밀번호 |
이메일 |
스프링 시큐리티에 이미 User 클래스가 있기 때문에 SiteUser로 회원 엔티티를 만든다.
package com.mysite.sbb.user;
import jakarta.persistence.*;
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;
}
@Column(unique = true)
: 유일한 값만 저장할 수 있다. 중복되게 저장할 수 없다.
@Repository
public interface UserRepository extends JpaRepository<SiteUser, Long> {}
SiteUser의 pk 타입이 Long이므로 <SiteUser,Long>을 사용한다.
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;
}
}
User 데이터를 생성하는 create 메서드를 추가한다.
이 때 사용자의 비밀번호는 보안을 위해 암호화하여 저장해야 한다.
암호화를 위해 시큐리티의 BCryptPasswordEncoder 클래스를 사용한다.
BCryptPasswordEncoder?
BCrypt 해싱 함수를 사용해서 비밀번호를 암호화한다.
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
PasswordEncoder를 직접 new로 생성하지 않고 bean으로 등록해서 사용하기 위해 @Configuration이 적용된 SecurityConfig에 @Bean 메서드를 생성한다.
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 UserCreateForm {
@Size(min = 3, max = 25)
@NotEmpty(message = "사용자 이름(ID)는 필수항목입니다.")
private String username;
@NotEmpty(message = "이메일은 필수항목입니다.")
@Email
private String email;
@NotEmpty(message = "비밀번호는 필수항목입니다.")
private String password1;
@NotEmpty(message = "비밀번호 확인 필수항목입니다.")
private String password2;
}
@Email
: 해당 속성의 값이 이메일 형식과 일치하는지 검증
@Controller
@RequiredArgsConstructor
@RequestMapping("/user")
public class UserController {
private final UserService userService;
@GetMapping("/signup")
public String signup(UserCreateForm userCreateForm) {
return "signup_form";
}
@PostMapping("/signup")
public String signup(@Valid UserCreateForm userCreateForm, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "signup_form";
}
if(!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())) {
bindingResult.rejectValue("password2", "passwordInCorrect", "2개의 패스워드가 일치하지 않습니다.");
return "signup_form";
}
try {
userService.create(userCreateForm.getUsername(), userCreateForm.getEmail(), userCreateForm.getPassword1());
} catch(DataIntegrityViolationException e) {
e.printStackTrace();
bindingResult.reject("signupFailed", "이미 등록된 사용자입니다.");
return "signup_form";
} catch(Exception e) {
e.printStackTrace();
bindingResult.reject("signupFailed", e.getMessage());
return "signup_form";
}
return "redirect:/";
}
}
비밀번호와 비밀번호 확인이 동일한지 검증한다.
만약 일치하지 않으면 bindingResult.rejectValue
로 오류가 발생하게 한다.
사용자ID 또는 이메일이 동일한 사용자가 회원가입을 시도할 경우 DataIntegrityViolationException
이 발생하므로 "이미 등록된 사용자입니다."라는 오류를 화면에 표시한다.
<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="${userCreateForm}" method="post">
<div th:replace="~{form_errors :: formErrorsFragment}"></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="password1" class="form-label">비밀번호</label>
<input type="password" th:field="*{password1}" class="form-control">
</div>
<div class="mb-3">
<label for="password2" class="form-label">비밀번호 확인</label>
<input type="password" th:field="*{password2}" 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 방식으로 /user/signup URL로 전송된다.
데이터베이스에 회원 정보가 저장된 것을 확인할 수 있다.