회원가입

suhan cho·2022년 7월 29일
0

회원 정보를 위한 엔티티

User도메인

User 엔티티

@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;
}
  • 엔티티명을 User을 안한 이유는 스프링 시큐리티에 이미 User클래스가 있기 때문
  • unique = true로 유일한 값만 저장 할 수 있을을 의미

UserRepository작성

public interface UserRepository extends JpaRepository<SiteUser, Long> {
}
  • primary키가 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;
    }
}
  • user데이터 생성하는 create 메서드 추가
  • 비밀번호는 보안을 위해 반드시 암호화 저장
  • BcryptPasswordEncoder 클래스 사용하여 암호화하여 비밀번호 저장
    (BCrypt 해싱함수(BCrypt hashing function을 사용하여 비밀번호 암호화)

BCrypt를 new에서 bean으로

  • new로 생성하는 방식보다는 bean으로 등록해서 사용하는 것이 더 좋다. 왜냐면 암호화 방식 변경하면 BcryptPasswordEncorder를 사용한 모든 프로그램을 일일이 찾아서 수정해야 하기 때문
    (PasswordEncoder는 BcryptPasswordEncorder인터페이스)
  • bean을 만드는 가장 쉬운 방법은 @Configuration이 적용된 SecurityConfig에 @Bean 메서드를 생성하는 것
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        http.authorizeRequests().antMatchers("/**").permitAll()
        .and()
                .headers()
                .addHeaderWriter(new XFrameOptionsHeaderWriter(
                        XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN));
        return http.build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

UserService 수정

@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;
    }
}

회원가입 폼

@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;
}
  • username은 필수항목이고 길이 설정
  • password1, password2는 비밀번호, 비밀번호확인 속성
  • @Email은 이메일 형식이 일치하는 여부

회원가입 컨트롤러

@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를 사용하여 오류 발생
    (bindingResult.rejectValue(필드명, 오류코드, 에러메시지)를 의미

회원가입 템플릿

<html layout:decorate="~{layout}" xmlns:layout="http://www.w3.org/1999/xhtml">
<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="password" class="form-label">비밀번호</label>
      <input type="password" th:field="*{password1}" class="form-control">
    </div>
    <div class="mb-3">
      <label for="password" 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>
  • 사용자 ID, 비밀번호, 비밀번호확인, 이메일 input엘리머트 추가
    <회원가입> 버튼을 누르면 폼 데이터가 post방식으로 /user/signup/ URL로 전송

내비게이션 바에 회원가입 링크 추가

<li class="nav-item">
                    <a class="nav-link" th:href="@{/user/signup}">회원가입</a>
</li>

navbar 템플릿에 회원가입 링크 추가

  • 동일한 id혹은 이메일을 사용하면 위와 같은 에러가 나온다
  • unique = true로 설정해놔서 그렇다

UserController 수정

@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";
        }

        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";
        }
        userService.create(userCreateForm.getUsername(), userCreateForm.getEmail(), userCreateForm.getPassword1());

        return "redirect:/";
    }
}

출처: https://wikidocs.net/162141

profile
안녕하세요

0개의 댓글