본문 바로가기
혼자 공부하는 것들/Spring

@validation 이용해서 값 검증하기 + 예외처리까지!🔍

by applepick 2022. 1. 30.
반응형

로그인 검증 부분을 어떻게 설계할까 고민을 많이 했습니다. 하드코딩과 @validation 두 가지를 고려했습니다.

 

1. 하드코딩

출처: nhn 기술 블로그

하드코딩으로 만든다면 각각의 계층에서 연속적으로 검증을 해줘야 하는 번거로움이 있습니다. 또한 검증 로직이 변경될 경우 각 계층을 같이 봐야하는 어려움이 있습니다. 

 

2.Bean Validation

출처: nhn 기술 블로그

java는 Bean Validation라는 유효성검사 프레임워크를 지원하고 있습니다. 각 계층에서 검증하는 것이 아닌 빈 어노테이션을 통해 공통적으로 필요한 부분만 검증할 수 있게 되었습니다.


시작

gradle 설정

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-validation'
}

의존성을 추가해줍니다.

Dependencies를 확인해보면 잘 주입된 것을 확인할 수 있습니다. 

 

로그인 폼을 사용할 loginDto입니다. 

import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Getter
@Setter
public class LoginDto {
    @NotBlank(message = "아이디를 입력해주세요")
    @NotNull(message = "아이디를 입력해주세요")
    private  String id;
    @NotBlank(message = "비밀번호를 입력해주세요")
    @NotNull(message = "비밀번호를 입력해주세요")
    private  String password;
}

빈 값일 경우와  Null이 아닐 경우 두 가지 validation을 추가해줍니다. 여러 가지 검증 어노테이션이 있습니다.  더 자세한 것들은  아래 공식문서를 확인해 보시면 됩니다.

https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/#section-builtin-constraints

 

Hibernate Validator 6.2.1.Final - Jakarta Bean Validation Reference Implementation: Reference Guide

Validating data is a common task that occurs throughout all application layers, from the presentation to the persistence layer. Often the same validation logic is implemented in each layer which is time consuming and error-prone. To avoid duplication of th

docs.jboss.org

Controller를 확인해봅시다. 

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;

@Controller
@RequiredArgsConstructor
@Slf4j
public class LoginController {

    private final LoginService loginService;

    @GetMapping("/login")
    public String loginForm(Model model, HttpSession session) {
        model.addAttribute("loginDto", new LoginDto());
        if (session != null) {
            return "login/main";
        }
        return "index";
    }

    @PostMapping("/login")
    public String loginCheck(@ModelAttribute("loginDto") @Valid LoginDto loginDto, BindingResult bindingResult, HttpServletRequest request){

        HttpSession session;

        if (bindingResult.hasErrors()) {
            return "login/main";
        }

        AdminDto adminDto  = loginService.adminFindByLoginId(loginDto);
        if (adminDto == null) {
            return "login/main";
        }
        .
        .
        .
        session = request.getSession();
        session.setAttribute("user" , employee);
        session.setAttribute("id", adminDto.getId());
        return "redirect:/index";
    }

    @GetMapping("/logout")
    public String logout(Model model, HttpServletRequest request) {

        model.addAttribute("loginDto", new LoginDto());
        HttpSession session = request.getSession(false);
        if (session != null) {
            session.invalidate();   // 세션 날림
        }

        return "login/main";
    }
}

Get :/login

로그인 페이지로 넘길 때 model에 loginDto를 같이 넘겨줍니다. 그래야 타임리프에서 form을 정상적으로 인식할 수 있습니다.

 

 

POST:/login

가장 중요한 로직입니다. 천천히 살펴보자면,

@PostMapping("/login")
public String loginCheck(@ModelAttribute("loginDto") @Valid LoginDto loginDto, BindingResult bindingResult, 
HttpServletRequest request){

    HttpSession session;

    if (bindingResult.hasErrors()) {
        return "login/main";
    }
    ...
 }

@ModelAttribute를 통해 form에서 loginDto객체를 가져옵니다. 이때 중요한 점은 @Valid를 붙여주고 BindingResult bindingResult을 검증할 해당 객체 뒤에 넣어줍니다. hasErrors()가 있을 경우 다시 로그인폼을 반환해줍니다. 

 

BindingResult이 뭐지?

BindingResult은 객체 바인딩을 반환해주는 메서드입니다.  객체를 까 보면

Errors를 extends 하고 있어 Errors에 있는 hasErrors()가  바인딩 객체 에러 처리를 해줍니다. 

 

마지막으로 view(Thymeleaf)를 살펴보겠습니다.

...
<form action="/login" th:object="${loginDto}" method="post" class="loginForm">
    <div class="idForm">
        <input type="text" th:field="*{id}" class="id" placeholder="ID"/>
        <div th:each="msg : ${#fields.errors('id')}">
            <p th:text="${msg}"></p>
        </div>
        </div>
    </div>
    <div class="passForm">
        <input type="password" th:field="*{password}" class="pw" placeholder="PW"/>
        <div th:each="msg : ${#fields.errors('password')}">
            <p th:text="${msg}"></p>
    </div>
    </div>
...

controller에서 model에 loginDto를 넘겨줬습니다. 롬복을 사용하여  getter와 setter를 사용합니다. 각 필드에 입력값을 지정해줍니다.

에러 처리를 위해 메시지를 작성해줍니다.

이런 식으로 입력 값이 없을 경우 Dto에서 지정한 메시지를 출력하게 됩니다. 

 

잘못된 부분이 있으면 언제든지 피드백해주세요~!

감사합니다.  

반응형

댓글