Notice
Recent Posts
Recent Comments
Link
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

hy6

3-06 회원 가입 (점프 투 스프링부트) 본문

점프 투 스프링 부트

3-06 회원 가입 (점프 투 스프링부트)

rantinum 2023. 11. 1. 09:29

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:/";
    }
}

  • 잘 출력 된다.