ましめも

技術系メモ

playのRequest#remoteAddressで取れるのはIPアドレスではない

ちゃんと細かくドキュメントを読まず http://www.playframework.com/documentation/2.1.0/HTTPServer の書くままに設定してると痛い目にあう

突然カンマ区切りのIPアドレスがやってくる

FugaLogging.write(id, "投稿しました", request.remoteAddress)

こんな感じでログを残していたのだが突然IPアドレスがカンマ区切りで記録されていることが…

2014-02-10 23:36:41,933 XXXXXX    A.B.C.D
2014-02-10 23:36:47,432 XXXXXX    A.B.C.D
2014-02-10 23:37:47,142 XXXXXX    A.B.C.D, E.F.G.H
2014-02-10 23:38:15,870 XXXXXX    A.B.C.D
2014-02-10 23:38:20,336 XXXXXX    A.B.C.D

うっ…

Request#remoteAddressはX-Forwarded-Forを返す

http://www.playframework.com/documentation/2.1.0/HTTPServerの下に記載されている通り次の場合はRequest#remoteAddressの返り値は接続元のIPアドレスではなくX-Forwarded-Forとなる。これは、nginxやApache等のリバースプロキシ存在下を考慮した仕様。

  • 127.0.0.1からのアクセス
  • application.conf等にtrustxforwarded=trueが書かれている

ドキュメントの通り次のようにnginxでproxy_set_headerを設定しているとX-Forwarded-Forには "接続元のIPアドレス" もしくは "接続元のIPアドレス, nginxにアクセスした時点で付加されていたX-Forwarded-For" という形になる。

  proxy_set_header   X-Real-IP $remote_addr;
  proxy_set_header   X-Scheme $scheme;
  proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;

アクセス元のIPアドレスだけを取る方法

今回のケースでは迷惑行為防止としてログに記録するのが目的なのでX-Forwarded-Forが全て記録されているのは好都合だったが、本当に「アクセス元のIPアドレス」がほしい場合があるかもしれない。

そういう場合は次のいずれかの方法をとれば良い。

  • Request#remoteAddress の結果を分解して一番右のIPアドレスを得る (おすすめ)
    • メリット: 確実
    • デメリット: ちょっとだけ面倒
    • (僕の頭でplayが勝手にX-Forwarded-Forの一番右のIPアドレスを取り出すのかと思い込んでいた…)
  • 上記のドキュメント通りのnginxの設定だった場合、X-Real-IPヘッダを取り出し使う
    • メリット: 簡単
    • デメリット: playには直接アクセスできないことが条件(X-Real-IPヘッダが信頼できる状況)
      • パフォーマンスの関係上、クライアントが直接playにリクエストを送りたい場合がある(一部の通信はnginxを通して, 一部の通信は直接playに…とか)と、X-Real-IPヘッダが信頼できるのかどうか判定できない。
  • proxy_set_header X-Forwarded-For $remote_addr としてしまう
    • メリット: 簡単
    • デメリット: X-Forwarded-Forの意味からズレてる


Request#remoteAddress が playへの直接アクセスのときとリバースプロキシ通したときとで意味が変わるのはややこしいなあ…