본문 바로가기

Spring

[Spring]기능 - 회원가입

회원가입을 만들어보자. 

 

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