배포된 서비스에서 특정 API가 리다이렉트되고 난 뒤 401 에러가 발생하면서 문제를 일으키게 되었다. 해당 문제에 대해 원인은 발견하고 해결 했지만 무슨 이유로 생기는지 어떻게 해결해야되는지 정리하면 좋을 것 같아서 정리하게 되었다.
문제가 되었던 컨트롤러
@GetMapping("")
public void hello(HttpServletRequest request, HttpServletResponse response) throws IOException {
String nickname = "riospring";
logging(request);
response.sendRedirect("/api/v1/user/" + nickname);
}
내가 원했던 API 통신
- path variable을 입력하지 않았을 때 가장 최근의 엔티티를 가져온다.
- 해당 엔티티의 Url로 리다이렉트한다.
- 해당 엔티티의 Url로 응답값을 전달해준다.
실제 API 통신
- path variable을 입력하지 않았을 때 가장 최근의 엔티티를 가져온다.
- 해당 엔티티의 Url로 리다이렉트한다.
- 401 Error 발생
문제원인
리다이렉트는 HTTP 프로토콜에서 새로운 요청-응답 주기를 생성하기 때문이다. 리다이렉트를 사용하면 서버는 클라이언트에게 리다이렉트 대상 URL을 가리키는 새로운 위치를 제공하며, Location에 있는 헤더의 정보를 통해 바로 이동하면서 JWT 토큰이 전달되지 않아서 생긴 문제였다. 해당 문제를 해결하기 위해서는 2가지 해결방법이 있는데 그방법은 아래와 같다.
클라이언트에서 301 코드를 따로 처리한다.
일반적으로 웹 브라우저와 다른 HTTP 클라이언트는 301 상태 코드를 자동으로 처리하여 지정된 위치로 리다이렉션을 수행한다. 하지만 경우에 따라 이러한 자동 리다이렉션을 중지하고 301 상태 코드를 수동으로 처리해야 되기 때문에 해당 옵션을 비활성화 하도록 합니다. 하지만 아래와 같은 경우는 일반적이지 않은 케이스이기 때문에 되도록이면 redirect를 사용하거나 forward를 사용하는게 좋다.
예시
서버
@Controller
public class MyController {
@GetMapping("/no-auto-redirect")
public ResponseEntity<String> noAutoRedirect() {
String redirectUrl = "https://example.com/target";
// Create response entity with status code and Location header
return ResponseEntity.status(HttpStatus.FOUND) // 302 status code
.header("Location", redirectUrl)
.body("Redirecting to: " + redirectUrl);
}
}
클라이언트
$.ajax({
url: '/your-endpoint',
type: 'GET', // or POST, depending on your needs
success: function(data, textStatus, xhr) {
if (xhr.status === 301 || xhr.status === 302) {
var redirectUrl = xhr.getResponseHeader('Location');
// Handle the redirect manually
// For example, you could update the URL in the browser's address bar:
window.location.href = redirectUrl;
} else {
// Handle the response data as usual
// ...
}
},
error: function(xhr, textStatus, errorThrown) {
// Handle any errors
// ...
}
});
서버에서 포워드를 통해 리다이렉트 한다.
스프링에서는 포워드라는 기능을 통해 클라이언트가 보낸 정보를 그대로 유지한채로 URL을 변경한 응답값을 전달 할 수 있다. 두번 통신하면서 발생하는 네트워크 비용과 서버에서 두번이나 요청을 처리하면서 발생하는 인증/인가나 로깅 등의 비용을 아끼면서 성능면으로도 빠르게 처리할 수 있다.
@GetMapping("/forward")
public void forward(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/api/v1/user/target");
requestDispatcher.forward(request, response);
}
'JAVA > Spring Boot' 카테고리의 다른 글
(Spring Security)스프링 환경에서 JWT 토큰 발급 (0) | 2022.03.21 |
---|---|
(node.js) express 프로젝트 구조 (0) | 2021.08.31 |
(Spring Boot) 동작 원리 (0) | 2021.08.10 |
(스프링 부트) 커스텀어노테이션으로 중복코드 방지 (0) | 2021.07.14 |
(스프링 부트) 구글 로그인 구현 (0) | 2021.07.13 |