JavaSer/javadrive/filter

Last-modified: 2010-10-15 (金) 18:50:09

フィルタ

 サーブレットを呼び出すときに
 「自動で」「先立って」呼び出されるプログラムのこと。
 呼び出し側は気がつかない。多段にフィルタをかけることも可能

 ユーザー認証など、すべてのサーブレットで必ず発生するような
 前処理などに使うとよい。

 フィルタのかけ方は以下の通りで、servletの指定法と似ている。 


 <web-app>
  <filter>
  <filter-name>
  フィルタ名
 </filter-name>
  <filter-class>
  フィルタとして実行されるサーブレットクラス名
  </filter-class>
  </filter>
 </web-app>

 <web-app>
  <filter-mapping>
  <filter-name>
  フィルタ名
  </filter-name>
  <url-pattern>
  URLパターン
  </url-pattern>
  </filter-mapping>
 </web-app>

 
 <filter-mapping>は、ワイルドカードなどを指定することも可能。
 #<servlet-mapping>要素の指定方法と同じ 

 ※注意点としては、現段階では、
  <servlet>や<servlet-mapping>要素よりも前に記述しておく必要がある。 
  #将来的に記述順序は任意になるらしい

フィルタ呼び出し時のメソッド

 フィルタでサーブレットを呼び出すと、doFilterが呼び出される。
 通常のサーブレットのdoGetメソッドやdoPostメソッドに相当する。
 #ただし、第3引数にFilterChainインターフェースが存在するところが異なる

FilterChainの役割

 FilterChainは、次に呼び出すべきサーブレットが何かを保持していて、
  chain.doFilter
 と呼び出すことで、次のサーブレットに移行できる
 (フィルタが呼び出された時もdoFilterなので紛らわしい)

 chain.doFilterを呼び出さないことで、処理の流れをせき止めることも可能。
 認証が行われていれば、サーブレットを引き継ぎ、
 認証が行われていなければ、サーブレットを呼び出さずに、ログインページへ
 リダイレクトするといった処理が可能。

フィルタの多重呼び出し

 フィルタを多重に呼び出すときは、単純に
 <filter>と<filter-mapping>を2つ以上並べるだけ。

フィルタはデフォルトでは、URLがリクエストされたときに呼び出されるが、

 ・フォワードが行われたとき(FORWARD)
 ・インクルードが行われたとき(INCLUDE)
 ・エラーページへ処理が移るとき(ERROR)
 にそれぞれ呼び出すこともできる
 <filter-mapping>要素内で、<dispatcher>要素にオプションを書き込むだけ。
 複数のオプションを設定することも可能

サーブレットのユーザー認証について

 Tomcatでは以下の4つの認証方法が用意されている
 BASIC認証, DIGEST認証, FORM認証, CLIENT-CERT認証
 (サーブレットコンテナによって異なる)

 http://www.javadrive.jp/servlet/auth/では、
 BASIC認証, DIGEST認証, FORM認証についてのみ解説
 
 

レルム

 レルムとは、ユーザー名とパスワードの管理方法のこと。

 UserDatabaseRealm: ユーザー情報をファイルで管理する方法
            本格的な用途にはあまり向いていない

 JDBCRealm: ユーザー情報をデータベースで管理する方法
        本格的な用途にはこちらのほうが適している

 JNDIRealm, DataSourceRealm
 というレルムもあるが、http://www.javadrive.jp/servlet/auth/では解説なし

Basic認証のやり方(ファイル管理でやる方法)

 「role」は認証を与える単位のようなもの。
 ユーザー認証の際にはロール単位でアクセス許可を与える。

 Tomcatインストールフォルダ>conf>tomcat-users.xmlと進み、
 その中に、
 <role rolename="sales"/>
  <user username="yamada" password="yamada" roles="sales"/>
 のように記述する。

 その後、web.xmlに以下のように記述する。


<security-constraint>
<web-resource-collection>
<web-resource-name>User Basic Auth</web-resource-name> #リソース名
<url-pattern>/*</url-pattern> #セキュリティの対象となるサーブレット
</web-resource-collection>
<auth-constraint>
<role-name>sales</role-name> #tomcat-users.xmlに指定したrole名
</auth-constraint>
</security-constraint>

<login-config>
<auth-method>BASIC</auth-method> #Basic認証のときは、BASICと設定
<realm-name>User Basic Auth</realm-name> #レルム名
</login-config>

 #以下は必須ではないが、管理アプリなどが参照するためのロール名の記述
<security-role>
<role-name>sales</role-name>
</security-role>

 #リソース名とレルム名は何に使うのかよくわからなかった

 <auth-constraint>にrole名を追加しておかないと、
 「Basic認証は通ったが、403で拒否される」という状況になる。

Basic認証のやり方(データベースでやる方法)

 (1)DBテーブルを2つ用意する
   ・ユーザ名とパスを含むテーブル(ユーザテーブル)
   ・ユーザ名とロール名を含むテーブル(ロールテーブル)

 ユーザテーブルの例:
 create table user_table(
  user varchar(30), pass varchar(30) );

 ロールテーブルの例:
 create table role_table(
  user varchar(30), role varchar(30) );

 (2)server.xmlの編集
 Tomcatインストールフォルダ>conf>server.xmlと進み、
 以下のような情報を設定


 <Realm className="org.apache.catalina.realm.JDBCRealm"
#Javaクラス名(固定)
driverName="com.mysql.jdbc.Driver"
#MySQLのJDBCドライバのクラス名
connectionURL="jdbc:mysql://localhost/auth"
#DBのURL(getConnectionのときと同じ指定方法)
connectionName="authtest" connectionPassword="authtest"
#DBのユーザ名とパスワード
userTable="user_table" userNameCol="user" userCredCol="pass"
#ユーザテーブル名とユーザ名のカラム名,パスワードのカラム名
userRoleTable="role_table" roleNameCol="role" />
#ロールテーブル名とロール名のカラム名

BASIC認証の欠点とDIGEST認証

 平文でパスワードが流れるため、盗聴に弱い
 DIGESTはユーザ名とパスワードをMD5でハッシュ(ダイジェスト)化して送る

 ユーザーからの見た目は、BASIC認証と変わりがない。
 現在はほとんどのブラウザがDIGEST認証を採用しているので、
 DIGEST認証を使うようにしたほうがよい。

 ※UserDatabaseRealmレルムやJDBCRealmレルムの属性に
  「digest」属性があるが、DIGEST認証とは別のもの。
  この属性はパスワードなどをプレインテキストで保存しないための設定

DIGEST認証の設定法

 web.xmlで以下のように設定するだけ。
 その他の部分は、BASIC認証と同じ
 <login-config>
 <auth-method>DIGEST</auth-method> #ここがBASICだとBasic認証になる
 <realm-name>レルム名</realm-name>
 </login-config>

FORM認証

 FORM認証ではユーザー名とパスワードを入力してもらう画面を
 ダイアログの替わりに自分で作成したHTMLページを指定することが出来る。

 また、HTTPプロトコルの認証を使わず、認証を通った後は
 セッションって認証の管理を行う。有効期限もセッションの設定にしたがう

FORM認証の使い方


 <login-config>
  <auth-method>FORM</auth-method> #Basic認証のときはBASICだった
  <realm-name>レルム名</realm-name>
  <form-login-config>
  <form-login-page>/login.html</form-login-page>
           #ログイン時に表示されるHTMLページ
  <form-error-page>/error.html</form-error-page>
           #ログイン失敗時に表示されるHTMLページ
 </form-login-config>
 </login-config>

 その他は、BASIC認証と設定方法は同じ

 ただし、フォームは以下のようにする


 <form method="POST" action="j_security_check" name="loginform">
 <input type="text" name="j_username" size="16">
 <input type="password" name="j_password" size="16">
 <input type="submit" value="login">
 </form>

 
 1)入力欄には「text」コントロールと「password」コントロールを使う
 2)名前はそれぞれ「j_username」と「j_password」と決まっている
 3)フォームの送信先も「j_security_check」と決まっている
 4)ログインフォームから送られてきたデータの処理は自動化されている
 5)レイアウトは自由だが、フォームに他の項目を追加して、
   情報を取り出すことはできない(4の理由による)

 エラー情報などは送られてこないので、
 エラーページは静的なページしか表示できない。

サーブレット側から認証ユーザーを見る方法

 request.getRemoteUser();
 戻り値として認証に成功したユーザー名を得られる
 request.getUserPrincipal().getName();でも同じ値が得られる
 (使い分け方法はよくわかっていないとのこと)

 request.getAuthType();
 戻り値として、BASIC、FORM、CLIENT_CERT、DIGESTのどれか1つの値
 どの認証方式で認証したかがわかる

 request.isUserInRole("ロール名");
 ユーザがロールに属しているかをチェックできる。
 ロールによって処理を分けるときなどに使える

 

カスタム認証

 Tomcatで標準で用意されている認証は、容易に利用できるが、
 カスタマイズに限界がある。たとえば、ログアウト処理などを利用できない

 カスタム認証といっても特別なものではなく、
 接続はセッション、MySQLとの接続でユーザー名とパスワードの取得、
 認証チェックなどを自前で行うことを指している

 ちなみに、ユーザの入力したページを無理やりログインページに飛ばした場合
 String target = request.getRequestURL();
 session.setAttribute("target", target);
 のようにして、本来アクセスしようとしたURLを
 セッションのtargetに保存しておくとよい。

カスタム認証の設計のおおまかな流れ

1.HttpSession session = request.getSession(false);
  として、session==nullならばまだ認証されていない
  →ログインページへ

  ※ダブルチェックで、認証後にsession.setAttribute("login", "OK");
   のようにセットするとして、session.getAttribute("login");が
   nullか否かをチェックするとよい

2.認証失敗時にsession.setAttribute("status", "Not Auth");
  とセットするとして、
  Object status = session.getAttribute("status");
  status != nullならば、一度認証を失敗していることがわかる。
  →「認証に失敗しました。再度ユーザ名とパスワードを入力してください」
    のような画面を表示する

3.認証の仕方は特にサンプルのページでは触れられていない
  (とりあえず、nullでなければログインするようにしている)

4.認証が成功したら、
  String target = (String)session.getAttribute("target");
  response.sendRedirect(target);
  として、本来のアクセス先へ飛ばす

5.DBから正しいユーザ名とパスワードを取得する場合は、


  String user = request.getParameter("user");
  String pass = request.getParameter("pass");

  String sql = "SELECT * FROM user_table WHERE user = ? && pass = ?";
  PreparedStatement pstmt = conn.prepareStatement(sql);

  pstmt.setString(1, user);
  pstmt.setString(2, pass);
  ResultSet rs = pstmt.executeQuery();

  のようにして、データベースから取り出して比較する。
  (一致するレコードがあるかを検索して、あれば認証する)

6.ログアウト処理

  セッションを無効にすればよいだけ。

  HttpSession session = request.getSession(true);
  session.invalidate();

  response.sendRedirect("/auth/Login");

 [getSession]
 trueを指定すると、セッションがすでに開始されていればそのセッションを返し、
 開始されてなければ新規にセッションを作成する。
 falseを指定するとセッションが存在しない場合はnullを返す。

7.認証が通っているかどうかの判別はフィルタを利用するとよい。

  難しいことはないが、

  doGet(HttpServletRequest request, HttpServletResponse response)
  doFilter(ServletRequest request, ServletResponse response, ...

  のように、同じrequestとresponseでもクラス名が違うことに注意。
  →キャストして使えばOK

  フィルタ用サーブレットにおけるリダイレクトの例)
  ((HttpServletResponse)response).sendRedirect("/auth/Login");

スケジュール管理ツール作成例

 http://www.javadrive.jp/servlet/schedule/