회원가입 기능 구성하기
게시판이 얼추 만들어졌으니 회원 가입 기능을 완성해보자
회원 엔티티 생성하기
/user/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;
}
회원의 필요한 속성은 사용자id, 비밀번호, 이메일이 필요하다.
또 username, email 에는 @Column(unique = true) 를 지정했는데 유일한 값만 저장이 가능하다는 것을 의미한다.
로컬 서버를 재시작한 후 H2 콘솔에 접속해보자.
User 리포지터리와 서비스 생성하기
/user/UserRepository.java
package com.mysite.sbb.user;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<SiteUser, Long> {
}
@@리포지터리는 인터페이스이다.
SiteUser의 기본키의 타입이 Long이므로 JpaRepository<SiteUser, Long>로 설정해주었다.
/user/UserService.java
package com.mysite.sbb.user;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
public SiteUser create(String username, String email, String password) {
SiteUser user = new SiteUser();
user.setUsername(username);
user.setEmail(email);
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
user.setPassword(passwordEncoder.encode(password));
this.userRepository.save(user);
return user;
}
}
UserService에는 회원 데이터를 생성하는 create 메서드를 추가했다.
여기서 비밀번호는 암호화를 하기 위해 스프링 시큐리티의 BCryptPasswordEncoder 클래스를 사용했다.
하지만 이렇게 객체를 new로 생성하게 되면 암호화 방식이 변경될 때 BCryptPasswordEncoder 를 사용한 부분을 모두 수정해야하는 번거로움이 있기 때문에 빈을 설정해서 사용하는 것이 좋다.
가장 쉬운 방법은 @Configuration이 적용됐던 SecurityConfig.java 파일에 @Bean 메서드를 새로 추가하는 것이다.
/sbb/SecurityConfig.java
package com.mysite.sbb;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
.csrf((csrf) -> csrf
.ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
.headers((headers)-> headers
.addHeaderWriter(new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
;
return http.build();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
이렇게 설정해놓으면 UserService도 수정해야한다.
/user/UserService.java
package com.mysite.sbb.user;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
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));
this.userRepository.save(user);
return user;
}
}
new로 생성하지 않고 빈으로 등록한 PasswordEncoder 객체를 주입받아 사용했다.
회원가입 폼 생성하기
/user/UserCreateForm.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;
@Setter
@Getter
public class UserCreateForm {
@Size(min = 3, max = 25)
@NotEmpty(message = "사용자 ID는 필수 항목입니다.")
private String username;
@NotEmpty(message = "비밀번호는 필수 항목입니다.")
private String password1;
@NotEmpty(message = "비밀번호 확인은 필수 항목입니다.")
private String password2;
@NotEmpty(message = "이메일은 필수 항목입니다.")
@Email
private String email;
}
@Size는 데이터 길이가 3~25 사이여야 한다는 조건, @Email 은 속성 값이 이메일 형식과 일치하는지 확인.
회원가입 컨트롤러 생성하기
/user/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
@Controller
@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";
}
userService.create(userCreateForm.getUsername(), userCreateForm.getEmail(), userCreateForm.getPassword1());
return "redirect:/";
}
}
Get으로 요청 시 템플릿을 제공하고, Post로 요청 시 회원가입을 진행하도록 했다.
또 비밀번호1과 2가 동일한지 검증하는 조건문이 추가됐고, 만약 일치하지 않는다면 오류가 발생하게 했다.
오류가 없다면 userService.create로 사용자 데이터를 저장한다.
회원 가입 화면 구성하기
회원가입 템플릿 생성하기
/templates/signup_form.html
<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>
네비게이션 바에 회원 가입 링크 추가하기
/templates/navbar.html
<nav th:fragment="navbarFragment" class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<div class="container-fluid">
<a class="navbar-brand" href="/">SBB</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<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" th:href="@{/user/signup}">회원 가입</a>
</li>
</ul>
</div>
</div>
</nav>
로그인 아래에 회원가입 메뉴바를 추가했다.
회원 가입 기능 확인하기
로컬 서버를 재시작한 후 확인해보자.
올바르게 회원가입을 진행하면 메인 페이지로 리다이렉트 된다.
데이터가 잘 들어갔는지 확인해보자.
중복 회원 가입 방지하기
회원가입 시 이미 등록한 사용자 아이디 또는 이메일 주소로 가입해보면 오류가 발생할 것이다.
이렇게 오류 화면을 그대로 보여주는 것을 옳지 않다.
이미 메일주소가 있다는 것을 알리는 메시지가 나오도록 수정해보자.
/users/UserController.java
@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:/";
}
'IT > Spring Boot' 카테고리의 다른 글
[Spring Boot] 28. 게시판 작성자 기능 구현 (0) | 2024.05.10 |
---|---|
[Spring Boot] 26. 로그인과 로그아웃 기능 구현하기(스프링 시큐리티) (0) | 2024.05.08 |
[Spring Boot] 24. 스프링 시큐리티(Spring Security) (0) | 2024.05.02 |
[Spring Boot] 23. 질문의 답변 개수 표시하기 (0) | 2024.05.02 |
[Spring Boot] 22. 페이징 기능 (3)게시물에 번호 지정하기 (0) | 2024.05.02 |