ValidationBindingResultValidation 분리Bean Validator@Valid vs @Validated쿠키와 세션쿠키(보안 이슈가 있음)세션(서버 측 부하가 생김)필터와 인터셉터서블릿 필터스프링 인터셉터
Validation
- 컨트롤러의 중요한 역할중 하나는 HTTP 요청이 정상인지 검증하는 것이다.
- 클라이언트, 서버 사이드에서 적절히 검증을 섞어 사용하되, 최종적으로 서버 사이드에서의 검증 작업은 필수적이다.
BindingResult
- 스프링에서 제공하는 Validation 처리 객체이다.
- 다음과 같이
BindingResult.addError()
로 에러 객체를 담은 후 처리한다.
@PostMapping("/add")
public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
...
if (!StringUtils.hasText(item.getItemName())) {
bindingResult.addError(new FieldError("item", "itemName", "상품 이름은 필수입
니다."));
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.addError(new ObjectError("item", "가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재 값 = " + resultPrice));
}
}
if (bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "validation/v2/addForm";
}
}
@ModelAttribute, @RequestBody, @RequestParam
@ModelAttribute
- 클라이언트로부터 일반 HTTP 요청 파라미터나 multipart/form-data 형태의 파라미터를 받아 객체로 사용하고 싶을 때 이용
@BindingResult
는@ModelAttribute
이후에 위치시켜야 한다. (바인딩을 먼저 하고 검증을 하기 때문)@RequestBody
- HTTP Body를 자바 객체로 변환한다. JSON 데이터를 처리할 때 사용한다.
@RequestParam
- URL 파라미터나 HTML form-data를 받아온다.
@BindingResult
가 있으면,@ModelAttribute
에 바인딩 오류가 발생해도 컨트롤러가 호출된다.BindingResult
이 없으면 400에러 발생BindingResult
이 있으면 Error 정보를BindingResult
에 담아 컨트롤러가 정상 호출됨
- 하지만
@RequestBody
는 바인딩 오류가 발생하면 바로 예외를 발생시킨다.
Validation 분리
- 스프링에서 제공하는 Validator 인터페이스
public interface Validator {
boolean supports(Class<?> clazz); // 해당 검증기를 지원하는 여부 확인
void validate(Object target, Errors errors); // 검증 대상 객체와 BindingResult
}
- Validator 호출 예시 코드
@Component // Validator을 스프링 빈으로 주입받아 처리
public class ItemValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Item.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
// ... Validator 로직 ...
}
}
// 호출부
@PostMapping("/add")
public String addItemV5(@ModelAttribute Item item,
BindingResult bindingResult, RedirectAttributes redirectAttributes) {
itemValidator.validate(item, bindingResult); // Validate
if (bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "validation/v2/addForm";
}
// 성공 로직 ..
}
Bean Validator
- 검증 기능을 매번 코드로 작성하는것은 번거롭다. 스프링은 Bean Validation을 통해 어노테이션으로 간단히 검증을 진행할 수 있도록 지원한다.
- 예시
@NotBlank
: 빈값 + 공백만 있는 경우를 허용하지 않는다.@NotNull
: null 을 허용하지 않는다.@Range(min = 1000, max = 1000000)
: 범위 안의 값이어야 한다.@Max(9999)
- 스프링 부트는 Bean Validator를 자동으로 글로벌 Validator로 등록한다.
- @ModelAttribute의 타입 변환을 시도하고, 성공하면 Validator을 적용한다.
@Valid
어노테이션을 통해 검증
@PostMapping("/user/add")
public ResponseEntity<Void> addUser(@RequestBody @Valid AddUserRequest addUserRequest) {
...
}
- message 속성을 사용하여 에러 메세지를 변경시킬 수 있다.
@NotBlank(message = "공백은 입력할 수 없습니다.")
@Valid
vs @Validated
@Valid
- Bean Validator을 통해 컨트롤러단에서 검증 진행
- MethodArgumentNotValidException 예외를 발생시킨다.
@Validated
- 컨트롤러단이 아닌 다른 곳에서 파라미터를 검증해야 하는 경우 사용한다.
- AOP 기반으로 메서드 요청을 가로채 유효성 검증을 진행한다.
- ConstraintViolationException 예외를 발생시킨다.
@Validated
는 빈으로 등록된 클래스에 붙여 사용한다. 그 안의 유효성을 검증할 메서드 파라미터에@Valid
를 붙여 검증을 진행한다.
@Service
@Validated
public class UserService {
public void createUser(@Valid UserDto userDto) {
// ...
}
- 하지만 보통 데이터를 다룰 때 전용 클래스를 생성하여 처리하기 때문에, Group은 현업에서 많이 사용되지 않는다.
// 1. 그룹용 인터페이스 생성
public interface SaveCheck {
}
// 2. 그룹 적용
@Data
public class Item {
@NotNull(groups = UpdateCheck.class) //수정시에만 적용
private Long id;
@NotBlank(groups = {SaveCheck.class, UpdateCheck.class})
private String itemName;
@NotNull(groups = {SaveCheck.class, UpdateCheck.class})
@Range(min = 1000, max = 1000000, groups = {SaveCheck.class, UpdateCheck.class})
private Integer price;
@NotNull(groups = {SaveCheck.class, UpdateCheck.class})
@Max(value = 9999, groups = SaveCheck.class) //등록시에만 적용
private Integer quantity;
}
// 3. 로직에 사용
@PostMapping("/add")
public String addItemV2(@Validated(SaveCheck.class) @ModelAttribute Item item,
BindingResult bindingResult, RedirectAttributes redirectAttributes) {
//...
}
- 참고
- 스프링 부터 3.2부터
@Constraint
어노테이션들이@Valid, @Validated
없이 기본적으로 유효성 검증을 지원한다고 한다.
쿠키와 세션
쿠키(보안 이슈가 있음)

- 쿠키 생성
Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
response.addCookie(idCookie);
- 쿠키 조회(
@CookieValue
)
@GetMapping("/")
public String homeLogin(
@CookieValue(name = "memberId", required = false) Long memberId, Model model) {
// memberId을 이용하여 로그인 로직 ..
}
- 쿠키 삭제(로그아웃)
Cookie cookie = new Cookie(cookieName, null);
cookie.setMaxAge(0);
response.addCookie(cookie);
세션(서버 측 부하가 생김)


- 서버 세션 저장소에 UUID 형태로 세션 ID을 저장한 후, 클라이언트에게 세션ID만 쿠키에 담아 전달한다.
- 따라서, 세션 방식도 결국 쿠키로 연결되어야 한다.
- 하지만 쿠키엔 세션ID만 들어가기 때문에 유저의 정보는 보호된다.
- 클라이언트는 요청마다 쿠키에 세션ID를 담고, 서버는 세션 저장소를 통해 해당 유저의 정보를 확인한다.
- HttpSession
- 서블릿의 HttpSession를 사용하여 세션을 생성할 수 있다.
//세션이 있으면 있는 세션 반환, 없으면 신규 세션 생성
HttpSession session = request.getSession();
//세션에 로그인 회원 정보 보관
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
@SessionAttribute
- 세션을 편하게 사용할 수 있도록 스프링에서는 @SessionAttribute를 지원한다.
@SessionAttribute(name = "loginMember", required = false) Member loginMember
필터와 인터셉터
필터와 인터셉터를 다루기 위해, HTTP 헤더나 URL 정보등이 필요할 수 있다. 이런 경우 HttpServletRequest를 사용하여 Http Request 가져올 수 있다.
- HttpServletRequest
- http request 정보를 서블릿에게 전달하기 위한 목적
- 헤더정보, 파라미터, 쿠키, URI, URL 등의 정보를 읽어드리는 메서드를 가지고 있음.
- Body의 stream을 읽어 들이는 메서드를 가지고 있음.
- HttpServletResponse
- WAS는 어떤 클라이언트가 요청을 보냈는지 알고 있고, 해당 클라이언트에게 응답을 보내기 위한 객체
- Content type, 응답 코드, 응답 메시지 등을 전송
서블릿 필터
- 필터의 흐름
HTTP 요청 → WAS → 필터 → 서블릿 → 컨트롤러
- 필터 체인(여러개의 필터
HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러
- 필터 인터페이스
- 필터 인터페이스는 싱글톤 객체로 생성되고 관리된다.
- 하단 인터페이스를 implements 하고, 메서드들을 @Override하여 구현해 사용한다.
public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException{}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public default void destroy() {}
}
init()
- 필터 초기화 작업
doFilter()
- 필터가 걸릴 때마다 doFilter 메서드가 호출됨
- doFilter안에
chain.doFilter(request, response);
가 있으면, 필터 체인으로 다음 필터로 이동한다.
destroy()
- 서블릿 컨테이너가 종료될 때 호출됨
- 필터 등록
- 스프링 부트에서는 FilterRegistrationBean를 사용하여 필터 등록이 가능하다.
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean
= new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter()); // 등록할 필터 지정
filterRegistrationBean.setOrder(1); // 필터 체인의 순서 지정
filterRegistrationBean.addUrlPatterns("/*"); // 필터를 적용할 URL 패턴
return filterRegistrationBean;
}
}
스프링 인터셉터

- 인터셉터의 흐름
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러
- 인터셉터는 스프링 MVC가 제공하는 기능이다.
- 인터셉터 체인
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터1 -> 인터셉터2 -> 컨트롤러
- 인터셉터 인터페이스
- preHandle
- 컨트롤러 호출 전
- postHandle
- 컨트롤러 호출 후
- afterCompletion
- 컨트롤러 요청 완료 이후(View 랜더링 이후)
- 예외가 발생해도 항상 호출된다
- 예외와 무관하게 공통 처리를 할 경우 afterCompletion에서 처리한다.
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {}
default void postHandle(HttpServletRequest request, HttpServletResponseresponse response,
Object handler, @Nullable ModelAndView modelAndView) throws Exception {}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, @Nullable Exception ex) throws Exception {}
}
- 인터페이스 등록
WebMvcConfigurer
가 제공하는addInterceptors()
를 사용해서 인터셉터를 등록할 수 있다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor()) // 인터셉터 등록
.order(1) // 인터셉터 호출 순서
.addPathPatterns("/**") // 패턴 처리
.excludePathPatterns("/css/**", "/*.ico", "/error");
}
//...
}
Share article