회원가입을 만들어보자.
1. 엔티티 생성
회원의 정보를 담을 수 있는 엔티티를 생성한다.
@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;
}
이름과 이메일은 한가지만 들어올 수 있도록 unique를 주었다.
2. Repository와 Service class 생성
기능을 위한 Repository와 Service를 생성한다.
- UserRepository
package com.mysite.sbb.user;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<SiteUser, Long> {
}
- UserService
@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;
}
}
여기서 나오는 BCryptPasswordEncoder는 보안을 위한 암호화 객체이다. 하지만 회원들이 여러번 등록된다면, BCryptPasswordEncoder 객체를 계속해서 만들어 암호화를 해 비밀번호를 저장한다면, 비밀번호를 생성할 때 사용한 BCryptPasswordEncoder 객체를 하나하나 찾아 수정해줘야 한다. 이러한 고생을 방지하기 위해 객체 하나를 주입받아 지속해서 사용할 수 있도록 하자.
객체 주입을 위해 Bean을 사용해야 하는데, 이는 Spring server가 실행될 때 component 전체를 한번 스캔 할 때 필요한 정보들을 저장해두는데, Bean이 바로 저장할 필요한 정보를 기입하는 어노테이션이다.
또한 직접 제어가 불가능한(우리가 만들지 않은) 외부 라이브러리 등을 주입할 때 사용하는데, 객체를 반환하는 코드를 작성하면 된다.
++ Bean 어노테이션은 Configuration 어노테이션 내에서만 선언 가능하다.
예시를 보자
@Configuration 어노테이션이 있는 SecurityConfig class에서 Bean을 만든다.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
단지 새로운 객체를 반환하기만 한다.
그러면 미리 만들어 둔 Service class에서 메서드 실행마다가 아닌 객체를 미리 생성하고, 그 객체를 계속 사용할 수 있도록 코드를 바꿀 수 있다.
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. Form 생성
그러면 회원가입을 할 수 있는 폼을 만들어 보자.
일단 엔티티의 컬럼들이 에러가 발생하지 않도록 확인하기 위해 UserCreateForm class를 만든다.
@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;
}
그 후 컨트롤러를 통해 url를 Mapping하도록 하고, 어떠한 기능을 실행할지 넣어준다.
@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:/";
}
}
signUp을 할 때
if (!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())) {
bindingResult.rejectValue("password2", "passwordInCorrect",
"2개의 패스워드가 일치하지 않습니다.");
return "signup_form";
}
이 부분은 패스워드와 패스워드 확인의 값이 서로 다를 때 내용을 출력시키는 것이다.
따로 어노테이션이 없기 때문에 직접 구현을 해주었다.
blindingResult.rejectValue는 총 3개의 파라미터(필드명, 오류코드, 에러메세지)를 갖는다.
다음 폼을 보여줄 Template을 만든다.
Controller에서 redirect할 html파일의 이름은 signup_form 이기 때문에 똑같이 이름을 signup_form으로 만들어 준다.
그러면 잘 출력된다!
회원가입도 잘 된다!
4. 중복된 사용자 회원가입 방지
회원가입을 할 때 중복된 아이디로 회원가입을 할 수 없다.
중복된 아이디를 적었을 때 에러페이지를 출력하지 않도록 하는 방법은 없을까?
예외처리를 이용하자.
DB에서 UNIQUE로 된 id를 중복되게 넣는다면 발생하는 에러는 DataIntegrityViolationException 이다. 이 Exception을 갖고 예외처리를 만들어보자.
try {
userService.create(userCreateForm.getUsername(),
userCreateForm.getEmail(), userCreateForm.getPassword1());
} catch(DataIntegrityViolationException e) {
e.printStackTrace();
bindingResult.reject("signupFailed", "이미 등록된 사용자입니다.");
return "signup_form";
}
DataIntegrityViolationException이 발생하면 에러메시지로 "이미 등록된 사용자입니다."를 화면에 출력시킬 수 있도록 했다.
확인해보자.
user1이라는 id를 가진 사용자가 있다. 회원가입 form에서도 user1으로 다시 회원가입을 하려 하면
에러메세지가 잘 출력이 되는 것을 확인할 수 있다.
++ Id와 Email의 중복을 각각 확인하는 방법
Id와 Email 둘 다 에러메세지는 "이미 등록된 사용자입니다." 로 출력된다. 이를 따로따로 확인할 수 있는 방법이 있을까?
service에서 username / email이 존재하는지 확인 후 존재하면 에러를 발생시키고, Controller에서 각각의 에러에 따라 bindingResult.reject를 해주면 된다.
UserRepository에서 existsByName과 existsByEmail 메서드를 만든다.
boolean existsByUsername(String username);
boolean existsByEmail(String email);
UserService에서 DataIntegrityViolationException이 발생할 경우 조건문을 사용해 Repository에서 만든 existsBy를 사용해 존재할 경우 각각 에러를 발생시킨다.
try {
userRepository.save(user);
} catch (DataIntegrityViolationException e) {
if (userRepository.existsByUsername(username)) {
throw new SignupUsernameDuplicatedException("이미 사용중인 username 입니다.");
} else {
throw new SignupEmailDuplicatedException("이미 사용중인 email 입니다.");
}
}
해당 에러를 각각 class로 만들어주어야 한다.
public class SignupUsernameDuplicatedException extends RuntimeException{
public SignupUsernameDuplicatedException(String message) {
super(message);
}
}
그리고 UserController에서 각각의 오류에 대한 catch문을 작성한다.
try {
userService.create(userCreateForm.getUsername(), userCreateForm.getEmail(), userCreateForm.getPassword1());
} catch (SignupEmailDuplicatedException e) {
bindingResult.reject("signupEmailDuplicated", e.getMessage());
return "signup_form";
} catch(Exception e) {
e.printStackTrace();
bindingResult.reject("signupFailed", e.getMessage());
return "signup_form";
}
결과화면
'Spring' 카테고리의 다른 글
[Spring]기능 - 질문 수정 및 삭제 (0) | 2022.08.22 |
---|---|
[Spring]기능 - 작성자 출력하기 (0) | 2022.08.22 |
CSRF(Cross Site Request Forgery) (0) | 2022.08.18 |
[Spring]Spring Security (0) | 2022.08.18 |
[Spring]기능 - 페이징 기능 (0) | 2022.08.17 |