JavaでX-Forwarded-Forを取る

JavaでX-Forwarded-Forを取るため、以下のコードを書いた。

String xff = httpServletRequest.getHeader("X-Forwarded-For");

ローカル環境で実行しているときは問題なく取得できたが、Kubernetes上では取得できなかった。

getHeaderの代わりにgetHeadersを試してもうまくいかない。

Enumeration<String> xffs = httpServletRequest.getHeaders("X-Forwarded-For");

HTTPヘッダーの一覧を取得してみても、Kubernetes環境ではX-Forwarded-Forが含まれていない。

Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
while (headerNames.hasMoreElements()) {
    String headerName = headerNames.nextElement();
    System.out.println(headerName);
}

アプリケーションはSpring Boot 2.7 で実装していたため、SpringとX-Forwarded-Forを検索して調査することにした。

server.use-forwarded-headers

HerokuのHelpにWhy can't I access the X-Forwarded-For header in my Java Spring app?というものがあり、Spring FrameworkがX-Forwarded-Forを取り除いてTomcatのRemoteIpValve機能の設定に使用するということが書いてあった。

Helpに従い、server.use-forward-headers=falseの設定をapplication.propertiesに書いてみたが、IntelliJ IDEAにDeprecatedであることを指摘された。

server.forward-headers-strategy

server.use-forwarded-headersの代わりにserver.forward-headers-strategyが導入されていた。

server.forward-headers-strategyについて調べると、Spring Boot / How-to Guides / Embedded Web Serversに以下の記述があった。

If your application runs in a supported Cloud Platform, the server.forward-headers-strategy property defaults to NATIVE. In all other instances, it defaults to NONE.

以下のクラウドプラットフォームではserver.forward-headers-strategyのデフォルトがNONEからNATIVEに変わっているらしい。

  • Azure App Service platform.
  • Cloud Foundry platform.
  • Heroku platform.
  • Kubernetes platform.
  • Nomad platform.
  • SAP Cloud platform.

ローカル環境でもserver.forward-headers-strategy=NATIVEに設定してテストしたところ、X-Forwarded-Forが取れないことがわかった。

Kubernetes上のSpring BootでX-Forwarded-Forを取得する

httpServletRequest.getRemoteAddr()

server.forward-headers-strategy=NATIVEのとき、httpServletRequest.getRemoteAddr()をするとカンマで区切られたX-Forwarded-Forのうち、プライベートIPアドレスの左隣の値が取得できた。全てプライベートIPアドレスだった場合は一番最初の値が取得できた。

server.tomcat.remoteip.internal-proxiesのデフォルトが以下になっている。

10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|192\\.168\\.\\d{1,3}\\.\\d{1,3}|169\\.254\\.\\d{1,3}\\.\\d{1,3}|127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|100\\.6[4-9]{1}\\.\\d{1,3}\\.\\d{1,3}|100\\.[7-9]{1}\\d{1}\\.\\d{1,3}\\.\\d{1,3}|100\\.1[0-1]{1}\\d{1}\\.\\d{1,3}\\.\\d{1,3}|100\\.12[0-7]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}|0:0:0:0:0:0:0:1|::1

X-Forwarded-Forに設定されているIPアドレスのリストのうち、信頼できるIPアドレス(デフォルトではプライベートIPアドレス)の一個前をクライアントIPアドレスとして採用するというスタンダードな方法なため、特に違和感なく使用できる。

server.forward-headers-strategy=NONE

Kubernetes上で実行するときとローカルで実行するときで動作が変わってしまうのが一番分かりづらいポイントだった。明示的にserver.forward-headers-strategy=NONEを指定すれば、環境に関わらずX-Forwarded-Forが取得できる。

ただし、信頼できるIPアドレスの一個前をクライアントIPアドレスとして採用する処理を自前で書かないといけないという問題がある。

単にX-Forwarded-Forに設定されている全IPアドレスを取得するなどが目的の場合はこちらが適していると思う。