- java
- nginx
- 스프링
- session
- 자바
- join
- 프로그래머스
- Docker
- spring
- springboot
- mysql
- spring security 6
- spring boot
- select
- sql
- ORM
- AWS
- string
- 데이터베이스
- DI
- 1차원 배열
- 스프링부트
- Django
- SSL
- Git
- @transactional
- spring mvc
- jpa
- 문자열
- PYTHON
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
개발하는 자몽
[Spring Boot / Error] Required request body is missing 본문
상황
`Required reuqest body is missing`라는 문구와 함께 `HttpMessageNotReadableException` 타입의 에러가 발생했다.
`Controller`의 있는 메서드에서 `request body`를 인식하지 못하는 문제인 것으로 확인했다.
@RestController
public class RestController {
@PostMapping("/aaa")
public ResponseEntity<Void> aaa(@RequestBody @Valid final RequestDto dto) {
...
return ResponseEntity.status(HttpStatus.CREATED).build();
}
...
}
해당 메서드에 `@RequestBody`를 적용하여 JSON 데이터를 받고 있다. 메서드 내부에서 로그를 찍어봤을 때 해당 값이 잘 넘어오는 것을 확인했으며 디버깅을 했을 때도 존재하는 것을 확인했다. 그러나 반환값이 `201`이 아닌 `403`으로 확인된다.
원인
디버깅을 진행했을 때 직접 작성한 코드 내에서는 반환값이 정상적으로 201로 저장되었다. 그러다가 신기하게도 컨트롤러 전에 이미 거쳤을 `Filter`를 컨트롤러 이후에 또 방문하는 것을 발견했다.
계속 디버깅을 하다 보니 `OncePerRequestFilter`를 상속받는 커스텀 필터인 `JwtAuthenticationFilter`에 문제가 있었다.
Spring에서 모든 요청들은 컨트롤러에 도달하기 전에 Filter, Interceptor 등을 거친다. 직접 정의한 필터도 예외는 아니다.
아래 코드는 직접 정의한 필터이다.
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
if(특정 조건) {
filterChain.doFilter(request, response); //first doFilter
}
String token = getJwtFromRequest(request);
if (StringUtils.hasText(token)) {
...
}
filterChain.doFilter(request, response); //second doFilter
}
특정 조건인 경우에 토큰 검증 로직을 수행하지 않도록 바로 `doFilter`를 호출하는 코드인데, 여기에 문제가 있었다. 아무리 여기서 `doFilter`를 먼저 호출한다고 해도 결국은 메서드이므로 해당 메서드를 통해 컨트롤러까지 도달하여 나머지 로직을 수행하고 나면 다시 해당 필터로 돌아오게 된다.
실제로 아래와 같이 로깅을 해놓고 문제가 되는 API를 호출하면 다음과 같이 콘솔창에 출력되는 것을 확인할 수 있다.
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
if(특정 조건) {
log.info("before first doFilter");
filterChain.doFilter(request, response); //first doFilter
log.info("after first doFilter");
}
log.info("here");
String token = getJwtFromRequest(request);
if (StringUtils.hasText(token)) { //특정 조건의 경우 토큰이 필요없는 요청이므로 통과 못함
...
}
log.info("before second doFilter");
filterChain.doFilter(request, response); //second doFilter
}
...
before first doFilter
after first doFilter
here
before second doFilter
...
첫 번째 `doFilter` 이후로도 두 번째 `doFilter`에 방문하는 것을 알 수 있다.
따라서 그다음 로직을 수행하게 되어 가장 아래에 있는 `doFilter`를 거치게 된다. 이렇게 필터를 두 번 거치고 나니 `ResponseEntity`의 `status code`가 `403`으로 변하게 되었다. 내부 로직을 세세하게 보지는 못했으나, 필터를 또 거치면서 어떤 조건을 충족하게 되어 상태 코드가 변한 것 같다.
해결
해결방법은 간단하다. 필터를 두 번 거치지 않도록 하면 되므로 특정 조건의 경우 `doFilter`를 수행하게 했던 코드를 제거한다.
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
String token = getJwtFromRequest(request);
if (StringUtils.hasText(token)) {
...
}
filterChain.doFilter(request, response); //second doFilter
}
기존에 특정 조건을 확인했던 코드는 쓸데없이 검증하지 않도록 작성했던 것이다. 그러나 이미 아래에 `if`문으로 토큰을 확인하며, 반드시 `doFilter`를 수행하므로 해당 코드는 필요가 없는 것으로 판단하여 제거했다.
이후 원하는 대로 정상 작동했다!
🔖참고
https://upcurvewave.tistory.com/614
'Java > Spring' 카테고리의 다른 글
[JPA Error] No EntityManager with actual transaction available for current thread - cannot reliably process 'flush' call (0) | 2024.10.23 |
---|---|
[JPA] 임베디드 타입(@Embeddable, @Embedded)에 관하여 (0) | 2024.08.23 |
[Spring Security] 예외 처리하기 (0) | 2024.06.21 |
[Spring Security] 역할/권한 구현하기 (0) | 2024.06.14 |
[Spring Security] 토큰 기반 로그인/로그아웃 구현하기 (0) | 2024.06.08 |