リクエストの処理

フィルタでは、リクエストオブジェクトのヘッダーまたは ServletContext を直接処理できます。ServletRequestWrapper クラスまたは HttpServletRequestWrapper クラスを拡張するオブジェクト内にリクエストをラップすることによって、リクエストメソッドをオーバーライドすることもできます。ラッパークラスの使用方法の詳細については、 「ラッパーの使用」 を参照してください。

このセクションでは、リクエストの一般的な処理方法について説明します。

リクエストヘッダーの処理

リクエストオブジェクトには、フィルタがさまざまな方法で使用できるヘッダー情報が含まれています。これらのヘッダーはクライアントブラウザによって生成されます。リクエストヘッダーを調べることによって、フィルタは次のタスクを実行できます。

フィルタは通常、次のリクエストヘッダーを使用します。

ヘッダー情報のロギング

フィルタの一般的なタスクは HTTP リクエストおよびレスポンスヘッダーをログファイルや標準出力に出力することです。この機能はリクエストおよびレスポンスのデバッグおよび解析に役立ちます。

次のフィルタコードサンプルでいくつかの方法を紹介します。

package jrunsamples.filters;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
public class HeaderFilter extends GenericFilter {
 public void doFilter(ServletRequest req, ServletResponse resp, 
FilterChain chain) throws java.io.IOException, 
javax.servlet.ServletException {
  ServletContext context = filterConfig.getServletContext();
  chain.doFilter(req, resp);
  HttpServletRequest request = (HttpServletRequest)req;
  context.log("******リクエストヘッダー******");
  context.log("リクエスト:" + request.getRequestURI());
  Enumeration headers = request.getHeaderNames();
  if (headers == null) {
  } else {
   while (headers.hasMoreElements()) {
    String name = (String) headers.nextElement();
    String value = request.getHeader(name);
    context.log(name + "=" + value);
   }
  }
  HttpServletResponse response = (HttpServletResponse)resp;
  context.log("******レスポンス情報******");
  String charencode = response.getCharacterEncoding();
  int bufsize = response.getBufferSize();
  context.log("文字エンコード:" + charencode);
  context.log("バッファーサイズ:" + bufsize);
 }
}

Web アプリケーションの web.xml デプロイメントディスクリプタで、フィルタを定義し、そのマッピングを追加します。次に例を示します。

<filter> 
 <filter-name>HeaderFilter</filter-name>
 <filter-class>jrunsamples.filters.HeaderFilter</filter-class> 
</filter>
<filter-mapping> 
 <filter-name>HeaderFilter</filter-name>
 <url-pattern>/*</url-pattern> 
</filter-mapping> 

Web アプリケーション内のリソースをリクエストする際に、このフィルタは、次のような出力を生成します。

11/21 08:04:34 info JSPServlet:init
11/21 08:04:34 info ******リクエストヘッダー******
11/21 08:04:34 info リクエスト:/welcome.jsp
11/21 08:04:34 info Accept=image/gif, image/x-xbitmap, image/jpeg, 
image/pjpeg,application/vnd.ms-powerpoint, application/
vnd.ms-excel, application/msword, */*
11/21 08:04:34 info Cookie=JSESSIONID=1887861006284883609
11/21 08:04:34 info Connection=Keep-Alive
11/21 08:04:34 info Host=localhost:8100
11/21 08:04:34 info Accept-Encoding=gzip, deflate
11/21 08:04:34 info User-Agent=Mozilla/4.0 (compatible; MSIE 5.5; 
Windows NT 5.0)
11/21 08:04:34 info Accept-Language=en-us
11/21 08:04:34 info ******レスポンス情報******
11/21 08:04:34 info 文字エンコード:ISO-8859-1
11/21 08:04:34 info バッファサイズ: 8192

リクエストパラメータに基づいたリクエストの転送

フィルタはリクエストパラメータにアクセスできます。リクエストパラメータは通常、<FORM> ブロック内のリクエスト側ページによって設定されるか、セッションオブジェクト内の値から取り出されます。リクエストパラメータは、リクエストクエリ文字列 (GET リクエストなど) に含めたり、リクエスト本体 (POST リクエストなど) に含めることができます。

フィルタは通常、チェーン内の次のフィルタにリクエストをチャレンジなしで渡します。ただし、これは必須動作ではありません。リクエストを処理する際、フィルタは、リクエストを他のリソースに転送したり、独自のレスポンスを生成したりすることができます。この機能は、セキュリティチェックによるアクセスの阻止、ユーザーの転送、ブラウザタイプに基づいてリクエストの転送を行う場合に便利です。

フィルタチェーンを中断するには、RequestDispatcher を使用し、リクエストを転送することによって行います。サーブレットまたはフィルタの doFilter メソッド内でリクエスト転送を呼び出した後、チェーン内で、そのリクエストの前または後にフィルタが処理されることはありません。これは、既にリクエストを前処理したフィルタが対象となります。

次のコードサンプルは、リクエストパラメータ "username" が "nick" でない場合にリクエストを転送します。

...
public void doFilter (ServletRequest request, ServletResponse 
response, FilterChain chain) throws IOException, 
ServletException {
 if (request.getParameter("username").equals("nick")) {
  ...//通常のフィルタ処理
 } else {
  forwardToErrorPage(request, response);
 }
 chain.doFilter(request, response);
}
public void forwardToErrorPage (ServletRequest req, ServletResponse 
res) {
 System.out.println("エラーページに転送されました");
 try {
  RequestDispatcher rd = req.getRequestDispatcher("/Error.jsp");
  rd.forward(req, res);
 } catch (Exception e) {
  System.out.println(e.toString());
 }
}
...

User-Agent に基づいたリクエストの転送

リクエスト転送のその他の例としては、リクエスト側のブラウザのタイプに基づいた方法があります。リクエストオブジェクトの User-Agent ヘッダーを調べて、ブラウザのタイプを検出し、非互換ブラウザを転送することができます。ヘッダーに直接アクセスするには、ServletRequest オブジェクトを HttpServletRequest としてキャストする必要があります。たとえば、次のように記述します。

...
public void doFilter(ServletRequest req, ServletResponse resp, 
FilterChain chain) throws IOException, ServletException {
 HttpServletRequest request = (HttpServletRequest)req;
 String agent = (String) request.getHeader("User-Agent");
 System.out.println("agent=" + agent);
 if (agent.startsWith("Mozilla") || agent.startsWith("Microsoft")) {
  chain.doFilter(req, resp);
 } else {
  try {
   RequestDispatcher rd = request.getRequestDispatcher("/Non-Com/");
   rd.forward(req, resp);
  } catch (Exception e) {
   System.out.println(e.toString());
  }
 }
 chain.doFilter(req,resp);
}
...

フィルタを使用した基本的な認証の指定

認証は、ユーザーの資格 (ユーザ名とパスワード) を収集し、それらをシステム内で検証する処理です。認証では通常、資格を、データベースまたは LDAP サーバなどの一部のユーザーレポジトリと照合し、ユーザーの ID を確認する必要があります。リクエストヘッダーを評価することによって、フィルタを使用して、Web アプリケーションリソースに基本的なホストベース認証を提供できます。

次のフィルタサンプルは、受信リクエストの IP アドレスを検証し、ローカルホスト (127 で始まる IP アドレス) からのリクエストだけがチェーンを通過できるようにして実現するホストベース認証です。フィルタは RequestDispatcher を使用して、非認証 IP アドレスからのリクエストをエラーページに転送します。

リクエストヘッダーはなりすまされる可能性があるため、この方法は本格的な認証方法ではありません。しかし、セキュリティ戦略全体の一部を担うものとして有効です。

package jrunsamples.filters;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class HostAuthFilter extends GenericFilter {
 public void doFilter(ServletRequest request, ServletResponse 
response, FilterChain chain) throws java.io.IOException, 
javax.servlet.ServletException {
  String remoteaddr = request.getRemoteAddr();
  System.out.println("remoteaddr = " + remoteaddr);
  if (remoteaddr.startsWith("127.")) {
   System.out.println("ホストからのリクエストは認証されました");
   chain.doFilter(request, response);
  } else {
   RequestDispatcher rd = request.getRequestDispatcher("/Error.jsp");
   System.out.println("ホストからのリクエストは認証されませんでした");
   rd.forward(request, response);
  }
 }
}

Web アプリケーションの web.xml デプロイメントディスクリプタで、フィルタを定義し、そのマッピングを追加します。次に例を示します。

<filter> 
 <filter-name>HostAuthFilter</filter-name>
 <filter-class>jrunsamples.filters.HostAuthFilter</filter-class> 
</filter>
<filter-mapping> 
 <filter-name>HostAuthFilter</filter-name>
 <url-pattern>/startpage.html</url-pattern> 
</filter-mapping>

ローカルホストから Web アプリケーション内の startpage.html をリクエストすると、このフィルタは、次のようなメッセージを出力します。

remoteaddr = 127.0.0.1
ホストからのリクエストは認証されました

認証条件と一致しない別のホスト (この場合は無関係のホスト) からリソースをリクエストすると、このフィルタは、リクエストを Error.jsp に転送し、次のようなメッセージを出力します。

remoteaddr = 10.1.116.192
ホストからのリクエストは認証されませんでした

フィルタを使用した基本的な承認の指定

承認は、アクセス制御リストなどを使用することによって、ユーザーが所定のリソースにアクセスできるようにする処理です。サーブレットエンジンは承認を使用する前にユーザーを認証します。リソース表示の承認がユーザーに与えられていない場合、サーブレットコンテナ (エンジン) はアクセスを許可しません。

フィルタは、リクエストヘッダーへのアクセスを利用して、リクエストがターゲットリソースに送信される前にユーザーに承認が与えられているかどうかを調べます。フィルタを、Web コンテナセキュリティの他のメソッドと組み合わせることによって、基本的なセキュリティシステムを作成できます。

次の手順は、Authorization (承認) ヘッダーを調べるフィルタの設定方法です。ヘッダーが設定されると、ユーザーはログインしてリクエストを続行することができます。ユーザーがログインできない (Authorization リクエストヘッダーが設定されていない) と、フィルタはリクエストをログインページに転送します。

リクエストヘッダーはなりすまされる可能性があるため、この方法は本格的な承認方法ではありません。しかし、セキュリティ戦略全体の一部を担うものとして有効です。

基本的な承認チェックを目的としたフィルタを設定するには

  1. 次のフィルタをコンパイルします。
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    import java.util.*;
    public class AuthFilter extends GenericFilter {
     public void doFilter(ServletRequest request, ServletResponse 
    response, FilterChain chain) throws java.io.IOException, 
    javax.servlet.ServletException {
      HttpServletRequest req = (HttpServletRequest)request;
      String authorization = req.getHeader("Authorization");
      if (authorization != null) {
       System.out.println("承認されたアクセス:" + req.getRemoteUser());
       chain.doFilter(request,response);
      } else {
       System.out.println("承認されなかったアクセス:" + 
    req.getRemoteUser());
       RequestDispatcher rd = request.getRequestDispatcher("/
    login.html");
       rd.forward(request, response);
      }
     }
    }
    
  2. フィルタとマッピングを web.xml ファイルに追加します。次のように指定します。
    <filter> 
     <filter-name>AuthFilter</filter-name>
     <filter-class>jrunsamples.filters.AuthFilter</filter-class> 
    </filter>
    ...
    <filter-mapping> 
     <filter-name>AuthFilter</filter-name>
     <url-pattern>protected_resource_mapping</url-pattern> 
    </filter-mapping>
    
  3. 新規ユーザーを追加し、そのユーザーに <JRun のルートディレクトリ>/server_name/SERVER-INF/jrun-users.xml ファイル内のロールを割り当てます。次に例を示します。
    <user>
     <user-name>nick</user-name>
     <password>danger</password>
    </user>
    <role>
     <role-name>manager</role-name>
     <user-name>nick</user-name>
    </role>
    
  4. web.xml ファイルで、ターゲットリソースに対するセキュリティ制約を追加します。これによって、ユーザーがそのページをリクエストすると、ユーザーにログインをリクエストします。最も簡単なサンプルでは BASIC 認証を使用します。この認証では、クライアントのブラウザが、ユーザー名とパスワードを入力するようにユーザーに要求します。クライアントの入力が認証されると、以後のリクエストの Authorization ヘッダーが設定されます。
  5. JRun サーバを再起動します。
  6. web.xml ファイル内にセキュリティ設定値を追加します。たとえば、次のように指定します。
    <login-config>
      <auth-method>BASIC</auth-method>
     </login-config>
     <security-constraint>
      <web-resource-collection>
       <web-resource-name>Protected Page</web-resource-name>
       <url-pattern>/login.html</url-pattern>
       <http-method>GET</http-method>
       <http-method>POST</http-method>
      </web-resource-collection>
      <auth-constraint>
       <role-name>manager</role-name>
      </auth-constraint>
     </security-constraint>
    

リクエストの変更

フィルタには、リクエストデータを検証して処理中に変更する機能があります。この機能は、フォーム入力を検証したり、リクエスト側クライアントがアクセスできない内部設定を行ったりする場合に便利です。

フィルタ内でリクエストオブジェクトを変更したり、リクエストがターゲットリソースに送られる前にユーザーのステートを変更したりするには、さまざまな方法があります。これらの方法は次のものによって実装されます。

次のセクションでは、これらのメソッドの一部のサンプルについて説明します。

リクエストを変更するためのリクエスト属性の設定

request.setAttribute メソッドを使用すると、フィルタがターゲットリソースにリクエストを渡す前に、リクエストの属性の追加または変更を行うことができます。

また、String オブジェクトだけでなく、他のオブジェクトも属性として設定することができます。フィルタの次のコード抜粋では、リクエストの 2 つの属性、すなわち String オブジェクトと Integer オブジェクトを設定します。

...
String sVeggies = "ブロッコリーとほうれんそう";
Integer x = new Integer(6);
//chain.doFilter() の前に呼び出す必要があります。
request.setAttribute("vegetables",sVeggies);
request.setAttribute("some_number",x);
chain.doFilter(request, response);
...

JSP 内の次の式は、これらの属性の値を取得します。getAttribute は汎用オブジェクトを返します。したがって、String のみを返す request.getParameter メソッドとは異なり、結果を希望のオブジェクトタイプにキャストする必要があります。

次の場合、vegetables 属性の値は String にキャストされ、x は Integer にキャストされます。

<%= (String)request.getAttribute("vegetables") %>
<%
 Integer x = (Integer)request.getAttribute("some_number");
 out.println(x.intValue() * 30);
%>

セッションオブジェクトを使用して複数のリクエストにわたる変数を渡す方法をお勧めします。コントローラサーブレットまたは RequestDispatcher でリクエストを処理しない場合は、リクエストオブジェクトに対して setAttribute を使用する方法は使用しないでください。

セッションの操作

セッションオブジェクトはリクエストオブジェクトよりも大きなスコープを持っています。リクエスト属性は、現在リクエストされているリソースでのみ使用できます。セッション属性は複数のリクエストにわたって継続します。フィルタを使用すると、セッションを作成し、セッションオブジェクト内の値を設定し、クライアントがリクエストするすべてのページからこれらの値にアクセスすることができます。

ターゲットリソースではなく、フィルタ内でセッションオブジェクトを作成し、これにデータを挿入する場合は、Web コンポーネントの作業を分離しておくと便利です。これにより、コードを再利用しやすくなります。これは、Front Controller などのデザインパターン戦略でしばしば使用される手法です。

次のフィルタはセッションオブジェクトを作成し (まだセッションオブジェクトが存在しない場合)、リクエストパラメータの値を名前として保存し、この名前をセッション属性として設定し、チェーン経由でリクエストをターゲットリソースに渡すことによってリクエストの処理を終了します。

public class SessionFilter extends GenericFilter {
 public void doFilter(ServletRequest request, ServletResponse 
response, FilterChain chain) throws java.io.IOException, 
javax.servlet.ServletException {
  //セッションオブジェクトにアクセスできるように、必ず、ServletRequest を 
HttpServletRequest にキャストしてください。
  HttpServletRequest req = (HttpServletRequest)request;
  //新規セッションを作成します。
  HttpSession session = req.getSession(true);
  String name = request.getParameter("name");
  session.setAttribute("name", name);
  //制御をターゲットリソースに渡します。
  chain.doFilter(req, response);
 }
}

文字エンコードの設定

複数の文字セットおよびロケールをサポートしている多言語 Web アプリケーションでは、リクエストの文字エンコードを設定する必要があります。ただし、リクエストパラメータは、コンテナのデフォルトの文字エンコードを使用して解析されることがあります。解析エラーを防ぐには、フィルタを使用してリクエストの文字エンコードを設定してください。

たとえば次のように、リクエストを渡す前に、setCharacterEncoding メソッドを呼び出す必要があります。

...
public void doFilter(ServletRequest request, ServletResponse response, 
FilterChain chain) throws IOException, ServletException {
 request.setCharacterEncoding("ISO-8859-1");
 chain.doFilter(request, response);
}
...

ロケール対応 Web アプリケーションの操作の詳細については、弟 3 章、「国際化対応とローカリゼーション」を参照してください。