BCryptPasswordEncoder
를 Bean 으로 등록해 Container 에 저장BCryptPasswordEncoder
는 PasswordEncoder
의 구현체이다.@Configuration
@EnableWebSecurity
public class SecurityConfig {
//-- Spring Security 모든 페이지에 대해 접근 허용 --//
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests().requestMatchers(
new AntPathRequestMatcher("/**")
).permitAll();
return http.build();
}
//-- Spring Security password 암호화 설정 빈 등록 --//
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
PasswordEncoder
를 DI 해 클라이언트가 입력한 Password 를 암호화 한다.@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository repository;
//-- password encoder DI --//
private final PasswordEncoder passwordEncoder;
public SiteUser create(String username, String email, String password) {
SiteUser user = new SiteUser();
user.setUsername(username);
user.setEmail(email);
// password 를 암호화해서 자장하는 작업
user.setPassword(passwordEncoder.encode(password));
repository.save(user);
return user;
}
}
단순 회원가입 정보를 전달할 때 사용할 DTO 를 생성한다.
@Email
도 중복이 불가능한 속성을 갖고있음@Data
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 // 입력한 양식이 email 이 맞는지 확인하는 어노테이션
private String email;
}
bindingResult.rejectValue
hasErrors()
ture
를 반환한다.@Controller
@RequestMapping("/user")
@RequiredArgsConstructor
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
) {
// userCreateForm 의 양식과 일치하는지 검증하는 작업
// 일차하지 않을경우 userCreateForm 에 정의한 메시지가 modele 에 저장된다.
if (bindingResult.hasErrors()) return "signup_form";
// 페스워드 1과 2가 동일한지 확인하는 작업
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";
}
// try 가 성공할 경우 메인 페이지로 리다이렉트
return "redirect:/";
}
<div th:fragment="formErrorsFragment" class="alert alert-danger"
role="alert" th:if="${#fields.hasAnyErrors()}">
<div th:each="err : ${#fields.allErrors()}" th:text="${err}" />
</div>
<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 시작-->
<form th:action="@{/user/signup}" th:object="${userCreateForm}" method="post">
<!-- form_errors 를 호출해 양식에 맞게 작성되었는지 검증-->
<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>
<!-- 가입 form 종료-->
</div>
</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" th:href="@{/user/login}">로그인</a>
</li>
<!-- 로그인 페이지로 이동 종료-->
<!-- 회원가입 페이지로 이동 시작-->
<li class="nav-item">
<a class="nav-link" th:href="@{/user/signup}">회원가입</a>
</li>
<!-- 회원가입 페이지로 이동 종료-->
</ul>
</div>
</div>
</nav>