フィルタ
サーブレットを呼び出すときに
「自動で」「先立って」呼び出されるプログラムのこと。
呼び出し側は気がつかない。多段にフィルタをかけることも可能
ユーザー認証など、すべてのサーブレットで必ず発生するような
前処理などに使うとよい。
フィルタのかけ方は以下の通りで、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
connectionURL="jdbc:mysql://localhost/auth"
#DB
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");