Java Servlet/JSP: カスタムエラーページとアクセス制限を併用する場合の注意点

Java Servlet/JSP でサーバアプリケーションを作成する時、設定ファイル web.xml に記述することで、以下のような機能が使える。

  • カスタムエラーページ。HTTP ステータスコード (or 例外種別) ごとに、ブラウザに返されるエラーページを変更することができる。
  • アクセス制限。アプリケーションが使用する URL の一部または全部に対して、認証を要求することができる。

しかし、この両者を組み合わせて使う場合、気をつけないと、「アクセス制限をかけたはずのページにアクセスしても、認証ダイアログが表示されず、カスタムエラーページがいきなり表示されてしまう」という落とし罠がある。

原因

BASIC 認証や DIGEST 認証を使用する場合、最初にブラウザからサーバへアクセスされた時に、レスポンスとして、HTTP ステータスコード 401 が返される。この時、レスポンスのヘッダには

WWW-Authenticate: Basic realm="myrealm"

が含まれ (BASIC 認証で、レルム名が myrealm の場合)、ボディはエラーページになっている。
これをブラウザが受け取ると、まず認証ダイアログを表示する。ユーザが ID とパスワードを入力した場合はその情報を含めるかたちでリクエストを送りなおし、ユーザがキャンセルした場合はエラーページの内容を表示する。
ところが、カスタムエラーページを設定していると、サーバがリクエストを受け取った後、カスタムエラーページが呼び出される際に、状態がリセットされてしまう。結果としてブラウザに返されるレスポンスは、ステータスが 200 (OK)、WWW-Authenticate ヘッダは含まれず、ボディがカスタムエラーページ……ということになる。これでは、認証を要求しない普通のページと同じである。
したがって、ブラウザは認証ダイアログを表示せず、そのままエラーページの内容を表示してしまう。

対応方法

認証が正常に機能するためには、カスタムエラーページを (静的な HTML ではなく) JSP として作成する必要がある。
そして、JSP のファイル内で、ステータスとヘッダを設定しなおす。

<%
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setHeader("WWW-Authenticate", "Basic realm=\"myrealm\"");
%>