티스토리 뷰

얼마전에 다음과 같은 이슈가 들어왔다.

  • 톰캣과 JEUS(상용 WAS서버의 하나)의 Location Header가 다르다.
  • 톰캣은 Location 헤더에 상대경로를 사용하고 있고, JEUS는 절대경로를 사용하고 있다.
  • 이로 인해 톰캣 기반으로 만든 솔루션이 오작동 하고 있다.

톰캣의 http 헤더

참고로 제우스는 location header 를 보낼 때 절대경로로 보낸다(주소가 들어가 있어 사진은 생략).

아마 솔루션 개발자 측에서는 당연히 상대경로로 올것이라고 예상하고, 절대경로로 변환하는 로직을 넣었을 것이라고 추정해볼 수 있다.

 

여기서 우리가 살펴봐야 할 것은 두 가지이다.

 

  1. Location 헤더가 뭐지?
  2. 왜 톰캣과 제우스가 동작이 다를까?

이에 대해서는 HTTP/1.1 스펙과 서블릿 스펙, 톰캣의 bugzilla를 확인해보면 알 수 있다.

 

먼저 1. Location 헤더가 뭐지? 부터 살펴보자.

 

location header는 redirect에 쓰이는 헤더이다. 어떤 요청이 WAS나 Web server로 들어왔을 때, 다른 주소의 페이지 등으로 요청을 다시 보내도록 지시하는 헤더이다.

이에 대한 내용은 rfc 7231에 나와 있다.

 

For 201 (Created) responses, the Location value refers to the primary resource created by the request. For 3xx (Redirection) responses, the Location value refers to the preferred target resource for automatically redirecting the request.

 

이 경우에는 302로 응답이 나갔으니 진하게 칠한 부분이 해당되는 부분이다.

 

그렇다면 왜 톰캣과 제우스의 응답이 다를까?

이에 대한 내용은 톰캣 bugzilla와 servlet spec을 살펴보면 알 수 있다.

 

일단 이 이슈에서 문제가 된 부분은

HttpServletResponse#sendRedirect 메서드이다.

 

서블릿 스펙을 살펴보자.

서블릿 스펙 3.1의 5.5장을 보면 다음과 같은 내용이 나온다.

5.5 Convenience Methods
The sendRedirect method will set the appropriate headers and content body to redirect the client to a different URL. It is legal to call this method with a relative URL path, however the underlying container must translate the relative path to a fully qualified URL for transmission back to the client. 

 

유저는 편의를 위해 상대경로로 보낼 수 있지만, 컨테이너 쪽에서 절대경로로 바꾸어주어야 한다는 내용이다. 즉, 유저가 상대경로로 보내고 싶다면, 서블릿의 sendRedirect 메서드를 사용해서는 안된다.

참고로 오라클의 공식 javadoc sendRedirect 메서드에 대해 이렇게 나와있다.

 

Sends a temporary redirect response to the client using the specified redirect location URL and clears the buffer. The buffer will be replaced with the data set by this method. Calling this method sets the status code to SC_FOUND 302 (Found). This method can accept relative URLs;the servlet container must convert the relative URL to an absolute URL before sending the response to the client. If the location is relative without a leading '/' the container interprets it as relative to the current request URI. If the location is relative with a leading '/' the container interprets it as relative to the servlet container root. If the location is relative with two leading '/' the container interprets it as a network-path reference (see RFC 3986: Uniform Resource Identifier (URI): Generic Syntax, section 4.2 "Relative Reference").

 

서블릿 스펙과 javaee7의 javadoc에서는 일관적으로 절대경로로 Location Header를 만들 것을 명확하게 언급하고 있다.

 

그렇다면 왜 톰캣은 상대경로를 사용하고 있는 것일까?

이에 대한 설명은 다시 location Header의 스펙으로 돌아가야 한다.

HTTP의 오래전 스펙중에 rfc2616란 스펙이 있다. 이 스펙에서는 location header에 대해 다음과 같이 언급하고 있다.

 

14.30 Location

The Location response-header field is used to redirect the recipient to a location other than the Request-URI for completion of the request or identification of a new resource. For 201 (Created) responses, the Location is that of the new resource which was created by the request. For 3xx responses, the location SHOULD indicate the server's preferred URI for automatic redirection to the resource.

The field value consists of a single absolute URI. Location = "Location" ":" absoluteURI

An example is:

Location: http://www.w3.org/pub/WWW/People.html

 

과거에는 스펙에서 절대경로를 언급하고 있다.

 

하지만 최근 스펙은 다르다. rfc7231에서는 location header에 대해 이렇게 언급한다.

 

If the Location value provided in a 3xx (Redirection) response does not have a fragment component, a user agent MUST process the redirection as if the value inherits the fragment component of the URI reference used to generate the request target (i.e., the redirection inherits the original reference's fragment, if any).

For example, a GET request generated for the URI reference "http://www.example.org/~tim" might result in a 303 (See Other) response containing the header field:

Location: /People.html#tim

which suggests that the user agent redirect to "http://www.example.org/People.html#tim"

 

예제가 추가되었다. 절대경로에 대한 예제는 후술되는 스펙에 나오기 때문에 "절대경로가 금지되었다"라고 해석하는게 아니라, "상대경로도 허용되었다"라고 보는게 맞다. 서버에서 절대경로를 만들기 위해 드는 비용을 절감하고, 서버-클라이언트간 주고받는 패킷도 1byte라도 아끼기 위한 노력의 일환으로 보인다.

 

톰캣은 이를 참고한 것으로 보인다.

톰캣의 관련 버그질라 링크는 아래와 같다.

https://bz.apache.org/bugzilla/show_bug.cgi?id=56917

불러오는 중입니다...

이슈 이름부터 Create a configuration to write relative 302 responses instead of absolute 이다.

 

이 이슈의 첫 커멘트부터 이렇게 적혀있다.

 

This would be contrary to the requirements of the Servlet specification. See https://java.net/jira/browse/SERVLET_SPEC-100 for the request to change that (and some other stuff).

 

그렇다. 위에서 말했듯이 스펙 위반인 것이다. 이를 알고 바꾼 것이다.

이에 대해 issuer는 이렇게 말한다.

 

Both the servlet spec and Tomcat need to be updated then. Tomcat doesn't have to wait for the servlet spec, unless the update is already accepted. The expense of doing 302 rewriting can be cleaned from numerous webserver environments.

 

물론 지당한 말이다. 하지만, 이후 발표된 서블릿 스펙 4.0에서도 상대경로에 관한 부분은 수용되지 않았다. 스펙이 바뀔거라고 기대하고 바꿨지만, 스펙은 그대로고, 모든게 다 그대로이다.

 

결국 다른 부분에서 문제가 생겼다.

 

1. I think this feature must be configurable. a. Regardless of browsers, this may affect configuration of reverse proxies. I guess that ProxyPassReverse may fail to rewrite some of redirects. http://httpd.apache.org/docs/2.4/mod/mod_proxy.html#proxypassreverse b. There may be some old / dumb clients. I am REOPENING this.

2. I agree that this is a nice feature and I think that enabling this feature by default is better from security point of view.

 

아파치에서 해당 헤더를 수용 못하는 것이다. 결국 이에 대해 옵션으로 절대경로와 상대경로를 고를 수 있도록 톰캣 측에서 옵션을 제공했다. 문제는 기본값이 서블릿 스펙을 위반한 "상대경로허용"이라는 것이다....

 

결국, 톰캣은 HTTP 최신 스펙을 반영했지만, 기본값은 서블릿 스펙을 위반한 셈이 되었다.

제우스는 최신 스펙을 반영하진 않았지만, 서블릿 스펙을 준수하고, HTTP 스펙도한 준수하고 있다(절대경로는 여전히 사용 가능해야 한다).

솔루션은 서블릿 스펙을 준수할 필요가 없다. 그러나, HTTP 스펙은 준수해야 한다. 절대경로로 오는 경우에 대해 처리를 하지 않았다는 것 자체가 솔루션 개발자의 스펙에 대한 무지를 드러냈다고 할 수 있다.

 

추가로 톰캣의 시장지배자적 위치를 이용한 막무가내 수정을 이번에 처음으로 알 수 있었다.

 

이 이슈와 별개로, 위 그림에서 Server 헤더가 등장하는데, 서버헤더를 찍어서 보내는 것은 권장하는 방법이 아니다.

코드를 고치든, mod_security를 사용하든 제거할 방안이 필요해보이지만, 이 부분은 내 영역 밖이기 때문에 아무말도 하지 않았다.

'개발 > 아파치와 톰캣' 카테고리의 다른 글

자바 어플리케이션 inputstream 주의사항  (0) 2019.01.20
ajp 요청 헤더2  (0) 2019.01.20
AJP 요청 헤더  (0) 2018.10.27
ajp 프로토콜 설정  (0) 2018.07.08
아파치 간단 빌드  (0) 2018.07.06
공지사항
최근에 올라온 글
최근에 달린 댓글
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
글 보관함