Javaのrequest.getSession(false); がなぜかnullになってしまう。

JavaでServletプログラムを作成していてセッションを使っていたのですが、なぜか request.getSession(false); がnullになってしまう事象にあいました。

初回アクセス時
HttpSession session = request.getSession( true );

二回目以降
HttpSession session = request.getSession( false );

session.getAttribute("xxx"); でセッション属性を取得したいのだけれど、sessionがnullのため取得できない。。アレコレ悩みました。

今回のウェブサーバーは Nginx + Tomcat の構成(同一サーバーに相乗り)なのですが、Nginxの proxy_pass ディレクティブでTomcatへの転送先URLを指定しています。

proxy_pass http://localhost:8080/test/;

末尾の /test/ はウェブアプリケーションのコンテキストパスです。実際にServletにアクセスするURLは、以下のようにしています。

http://tomcat/sample/fwservlet

URL中の tomcat がドメインで fwservlet はServlet名です。Nginxを介さずに直接Tomcatにアクセスする場合は、このようになります(localhost はIPアドレスに読み替えてください)。

http://localhost:8080/test/sample/fwservlet

ここでNginxに向けてリクエストをするとダメなのだけれど、直接Tomcatに向けてリクエストをするとうまくいくことに気づきました。原因はNginxにあるのか?と思いつつも、Firefoxのデバッグモードでクッキーを見てみました。リクエストはNginxに向けて投げています。

これを見るとクッキーには JSESSIONID が設定されていることが確認できました。なので初回アクセス時に新規セッションは問題なく作成されているようです。そうすると、なぜ二回目以降のアクセスでこのセッションが取得できないのか?になるのですが、クッキーのPath属性を見ると /test となっています。

Path属性は「送信するURLにここで指定するパスが含まれている場合のみクッキーが送信される」ということになるのですが、Nginx向けのリクエストURLには /test はないです。直接Tomcatに向ける場合は含まれています。つまり、Nginxに向けて投げた場合、クッキーが送信されていないから request.getSession( false ); で JSESSIONID が取得できず、nullになってしまっていることがわかりました。

この解決策ですが、/test/ のコンテキストパスを変えるのはいまさらだとインパクトが大きい。なので web.xml のサーブレットマッピングをいじることで対応しました。リクエストURLに強引に /test のパスを含めるようなやり方です。

こんな感じです。


<servlet>
    <servlet-name>fwservlet</servlet-name>
    <servlet-class>sample.fwservlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>fwservlet</servlet-name>
    <url-pattern>/test/fwservlet</url-pattern>
</servlet-mapping>

JSPも同様に対応します。


<servlet>
    <servlet-name>nolist</servlet-name>
    <jsp-file>/sample/nolist.jsp</jsp-file>
</servlet>
<servlet-mapping>
    <servlet-name>nolist</servlet-name>
    <url-pattern>/test/nolist.jsp</url-pattern>
</servlet-mapping>

この設定をするとNginxへのリクエストURLは以下のように変わります。

http://tomcat/test/fwservlet

これでServletを動かすと request.getSession( false ); でセッションを取得することができるようになりました。

TomcatのServletでクライアントIPを取得すると 127.0.0.1 になる現象

TomcatのServletでクライアントIPを取得するために HttpServletRequest の getRemoteAddr() メソッドを使っているのですが、取得するIPがいつも 127.0.0.1 となってしまうので調べてみました。環境は以下の構成です。NginxとTomcatは相乗りです。

ServletでのクライアントIP取得のソースコードです。


//IPアドレスの取得
String ip = request.getHeader("X-FORWARDED-FOR");
if ( ip == null || "".equals(ip) )
	ip = request.getRemoteAddr();
if ( ip == null )
	ip = "0.0.0.0";

このロジックだと request.getRemoteAddr(); がいつも 127.0.0.1 になってしまいます。ループバックアドレスなので自身のIPアドレスとなっているようです。つまり、TomcatからみたクライアントはNginxとなっています。

これを解消するにはNginx側の設定ファイルに proxy_set_header ディレクティブの追加が必要だとわかりました。以下のようにします。


location / {
	 :
	proxy_set_header X-Forwarded-for $remote_addr;
	proxy_pass http://localhost:8080/test/;
	 :
}

Nginxはデフォルトでは X-Forwarded-For ヘッダを付けてくれないので、付加する設定がいるようです。Servlet側でも request.getHeader("X-FORWARDED-FOR"); の処理を入れておく必要があります。こうするとクライアントIPの取得ができました。

X-Forwarded-For ヘッダはプロキシサーバーを通過した際に、送信元IPアドレスを特定するために使われます。HTTPヘッダには以下のような形で付与されます。

X-Forwarded-For: <client>, <proxy1>, <proxy2>

リクエストが複数のプロキシサーバーを通過する場合、それぞれの通過するプロキシサーバーのIPアドレスが右側に付け足されていきます。つまり、左端のIPアドレスが元のクライアントのIPアドレスになります。

なお、1つ手前のプロキシサーバー(一番最後に経由したプロキシサーバー)のIPアドレスは X-Forwarded-For ヘッダには記載されないので そのIPアドレスを取得するには request.getRemoteAddr() メソッドで取得します。

2025/01/06 補足
今回のケースではNginxは多段プロキシとなっておらず X-Forwarded-For を付与する一番最初のサーバーとなっています。ですので、$proxy_add_x_forwarded_for は使用しませんでした。仮にNginxとTomcatを相乗りさせず別サーバーとした場合は以下のようになります。

・送信元のクライアントIPアドレスは、X-Forwarded-For から取得。
・NginxのIPアドレスは、request.getRemoteAddr() メソッドで取得。