HTTP 프로토콜로 서버에 요청 메시지를 보낼 때 Header에 Cookie를 포함할 수 있습니다. 이 때, Cookie가 여러 개이면 각각을 세미콜론(;)으로 구분합니다. 아래와 같은 형태로 Cookie를 보내면 서버에서는 “yummy_cookie”의 값은 “choco”로, “tasty_cookie”의 값은 “strawberry”로 인식합니다.
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
예전에는 아래와 같이 콤마(,)로 구분된 Cookie도 서버에서 2개로 인식하여 처리할 수 있었습니다. 하지만 콤마로 구분하는 것은 표준이 아니기 때문에 최신 버전의 웹 서버에서는 “yummy_cookie”의 값이 “choco, tasty_cookie=strawberry”인 것으로 인식됩니다.
Cookie: yummy_cookie=choco, tasty_cookie=strawberry
표준을 따르는 것이 올바른 방법이고, 일반 사용자들이 사용하는 최신 버전의 웹 브라우저에서는 여러 개의 Cookie를 세미콜론으로 처리하기 때문에 아무런 문제가 되지 않습니다. 그런데 만약 클라이언트가 웹 브라우저가 아닌 소프트웨어라면, 그 소프트웨어가 PC가 아닌 디바이스에 내장된 것이라면 표준이 아니라는 이유로 무시하기는 어려울 것입니다. 결국 문제는 레거시(Legacy)입니다.
Tomcat에서 Legacy Cookie 사용
일반적으로 많이 사용되는 Tomcat에서도 이러한 예전 방식의 Cookie를 처리할 수 있도록 LegacyCookieProcessor를 설정할 수 있습니다.
https://tomcat.apache.org/tomcat-8.5-doc/config/cookie-processor.html
또한, Spring Boot의 Embedded Tomcat을 사용하는 경우에는 WebServerFactoryCustomizer라는 Bean을 통해 LegacyCookieProcessor를 설정할 수 있습니다. (아래 링크의 “3.14. Use Tomcat’s LegacyCookieProcessor”를 참고하세요.)
Jetty에서 Legacy Cookie 사용
Spring Boot의 기본 임베디드 웹 서버는 Tomcat이지만 Jetty로 변경하여 사용하는 경우가 많이 있습니다. 아래는 pom.xml의 dependency에서 Tomcat을 제외하고 Jetty를 추가하는 예시입니다.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <!-- Exclude the Tomcat dependency --> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <!-- Use Jetty instead --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency>
Jetty를 사용하면 위에서 설명한 LegacyCookieProcessor를 사용할 수 없습니다. 왜냐하면 LegacyCookieProcessor는 Tomcat에서만 사용 가능한 클래스이기 때문입니다.
수많은 삽질 끝에 알아낸 방법은 Tomcat에서 설정했던 방법처럼 WebServerFactoryCustomizer라는 Bean을 통해 CookieCompliance.RFC2965를 설정하는 것입니다. 아래 코드를 참고하세요.
@Bean public WebServerFactoryCustomizer<JettyServletWebServerFactory> cookieProcessorCustomizer() { return factory -> factory.addServerCustomizers(server -> { for (Connector connector : server.getConnectors()) { if (connector instanceof ServerConnector) { HttpConnectionFactory connectionFactory = ((ServerConnector) connector).getConnectionFactory(HttpConnectionFactory.class); connectionFactory.getHttpConfiguration().setRequestCookieCompliance(CookieCompliance.RFC2965); } } }); }
마무리
레거시(Legacy)라는 괴물은 늘 앞으로 나아가려는 개발자들의 발목을 잡고 개발자들을 지치게 합니다. 그럼에도 우리가 견딜 수 있는 건 세상 어딘가에서 같은 삽질을 하며 그 괴물과 싸우고 있는 개발자들이 있다고 믿기 때문일 것입니다.
그러니까, 지치지 말자구요!! :)