728x90
반응형
SMALL

회원가입 기능 구성하기

게시판이 얼추 만들어졌으니 회원 가입 기능을 완성해보자

 

회원 엔티티 생성하기

sbb아래 user 패키지 생성

/user/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;

}

회원의 필요한 속성은 사용자id, 비밀번호, 이메일이 필요하다.

또 username, email 에는 @Column(unique = true) 를 지정했는데 유일한 값만 저장이 가능하다는 것을 의미한다.

 

로컬 서버를 재시작한 후 H2 콘솔에 접속해보자.

SITE_USER 테이블이 잘 생성된 것을 볼 수 있다.

 

User 리포지터리와 서비스 생성하기

/user/UserRepository.java

package com.mysite.sbb.user;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<SiteUser, Long> {

}

@@리포지터리는 인터페이스이다.

SiteUser의 기본키의 타입이 Long이므로 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;
	}
}

UserService에는 회원 데이터를 생성하는 create 메서드를 추가했다.

여기서 비밀번호는 암호화를 하기 위해 스프링 시큐리티의 BCryptPasswordEncoder 클래스를 사용했다.

 

하지만 이렇게 객체를 new로 생성하게 되면 암호화 방식이 변경될 때 BCryptPasswordEncoder 를 사용한 부분을 모두 수정해야하는 번거로움이 있기 때문에 빈을 설정해서 사용하는 것이 좋다.

 

가장 쉬운 방법은 @Configuration이 적용됐던 SecurityConfig.java 파일에 @Bean 메서드를 새로 추가하는 것이다.

 

/sbb/SecurityConfig.java

package com.mysite.sbb;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
	@Bean
	SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
		http
			.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
					.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
			.csrf((csrf) -> csrf
					.ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
			.headers((headers)-> headers
					.addHeaderWriter(new XFrameOptionsHeaderWriter(
							XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
			;
		return http.build();
	}
	
	@Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}

 

이렇게 설정해놓으면 UserService도 수정해야한다.

 

/user/UserService.java

package com.mysite.sbb.user;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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;
	}
}

new로 생성하지 않고 빈으로 등록한 PasswordEncoder 객체를 주입받아 사용했다.

 

회원가입 폼 생성하기

/user/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;

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

@Size는 데이터 길이가 3~25 사이여야 한다는 조건, @Email 은 속성 값이 이메일 형식과 일치하는지 확인.

 

회원가입 컨트롤러 생성하기

/user/UserController.java

package com.mysite.sbb.user;

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 jakarta.validation.Valid;
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:/";
	}
}

Get으로 요청 시 템플릿을 제공하고, Post로 요청 시 회원가입을 진행하도록 했다.

또 비밀번호1과 2가 동일한지 검증하는 조건문이 추가됐고, 만약 일치하지 않는다면 오류가 발생하게 했다.

오류가 없다면 userService.create로 사용자 데이터를 저장한다.

 

 

회원 가입 화면 구성하기

회원가입 템플릿 생성하기

/templates/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>

 

네비게이션 바에 회원 가입 링크 추가하기

/templates/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>
		<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
		data-bs-target="#navbarSupportedContent"
		aria-controls="navbarSupportedContent" aria-expanded="false"
		aria-label="Toggle navigation">
		<span class="navbar-toggler-icon"></span>
		</button>
		<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>

로그인 아래에 회원가입 메뉴바를 추가했다.

 

회원 가입 기능 확인하기

로컬 서버를 재시작한 후 확인해보자.

메뉴에 회원 가입이 추가되었다.

 

회원가입 화면
비밀번호 두개를 서로 다르게 입력하면 위의 오류를 확인할 수 있다.

올바르게 회원가입을 진행하면 메인 페이지로 리다이렉트 된다.

 

데이터가 잘 들어갔는지 확인해보자.

비밀번호는 암호화되어 들어간다.

 

중복 회원 가입 방지하기

회원가입 시 이미 등록한 사용자 아이디 또는 이메일 주소로 가입해보면 오류가 발생할 것이다.

 

이렇게 오류 화면을 그대로 보여주는 것을 옳지 않다.

이미 메일주소가 있다는 것을 알리는 메시지가 나오도록 수정해보자.

 

/users/UserController.java

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

수정 후 다시 시도한 화면

 

728x90
반응형
LIST

+ Recent posts