フィルタでは、リクエストオブジェクトのヘッダーまたは ServletContext を直接処理できます。ServletRequestWrapper クラスまたは HttpServletRequestWrapper クラスを拡張するオブジェクト内にリクエストをラップすることによって、リクエストメソッドをオーバーライドすることもできます。ラッパークラスの使用方法の詳細については、 「ラッパーの使用」 を参照してください。
このセクションでは、リクエストの一般的な処理方法について説明します。
リクエストオブジェクトには、フィルタがさまざまな方法で使用できるヘッダー情報が含まれています。これらのヘッダーはクライアントブラウザによって生成されます。リクエストヘッダーを調べることによって、フィルタは次のタスクを実行できます。
フィルタの一般的なタスクは HTTP リクエストおよびレスポンスヘッダーをログファイルや標準出力に出力することです。この機能はリクエストおよびレスポンスのデバッグおよび解析に役立ちます。
FilterConfig インターフェイスを使用して ServletContext を取得します。
getHeader および getHeaderNames メソッドにアクセスするために、ServletRequest および ServletResponse オブジェクトを、HttpServletRequest およびHttpServletResponse オブジェクトとしてキャストします。そのため、フィルタは、javax.servlet.*.だけでなく、javax.servlet.http.* もインポートする必要があります。java.util.* をインポートする必要があります。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 ヘッダーを調べて、ブラウザのタイプを検出し、非互換ブラウザを転送することができます。ヘッダーに直接アクセスするには、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 リクエストヘッダーが設定されていない) と、フィルタはリクエストをログインページに転送します。
リクエストヘッダーはなりすまされる可能性があるため、この方法は本格的な承認方法ではありません。しかし、セキュリティ戦略全体の一部を担うものとして有効です。
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);
}
}
}
<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>
<user> <user-name>nick</user-name> <password>danger</password> </user> <role> <role-name>manager</role-name> <user-name>nick</user-name> </role>
<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 メソッド
次のセクションでは、これらのメソッドの一部のサンプルについて説明します。
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 章、「国際化対応とローカリゼーション」を参照してください。