hy6
3-06 회원 가입 (점프 투 스프링부트) 본문
1. 회원 정보를 위한 엔티티
- 지금까지는 질문, 답변 엔티티만 사용해왔지만, 이제부터는 회원 정보를 위한 엔티티가 요구된다. 회원정보에 들어갈 내용은 다음과 같다.
- 회원은 질문, 답변 도메인이 아닌 user도메인을 이용 할 것이다. com.mysite.sbb.user 패키지를 생성하자.
- 그 다음, SiteUser 엔티티를 다음과 같이 작성하자.
- 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;
}
2. User 리포지터리와 서비스
- User 리포지터리를 다음과 같이 작성한다.
- UserRepository.java
package com.mysite.sbb.user;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends 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;
}
}
- User 리포지터리를 사용하여 User 데이터를 생성하는 create 메서드를 추가했다.
- 사용자의 정보를 보호하기 위해 암호화를 거치는 메서드를 사용했다. BCryptPasswordEncoder 클래스를 사용하여 암호화하여 비밀번호를 저장했다. BCrypt 해싱 함수(BCrypt hashing function)를 사용해서 비밀번호를 암호화한다.
- 하지만 이렇게 BCryptPasswordEncoder 객체를 직접 new로 생성하는 방식보다는 PasswordEncoder 빈(bean)으로 등록해서 사용하는 것이 좋다. 왜냐하면 암호화 방식을 변경하면 BCryptPasswordEncoder를 사용한 모든 프로그램을 일일이 찾아서 수정해야 하기 때문이다.
- PasswordEncoder 빈(bean)을 만드는 가장 쉬운 방법은 @Configuration이 적용된 SecurityConfig에 @Bean 메서드를 생성하는 것이다. 다음과 같이 SecurityConfig를 수정하자.
- SecurityConfig.java
(... 생략 ...)
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
(... 생략 ...)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
(... 생략 ...)
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- 이렇게 PasswordEncoder를 @Bean으로 등록하면 UserService도 다음과 같이 수정할수 있다.
- UserService.java
package com.mysite.sbb.user;
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;
}
}
3. 회원가입 폼
회원 가입을 위한 폼 클래스를 작성하자. 다음처럼 UserCreateForm을 만들자.
- 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; @Getter @Setter 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; }
4. 회원가입 컨트롤러
- 엔티티와 서비스와 폼이 작성되었으니, 이제 컨트롤러를 제작하자
- UserController.java
package com.mysite.sbb.user;
import jakarta.validation.Valid;
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 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:/";
}
}
- /user/signup URL이 GET으로 요청되면 회원 가입을 위한 템플릿을 렌더링하고 POST로 요청되면 회원가입을 진행하도록 했다.
- 그리고 회원 가입시 비밀번호1과 비밀번호2가 동일한지를 검증하는 로직을 추가했다. 만약 2개의 값이 일치하지 않을 경우에는 bindingResult.rejectValue를 사용하여 오류가 발생하게 했다.
5. 회원가입 템플릿
회원가입 폼 제작을 위해 signup_form.html 파일을 작성하자.
- 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>
6. 네비게이션바에 회원가입 링크 추가하기
- 회원가입 화면으로 이동 할 수 있는 링크를 네비게이션바에 추가한다.
- 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>
(... 생략 ...)
<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>
- 화면이 잘 구현된다. 비밀번호 값을 다르게 입력하고 확인 버튼을 누르면 오류창이 뜨는걸 볼 수 있을 것이다.
7. 중복 회원가입
- 이미 가입한 사용자의 아이디로 새로 회원가입을 시도한다면, 에러코드 500이 출력 될 것 이다.
- unique = true 설정으로 인해서 동일한 아이디나 동일한 이메일로 가입을 또 시도하려고 하면 오류 메시지가 출력된다. 미관상 좋지 않으니 중복 시 그에 맞게 출력되는 코드를 작성하도록 하자.
- UserController.java
package com.mysite.sbb.user;
(... 생략 ...)
import org.springframework.dao.DataIntegrityViolationException;
(... 생략 ...)
public class UserController {
(... 생략 ...)
@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:/";
}
}
- 잘 출력 된다.
'점프 투 스프링 부트' 카테고리의 다른 글
3-05 스프링 시큐리티 (실습 기록) (0) | 2023.10.31 |
---|---|
3-04 답변 개수 표시 (실습 기록) (2) | 2023.10.31 |
3-03 게시물에 일련번호 추가하기 (실습 기록) (0) | 2023.10.31 |
3-02 페이징 (실습 기록) (0) | 2023.10.30 |
3-01 내비게이션바 (실습 기록) (0) | 2023.10.30 |