Valid?
우리가 흔히 Server와 Client가 통신을 진행할 때
HTTP/S 방식으로 서로 데이터를 주고 받는 경우가 정말 많아요! ㅎㅎ
하지만 때로는 Client가 옳지 않은 정보를 전달할 수도 있고,
Server에게 위험을 가하는 정보를 전달할 수도 있다는 것을 항상 염두해두어야 해요!
그래서 요번 시간에는
우리가 만든 Spring Boot Application Server에서
어떻게 Client Request Body를 검증할 수 있는지 알아보는 시간을 가지도록 해볼게요!
직접 구현하기~!
우선은 Request, Response에 대한 객체를 정의해볼까요?
package com.huisam.springstudy.validation;
import lombok.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@AllArgsConstructor
@Getter
@NoArgsConstructor
public class OrderRequest {
@NotNull @NotBlank
private String name;
@NotNull @NotBlank
private String product;
@NotNull @NotBlank
private String address;
}
간단하게 Order에 대한 Request 정보에는
@Validation 하는 로직이 담겨져 있습니다.!
그리고 json 직렬화를 위한 lombok 어노테이션을 추가했어요!
package com.huisam.springstudy.validation;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
public class OrderResponse {
private String name;
private String product;
private int price;
private String address;
private String img;
private LocalDateTime orderedTime;
}
Order에 대한 Response 정보입니다
Client에서 요구한 정보를 바탕으로 전달해주는 정보들입니다.(거의 행동이 GET과 똑같지만, 예시를 위해 이렇게 진행합니다)
엇 그럼 잠깐
왜 굳이 Response, Request에 대한 객체를 나누었을까요?
일반적으로 DTO 객체를 만들어서 통일해도 좋습니다
하지만 보통은 Client로부터 받는 Request 정보는 때에 따라서 엄청 변할 수 있고,
그렇다고 Response는 Server에서 지정한 API 스펙이기 때문에 유동적으로 변동하기가 힘들죠
그래서 2개의 객체를 분리함으로써 Request와 Response를 분리한 것입니다!
자 그러면 Controller를 만들러 가볼까요?
package com.huisam.springstudy.validation;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.time.LocalDateTime;
@RestController
public class OrderValidController {
@PostMapping(value = "valid/order",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<OrderResponse> getOrder(
@RequestBody @Valid OrderRequest orderRequest) {
OrderResponse orderResponse = OrderResponse.builder()
.name(orderRequest.getName())
.product(orderRequest.getProduct())
.address(orderRequest.getAddress())
.price(1000)
.orderedTime(LocalDateTime.now())
.img(orderRequest.getProduct() + ".jpg")
.build();
return ResponseEntity.ok(orderResponse);
}
}
Reqeust Body와 Valid 어노테이션을 통해서 client로 부터 가져온 정보들을 검증하게 됩니다!
만일 검증이 성공하면, Controller에 있는 로직들을 진행할 것이고
검증에 실패하면, MethodArgumentNotValidException 을 던질 것입니다.!
이에 대한 처리는 @ControllerAdvice 혹은 @RestControllerAdvice를 통해서 Exception을 컨트롤할 수 있습니다.!
다만 저희는 그에 대한 부분은 생략할게요 :)
개발자 여러분들께서 구현하시기 나름이니까요!
그럼 실제로 요청을 날려볼까요?
서버를 띄워놓고 이름만 공백으로 보내게 되면.?
따로 Error에 대한 Response를 정의하지 않으면, 이렇게나 긴 응답을 받게됩니다.!
주요 포인트는 400(Bad Reqeust)를 받게 되죠!
테스트 코드로도 검증해볼까요?
package com.huisam.springstudy.validation;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(OrderValidController.class)
class OrderValidControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
@DisplayName("Request 이름이 null이면 BadRequest를 응답한다")
void when_request_name_is_null_then_return_badReqeust() throws Exception {
/* given */
OrderRequest orderRequest = OrderRequest.builder()
.name(null)
.product("computer")
.address("Seoul")
.build();
/* when & then */
mockMvc.perform(post("/valid/order")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(orderRequest))
)
.andDo(print())
.andExpect(status().isBadRequest());
}
}
실제 요청에 대한 이름을 null로 요청하게 되면.!
expect status는 위에서 보았듯이 BadRequest(400)을 응답받게 됩니다!
물론 위의 코드는 요즘 유행하는 SpringWebFlux 테스트는 아닙니다.!ㅎㅎ
한번 찾아서 구현해보시는 것을 추천드립니다!
만일 Request객체 안에 또 다른 객체가 있다면 @Valid를 통해서 하시면 됩니다.!
요렇게요~!
package com.huisam.springstudy.validation;
import lombok.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.Valid;
@AllArgsConstructor
@Builder
@Getter
@NoArgsConstructor
public class OrderRequest {
@Valid
private Order order;
@NotNull @NotBlank
private String product;
@NotNull @NotBlank
private String address;
}
마무리
지금까지 저희는 Client로 부터 받은 Request 정보를 검증하는 것을 배워보았습니다.!
Client로부터 받은 정보는 항상 검증하고, 믿지 말아야 함을 명심해야 됩니다!!
실무에서 유용하게 쓰시길 바라면서, 이만 글을 마칠까 해요!ㅎㅎ
'Developer > Spring' 카테고리의 다른 글
Spring AOP의 원리 - CGlib vs Dynamic Proxy (11) | 2020.08.18 |
---|---|
Spring에서 API 문서를 자동화하는 방법 - Spring REST Docs (0) | 2020.06.22 |
Spring Boot 에서 log를 남기는 방법 - Spring log 남기기 (4) | 2020.06.13 |
Spring Mapstruct - Java Entity DTO 매핑을 편하게 하자! (6) | 2020.03.25 |
Spring 이란? - Spring에 대한 소개 (1) DI / MVC의 관점에서 (0) | 2020.02.14 |