티스토리 뷰

자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다.

 

토이 프로젝트로 배우는 자바 스프링 [0]. prologue

자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다. 프론트 코드 : https://github.com/laboratory-kkoon9/connector_front GitHub - laboratory-kkoon9/connector_front Contribute to laboratory-kkoon9/co

kkoon9.tistory.com

프론트 코드 : https://github.com/laboratory-kkoon9/connector_front

백엔드 코드 : https://github.com/laboratory-kkoon9/connector_back

배경

@RestController와 @Controller의 차이점을 공부하는 도중 생긴 에러입니다.

@Controller
public class MyRestController {

    @GetMapping("/api/hello")
    public User hello() {
        return User.builder().id(1L).name("eric").build();
    }
}

이전 포스팅에서 Filter를 거치지 않게 하기 (shouldNotFilter)를 배웠습니다.

현재 상황을 보면 @Controller로 선언된 API를 호출했는데, api 요청이 끝날 때 /api를 prefix로 한 요청이 한 번 더 filter에 걸리고 있습니다. 이때, dispatcherType이 FORWARD로 되어 있다는 것을 확인했습니다.

그래서 /api/api/hello라는 request는 jwtFilter에 걸쳐서 jwt token 예외를 발생시켰습니다.

원인

이 문제는 서블릿 포워드(forward) 동작이 발생하고 있기 때문에 나타나는 현상이라고 합니다.

@Controller로 선언된 API에서는 dispatcherType이 FORWARD로 설정되고 추가 요청이 발생한다고 합니다.

먼저 @Controller와@RestController의 차이점을 알아봅시다.

@Controller vs @RestController

Spring MVC에서는 기본적으로 @Controller는 뷰(view)를 반환하는 것으로 간주합니다.

예를 들어, String을 반환하면 이는 뷰 이름(view name)으로 간주되어, Spring의 뷰 리졸버(View Resolver)에서 해당 이름의 뷰를 찾으려 합니다. 뷰 이름이 매핑되지 않으면, 기본적으로 포워드(forward)를 통해 해당 경로로 다시 요청이 전달됩니다.

@RestController는 @Controller + @ResponseBody의 조합입니다.

즉, JSON 형식으로 직접 응답을 반환하므로 뷰 리졸버가 동작하지 않습니다. 반환 값이 뷰 이름이 아니라 HTTP 응답 바디로 전송되기 때문에 포워드 동작이 발생하지 않습니다.

prefix가 붙는 원인

@Controller로 선언했기 때문에 뷰 리졸버가 동작했다는 걸 알았고, 뷰 이름이 매핑되지 않았기 때문에 forward를 통해 해당 경로로 다시 요청이 전달되었다는 것까지 알게 되었습니다.

그렇다면 왜 "/api/api/hello"로 forward 되었을까요?

/api/hello 뿐만 아니라 /api/hello/temp 등등 여러 path로 호출해봤을 때 다음과 같은 패턴이 있었습니다.

  • /api/hello/temp로 호출했을 때 : /api/hello/api/hello/temp 로 forward
  • /api/hello/temp/tmp로 호출했을 때 : /api/hello/temp/api/hello/temp/tmp 로 forward 

이걸로 미루어봤을 때 api path에서 마지막 depth를 삭제 한 path + api path로 forward하는게 아닐까 싶습니다.

정확한 이유는 좀 더 공부해본 뒤에 다른 포스팅에서 다뤄보도록 하겠습니다.

해결방법

forward를 하게 하지 않으려면 여러 방법이 존재합니다.

제 상황에서 가장 현명한 해결 방법은 @RestController를 사용하거나 리턴 타입을 ResponseEntity 타입으로 내려주는 것입니다.

@Controller
public class MyRestController {

    @GetMapping("/api/hello")
    public ResponseEntity<User> helzzlo() {
        return ResponseEntity.ok(User.builder().id(1L).name("eric").build());
    }
}
@RestController
public class MyRestController {

    @GetMapping("/api/hello")
    public User helzzlo() {
        return User.builder().id(1L).name("eric").build();
    }
}
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
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 31
글 보관함