Spring Boot에서 Legacy Cookie 사용하기 (Tomcat & Jetty)

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”를 참고하세요.)

https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-use-tomcat-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)라는 괴물은 늘 앞으로 나아가려는 개발자들의 발목을 잡고 개발자들을 지치게 합니다. 그럼에도 우리가 견딜 수 있는 건 세상 어딘가에서 같은 삽질을 하며 그 괴물과 싸우고 있는 개발자들이 있다고 믿기 때문일 것입니다.
그러니까, 지치지 말자구요!! :)

Leave a Reply

Your email address will not be published. Required fields are marked *