Posted at

Spring Boot で Webコンテナを Undertow に切り替えたときに Cookie が読み込めない現象


概要

Spring MVC を使用した場合、デフォルトのWebコンテナは Tomcat ですが、切り替えることは可能です。 :point_right_tone2: 76. Embedded Web Servers

Tomcat から Undertow に切り替えた際に、既存で使用していた Cookie が読み込めなくなったので、その原因と解決方法をまとめます。

以下のバージョンで試したときの動作です。


  • Spring Boot 2.0.6.RELEASE


発生した現象

ドキュメントにあるように、以下の記述で Tomcat から Undertow に切り替えられます。


build.gradle

configurations {

compile.exclude module: 'spring-boot-starter-tomcat'
}
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web')
implementation('org.springframework.boot:spring-boot-starter-undertow')
}

再現コードとして以下の RestController を書きます。

hoge Cookie を取得してレスポンスするだけの単純なアプリケーションです。

@RestController

class DemoController {
@GetMapping("/")
public String hello(@CookieValue("hoge") String cookie) {
return cookie;
}
}

fuga&a=b という値で hoge Cookie をリクエストします。

すると、 Controller が受け取った値は fuga&a となっていて =b が消えてしまいました。

Tomcat の場合や、 spring-boot-starter-jetty で Jetty を使用した場合は期待通りに fuga&a=b が取得できます。

$ curl -H "Cookie: hoge=fuga&a=b" "http://localhost:8080/"

fuga&a


原因

Undertow に以下の記述があります。

https://github.com/undertow-io/undertow/blob/2.0.14.Final/core/src/main/java/io/undertow/util/Cookies.java#L176-L203


Parses the cookies from a list of "Cookie:" header values. The cookie header values are parsed according to RFC2109


RFC 2109 に従っているというわけで、そちらを読んでみるわけですが、どうやら Cookie の値の =" で囲まないといけないようです。

そのため、リクエストを以下のように " で囲むように変えると元々期待していた値を取得することができました。

$ curl -H 'Cookie: hoge="fuga&a=b"' "http://localhost:8080/"

fuga&a=b

原因はわかったとして、既存の "= を囲んでいないクライアントは問題が出てしまいます。

Undertow にはこれを解決するためのオプションが用意されています。 :point_right_tone2: Undertow


ALLOW_EQUALS_IN_COOKIE_VALUE

If this is true then Undertow will allow non-escaped equals characters in unquoted cookie values. Unquoted cookie values may not contain equals characters. If present the value ends before the equals sign. The remainder of the cookie value will be dropped. Defaults to false.


オプション名の通りですが、 Cookie の値として equal(=) を許可するものです。

Spring Boot でこのオプションを true にすれば良いので、以下のコードを追加します。

@Bean

public WebServerFactoryCustomizer<UndertowServletWebServerFactory> webServlerFactoryCustomizer() {
return undertow -> undertow.addBuilderCustomizers(builder -> builder.setServerOption(
UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, true));
}

こうすることで、以下のように = 以降の値も受け取れるようになります。

$ curl -H "Cookie: hoge=fuga&a=b" "http://localhost:8080/"

fuga&a=b


どうして Undertow だけ異なるのか

Undertow が RFC 2109 に従っているとして、他のWebコンテナ(Tomcat,Jetty)はなぜ問題にならなかったのでしょうか。

改めて Cookie についてですが、現在標準となっているのは RFC 6265 です。

HTTP cookie

Cookieとかセッションとかセキュリティとか

RFC 6265 を読むと RFC 2109 と異なり " で囲む必要がなくなっています。(囲んでも良い)


Jetty

CookieCutter.java



デフォルトはRFC6265のため " で囲む必要がありません。

以下のように設定でパーサーの挙動を切り替えることも可能なようです。

jetty.httpConfig.cookieCompliance=RFC2965


Tomcat

調べているうちにわかったのですが、 Tomcat はこのあたり以前から何度か変更してるみたいです。

Apache TomcatとHTTPクッキーにまつわる騒動 - GeekFactory

今はどうなっているのかと言うと、以下にあるように Rfc6265CookieProcessor が標準となっているので " で囲む必要がありません。

Apache Tomcat 9 Configuration Reference (9.0.12) - The Cookie Processor Component


The standard implementation of CookieProcessor is org.apache.tomcat.util.http.Rfc6265CookieProcessor.


LegacyCookieProcessor というものに切り替えることもでき、こちらを使用した場合はドキュメントにあるようにオプションで挙動を変更できるようなっています。今の Undertow と同じような感じですね。


allowEqualsInValue



所感

オプションで挙動は変えられるけど、 Undertow もデフォルトで RFC 6265 に従ったほうがいいと思いました。

後方互換の問題はあると思いますが、なんでこうなっているんでしょうね。

歴史とか経緯あまり知らないので、教えていただけたり、間違いなども指摘くださると助かります :pray:


参考