概要
Spring MVC を使用した場合、デフォルトのWebコンテナは Tomcat ですが、切り替えることは可能です。 76. Embedded Web Servers
Tomcat から Undertow に切り替えた際に、既存で使用していた Cookie が読み込めなくなったので、その原因と解決方法をまとめます。
以下のバージョンで試したときの動作です。
- Spring Boot 2.0.6.RELEASE
発生した現象
ドキュメントにあるように、以下の記述で Tomcat から Undertow に切り替えられます。
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 に以下の記述があります。
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 にはこれを解決するためのオプションが用意されています。 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 に従ったほうがいいと思いました。
後方互換の問題はあると思いますが、なんでこうなっているんでしょうね。
歴史とか経緯あまり知らないので、教えていただけたり、間違いなども指摘くださると助かります