📌Intro
스프링 부트를 이용한 로그인, 회원가입 기능은 크게 두가지 방법으로 구현할 수 있다.
- WebMvcConfigurer를 구현하는
WebMvcConfig
- 스프링 시큐리티를 사용하는
@EnableWebSecurity
SecurityConfig
이 글에서는 후자의 방법으로 로그인, 회원가입을 구현해보고자 한다.
📌 스프링 시큐리티란?
스프링 시큐리티는 인증과 권한을 담당하는 프레임 워크이다.
복잡하지 말고 '인증'과 '권한'만 생각하자.
인증(Authenticate)은 로그인을 의미한다.
권한(Authorize)은 인증된 사용자가 어떤 것을 할 수 있는지를 의미한다.
📌 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE'
스프링 시큐리티 의존성을 설치하고, 어플리케이션을 실행하면 아래와 같은 화면이 뜬다.
스프링 시큐리티는 기본적으로 인증되지 않은 사용자는 서비스를 사용할 수 없게끔 되어 있다.
따라서 인증을 위한 로그인 화면이 나타나는 것이다.
하지만 이러한 기능을 그대로 적용하지 않는 경우에는 시큐리티의 설정을 통해 바로 잡아야 한다.
(e.g 필자가 만드려는 식당 예약관리 서비스는 로그인을 하지 않아도 식당 목록을 볼 수 있어야 함)
📌 SecurityConfig 설정
package com.example.gatchtable.config;
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.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity //1
public class SecurityConfig {
@Bean //2
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http //3
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
;
return http.build();
}
}
1. @EnableWebSecurity
모든 요청 URL이 스프링 시큐리티의 제어를 받도록 만드는 애너테이션이다.
스프링 시큐리티를 이용하는 Config에서는 꼭 써줘야 한다.
2. SecurityFilterChain
SecurityFilterChain 빈을 생성하여 스프링 시큐리티의 세부 설정을 설정할 수 있다.
3. permitAll
3번 코드는 요청을 허락한다는 의미이다.
따라서 로그인을 하지 않더라도 모든 페이지에 접근할 수 있다.
(엄청 자세한 원리는 생략)
📌 회원가입 페이지
회원가입 정보를 입력할 페이지를 만들어주었다.
<구글링 가이드>
아래 코드를 이해하려면, 타임리프 문법과 Controller에서 View로 어떻게 Model을 전달하고,
이를 타임리프에서 어떻게 받는지를 이해하고 있어야 한다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>회원가입</title>
</head>
<body>
<!-- Header -->
<h1>GatchTable</h1>
<!-- Registration Form -->
<form th:action="@{/sign-up}" th:object="${signUpInputDto}" method="post">
<!-- Show All Errors -->
<div th:fragment="formErrorsFragment" th:if="${#fields.hasAnyErrors()}">
<div th:each="err : ${#fields.allErrors()}" th:text="${err}" />
</div>
<!-- User Type Checkbox -->
<div>
<label>User Type
<input type="radio" id="userType1" th:field="*{userType}" value="CUSTOMER">
<label for="userType1">Customer</label>
<input type="radio" id="userType2" th:field="*{userType}" value="OWNER">
<label for="userType2">Owner</label>
</label>
</div>
<!-- Name Input -->
<div>
<label for="name">Name</label>
<input type="text" id="name" th:field="*{name}">
</div>
<!-- Email Input -->
<div>
<label for="email">E-mail</label>
<input type="email" id="email" th:field="*{email}">
</div>
<!-- Password Input -->
<div>
<label for="password">Password</label>
<input type="password" id="password" th:field="*{password}">
</div>
<!-- Password Confirmation Input -->
<div>
<label for="confirmPassword">Confirm Password:</label>
<input type="password" id="confirmPassword" th:field="*{confirmPassword}">
</div>
<!-- Submit Button -->
<div>
<button type="submit">SignUp</button>
</div>
</form>
</body>
</html>
📌 회원가입 컨트롤러
회원가입 컨트롤러를 아래와 같이 만들어주었다.
<구글링 가이드>
BindResult가 @Valid를 위한하는 에러를 담아서 Model에 자동으로 addAttribute 한다는 것을 알고 있어야 한다.
@Controller
@RequiredArgsConstructor
public class SignUpController {
private final OwnerService ownerService;
private final CustomerService customerService;
@GetMapping("/sign-up")
public String getSignUpPage(Model model, SignUpInputDto signUpInputDto){
model.addAttribute(signUpInputDto);
return "sign-up";
}
@PostMapping("/sign-up")
public String registerUser(@Valid SignUpInputDto signUpInputDto, BindingResult bindingResult, Model model){
if(bindingResult.hasErrors()){
return "sign-up";
}
if(!signUpInputDto.getPassword().equals(signUpInputDto.getConfirmPassword())){
bindingResult.rejectValue("confirmPassword", "passwordInCorrect",
"비밀번호가 일치하지 않습니다.");
return "sign-up";
}
if(customerService.checkOverlapEmail(signUpInputDto) || ownerService.checkOverlapEmail(signUpInputDto)){
bindingResult.rejectValue("email", "emailOverlap",
"이미 가입된 이메일입니다.");
return "sign-up";
}
UserType userType = signUpInputDto.getUserType();
if(userType == UserType.CUSTOMER){
customerService.signUpCustomer(signUpInputDto);
} else if (userType == UserType.OWNER) {
ownerService.signUpOwner(signUpInputDto);
}
return "change-mode";
}
}
'Spring Boot' 카테고리의 다른 글
[SpringBoot] @Mock vs @MockBean (0) | 2023.09.29 |
---|---|
스프링 시큐리티를 이용한 로그인 (0) | 2023.09.03 |
트랜잭션의 개념과 @Transacional 어노테이션 (0) | 2023.08.11 |
객체 지향 프로그래밍을 해야하는 이유와 SOLID 원칙 (1) | 2023.08.09 |
[SpringBoot] 컨트롤러를 테스트 하는 방법 - @WebMvcTest, @MockBean (0) | 2023.07.24 |