ロードバランサー配下のWebサーバは、ロードバランサーがもともとのクライアントのリクエスト情報を保持するために X-Forwarded-
というHTTPヘッダを追加します。このリクエスト元の情報を保存するヘッダはただのデファクトスタンダードでしたが、今は RFC にもなっているようです。
AWS 環境でもこれは同じで、公式サイトや色々なブログでも、ELB配下のWebサーバは X-Forwarded-For
を利用してクライアントのIPアドレスを取得できると記載されています。例えば tomcat のアクセスログでは、以下のように変更することでリクエスト元のIPアドレスを取得することができます:
# デフォルトのアクセスログフォーマット pattern="%h %l %u %t "%r" %s %b" # X-Forwarded-For などの、リクエスト元の情報を追加したアクセスログフォーマット pattern="%{X-Forwarded-For}i %{X-Forwarded-Proto}i %h %l %u %t "%r" %s %b"
ここで私がハマった、、というか今でもよくわからないのですが、サーブレット側で X-Forwarded-For
を取得しようとすると null
が返ってきてしまいました。アクセスログで X-Fowarded-For
にクライアントのIPが出力されたところで安心していて、アクセスログで出力している HTTPヘッダがサーブレットで取得できないなんて考えも及びませんでした。。サーブレットでHTTPヘッダを全て出力させましたが、 X-Forwarded-Proto
はあるのに X-Forwarded-For
が見当たりません。
// これが null になる、アクセスログでは取得できているのに String clientIP = request.getHeader("X-Forwarded-For");
検索すると同じ問題を質問している AWS スレッドがありました: Forums | AWS re:Post
かいつまむと、理由はわからないが Tomcat(というかサーブレット) だと request.getRemoteAddr()
でリクエスト元のIPアドレスが取得できる・・・ということです。 getRemoteAddr()
はネットワーク層のリクエスト元 IP アドレスを返却するようなので、本来であれば EC2 インスタンスの手前にある ELB のIPアドレスになるはずです。上のスレッドに回答は付いていないのですが、手元のAWS環境で試すと、確かに getRemoteAddr()
でリクエスト元のIPアドレスが取得できました。
今後 X-Forwarded-For
でリクエスト元の IPアドレスが取得でき、 getRemoteAddr()
でELBのIPアドレスが返ってこないとも限らないので、 X-Forwarded-For
をチェックして null であれば getRemoteAddr()
で埋めておくのが無難でしょうか。どなたかこの辺りの事情詳しければぜひ教えてください。
追記
Twitter で先輩から、tomcat の機能でそうなっているのではないか、ということで以下のモジュールを紹介してもらいました:
RemoteIpValve (Apache Tomcat 8.5.90 API Documentation)
このモジュールはクライアントのIPアドレスなどを X-Forwarded-
系のプロトコルの値と置き換えるモジュールで、今回の挙動が説明できます。実際に、Elastic beanstalk 経由で入れた tomcat の server.xml には上記モジュールが設定されていて、内部プロキシのIPアドレスも定義されていました。
<Valve className="org.apache.catalina.valves.RemoteIpValve" protocolHeader="X-Forwarded-Proto" internalProxies="10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|169\.254\.\d+\.\d+|127\.\d+\.\d+\.\d+|172\.(1[6-9]|2[0-9]|3[0-1])\.\d+\.\d+" />