Apache Tomcat 逆引き辞典 戻る
目次 目的、対象など

Apache Tomcat(以下Tomcat) にはさまざまな機能が用意されています。主にserver.xmlやweb.xmlを変更することによって利用できる機能を「逆引き辞典」的に紹介します。Tomcatを使いなれているが、もう一段階上の使い方を学びたい方の参考になれば良いと思っています。またここに記述されている機能はTomcatでしか利用できない機能ではないため、他のサーブレットコンテナでも利用できるものもあります。

また文中に「TOMCATホーム」が登場しますが、これはTomcatをインストールしたディレクトリを指します。必要に応じ読み替えて下さい。文中に Tomcat5 といった記述があります。こちらは「Tomcatのバージョン5で動作確認した」という意になります。

アプリケーションを新規に追加する
server.xml編
Tomcat4.1 Tomcat5 Tomcat6 Tomcat7 Tomcat8

Tomcatに自身の作成したアプリケーションを登録するには以下のように行います。

以下の例ではルートディレクトリ直下にある「hogedirディレクトリ」をhogeという名称で登録しています。つまり「http://サーバ名:8080/hoge/index.jsp」は「/hogedir/index.jsp」を指し示すことになります。

server.xmlの例(解説に不要な部分は省略) <?xml version="1.0" encoding="UTF-8"?> <Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost" appBase="webapps"> <Context path="hoge" docBase="/hogedir"></Context> </Host> </Engine> </Service> </Server>
Contextファイル編
Tomcat4.1 Tomcat5 Tomcat6 Tomcat7 Tomcat8

上記と異なりContextタグを別ファイルにすることができます。別ファイルにするには「pathの値がファイル名になる」というようにファイル化します。つまりファイル名がpath属性を表すためpath属性は削除して下さい。

このファイルは通常「TOMCATホーム/conf/Catalina/localhost/」の中に配置して下さい。

Contextタグを別ファイルにする(hoge.xml) <Context path="hoge" docBase="/hogedir"></Context>

このサイトでは、ここで作成したhoge.xmlをContextファイルと呼ぶことにします。

path="foo/bar"のように階層化したい

表題の通りpathに階層をもたせる場合、ファイル名に/(スラッシュ)を含めることができないためファイル化できません。上記の場合「foo#bar.xml」とすることで別ファイルにすることができます。

データベース関連
コネクションプールを利用する
Tomcat4.1 Tomcat5 Tomcat6 Tomcat7 Tomcat8

JDBCを用いて接続する場合、接続に時間が掛かる(可能性がある)ため、コネクションプールを利用します。以下の設定で接続することを想定し設定してみます。また文中にResourceタグのname属性の値は自由に命名することができます。今回は「jdbc/ds」としました。

No. 項目名
1 JDBCドライバ com.mysql.jdbc.Driver
2 接続URL jdbc:mysql://localhost:3306/データベース名
3 接続ユーザ名 yuzamei
4 接続パスワード pasuwado
Contextファイルの例 <Context> <Resource name="jdbc/ds" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/データベース名" username="yuzamei" password="pasuwado" validationQuery="select 1" maxActive="20" maxIdle="10" /> </Context>
利用例(hoge.jsp) <%@ page import="java.sql.Connection" %> <%@ page import="java.sql.SQLException" %> <%@ page import="javax.naming.Context" %> <%@ page import="javax.naming.InitialContext" %> <%@ page import="javax.sql.DataSource" %> <html> <body> <% Connection conn = null; try { Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx .lookup("java:comp/env/jdbc/ds"); conn = ds.getConnection(); // 何かしらのSQL処理 } catch (Exception e) { // 適切な処理を記述すること } finally { if (conn != null) { try { conn.close(); } catch (SQLException e) { // 適切な処理を記述すること } } } %> </body> </html>
validationQuery

validationQuery="select 1"とありますが、これはなんでしょうか。コネクションプールではデータベースと接続状態を維持し続けます。接続し続けるたいとTomcat側が思っても、データベースによっては長時間の接続を許さないものもあります。するとTomcatは接続し続けているつもりでも実際に切断されてしまうことがあります。

このようなときに接続切れかどうか判断するためのSQLをvalidationQueryに記述します。ですがvalidationQueryはコネクションプールからコネクションを貸し出す度に実行されるのでデータベースにとって最小限の負担にしたいのです。よって"select 1"が一般的な設定値になります。

またContextファイルでなくserver.xmlに設定することもできます。以下はserver.xmlへの設定例です。

server.xmlへの設定例1 <?xml version="1.0" encoding="utf-8"?> <Server port="8005" shutdown="SHUTDOWN"> <GlobalNamingResources> <!-- Resourceの詳細設定は省略します(詳細はコチラ) --> <Resource name="jdbc/ds" /> </GlobalNamingResources> <Service name="Catalina"> </Service> </Server>
server.xmlへの設定例2 <?xml version="1.0" encoding="UTF-8"?> <Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost" appBase="webapps"> <Context path="hoge" docBase="/hogedir"> <!-- Resourceの詳細設定は省略します(詳細はコチラ) --> <Resource name="jdbc/ds" /> </Context> </Host> </Engine> </Service> </Server>
Resourceタグはどこに記述するのが正しいの?

Resourceタグはどこに記述するのが正しいのでしょうか。筆者が頻繁に見かける箇所をピックアップしてみます。

  • Server > GlobalNamingResources > Resource
  • Server > Service > Engine > Host > Resource
  • Server > Service > Engine > Host > Context > Resource

まずは理解しやすい下2つを比較してみます。これはResourceタグの設定がHostタグ配下で利用できるのかContextタグ内のみで利用できるのかの違いがあります。

下記を参照下さい。Hostタグの内側にContextが2つ設定されており、それぞれのContextタグ内にResourceタグが別々に設定されています。またContextタグと同一レベルでResourceタグ(jdbc/cmn)が設定されています。

このような設定の際、hogeアプリケーションでは「jdbc/cmn」「jdbc/hoge」が利用可能、foobarアプリケーションでは「jdbc/cmn」「jdbc/foobar」が利用可能です。このことを考えると「XXXに記述するのが正しい」ということはなく「アプリケーションの使い方による」ということが結論になります。

複数のResourceタグの設定例 <?xml version="1.0" encoding="UTF-8"?> <Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost" appBase="webapps"> <Resource name="jdbc/cmn" /> <Context path="hoge"> <Resource name="jdbc/hoge" /> </Context> <Context path="foobar"> <Resource name="jdbc/foobar" /> </Context> </Host> </Engine> </Service> </Server>
コネクタ関連

Tomcatではブラウザと直接やりとりしたり、Apache等のWEBサーバと連携することができます。この機能を担っているのがコネクタになります。

ポート番号8080を省略したい
Tomcat4.1 Tomcat5 Tomcat6 Tomcat7 Tomcat8

実装中などポート番号の入力を面倒に思ったことはありませんか。このようなときにはTomcatで標準的に動作しているコネクタのポート番号を変更します。

下記はインストール直後の設定なのでポート番号が8080になっています。この番号を80に変更することで今後はポート番号を省略することができます。

コネクタのポート番号 <?xml version="1.0" encoding="UTF-8"?> <Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> </Service> </Server>
Apacheと連携したい
Tomcat5 Tomcat6 Tomcat7 Tomcat8

上記の「ポート番号8080を省略したい」にある「protocol="HTTP/1.1"」はHTTP/1.1という方式で送信されたものを処理するコネクタという意味になります。つまり外部のブラウザとやりとりをするコネクタということの意です。

Apacheとやりとりするということは、Apacheとの専用コネクタを設け、そのコネクタでやりとりすることになります。

Apacheとやりとりするには一般的にAJPを利用します。下記例ではAJP/1.3を利用することを意味します。つまりAJP/1.3でTomcatにアクセスするとポート番号8009を使うという意になります。Apache側の設定は別の機会に紹介します。

Apacheと連携 <?xml version="1.0" encoding="UTF-8"?> <Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> </Service> </Server>
どちらのコネクタを利用するのか

ここでは「HTTP/1.1」「AJP/1.3」用の2つのコネクタの設定を学びました。ではどちらのコネクタを利用するのがよいのでしょうか。

筆者推奨では実装時(開発時)はApacheと連携するのが手間になるので「HTTP/1.1」で80ポートで利用、本番や検証環境など実稼働に近い場合は「AJP/1.3」を利用します。

つまり「使用していないコネクタはコメント化し利用しない」ということです。Apacheと連携するのにブラウザと直接やりとりする「HTTP/1.1」のコネクタが動作していてはApacheと連携する意味がありません。微妙ではありますが省メモリでもあります。不要なものは起動しないよう設定しましょう。

GETメソッドで日本語を送信すると文字化けする
Tomcat5 Tomcat6 Tomcat7 Tomcat8

文字化け問題は頻繁に耳にします。よくある対応として以下のコードを見かけます。

リクエストはUTF-8で送信された request.setCharacterEncoding("UTF-8");

しかし上記だけでは不足している場合があります。コネクタに以下の様な設定を加えて下さい。

コネクタのポート番号 <?xml version="1.0" encoding="UTF-8"?> <Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" useBodyEncodingForURI="true" /> </Service> </Server>

これはTomcat5系以降、FORMのGETメソッドでパラメータを送信した場合、setCharacterEncodingメソッドを無視するようになったためです(POSTはこの値に関係なく有効)。

認証関連

TomcatにはRealmという認証で利用できる機能があります。こちらを利用してみます。一般的に認証というと「認証に必要な情報を入力する」「入力された情報を利用し実際の認証を行う」の2つに分類されると思います。

簡単な認証

ここでは「認証に必要な情報を入力する」は「ブラウザが用意している認証画面」、「入力された情報を利用し実際の認証を行う(Realmと呼びます)」は「ユーザ名、パスワードが記載されているファイル」を利用します。

ブラウザが用意している認証画面
Tomcat4.1 Tomcat5 Tomcat6 Tomcat7 Tomcat8

「ブラウザが用意している認証画面」はBASIC認証と呼ばれています(少なくともココではそのように呼びます)。BASIC認証を行うためにはweb.xmlに以下の変更を加えます。

BASIC認証の設定例 <?xml version="1.0" encoding="UTF-8"?> <web-app> <login-config> <auth-method>BASIC</auth-method> </login-config> </web-app>
ユーザ名、パスワードが記載されているファイル
Tomcat4.1 Tomcat5 Tomcat6 Tomcat7 Tomcat8

こちらはTomcatをインストールすると既に用意されているファイル(TOMCATホーム/conf/tomcat-users.xml)を利用します。

tomcat-users.xml <?xml version="1.0" encoding="UTF-8"?> <tomcat-users version="1.0"> <role rolename="tomcat"/> <role rolename="admin"/> <user username="foo" password="tomcat1" roles="tomcat"/> <user username="bar" password="tomcat2" roles="admin"/> <user username="both" password="tomcat3" roles="tomcat,admin"/> </tomcat-users>

ここでは2つのことが定義されています。1つ目は権限名です。roleタグがこれに該当します。ここでいう権限名は自由に決めることができます。ここでは「tomcat権限」「admin権限」の2つを定義したことになります。

2つ目はユーザとパスワードです。userタグがこれに該当します。username属性とpassword属性がこれに当たります。最後にあるroles属性がこのユーザが所有する権利を表します。

roles属性にはroleタグで定義した権限名を記述することができます。rolesという名称から想像できる通り複数の権限をユーザに与えることができます。つまりbothユーザはtomcat権限とadmin権限の双方を与えられたことを意味します。

次に上記ファイルを利用する設定になります。server.xmlを以下のように設定して下さい(通常は設定せずともアンコメントされているはずです)。

server.xml <?xml version="1.0" encoding="UTF-8"?> <Server port="8005" shutdown="SHUTDOWN"> <GlobalNamingResources> <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" /> </GlobalNamingResources> <Service name="Catalina"> <Engine name="Catalina" defaultHost="localhost"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> <Host name="localhost" appBase="webapps"></Host> </Engine> </Service> </Server>
Realmは各アプリケーションで共通なの?

RealmもResourceと同様に各要素の中に含めることができます。記述箇所により有効範囲(利用することができる範囲)が決まります。アプリケーションの設計により、正しい箇所に記述しましょう。

どのURLを要認証とするか
Tomcat4.1 Tomcat5 Tomcat6 Tomcat7 Tomcat8

今回は以下の認証ルールを設定します。

  • URLに「http://サーバ名/アプリ名/admin/」が含まれていたら、admin権限を必要とする。
  • URLに「http://サーバ名/アプリ名/auth/」が含まれていたら、権限は必要ないが認証のみ必要とする。
  • URLに「http://サーバ名/アプリ名/guest/」といったように、上記に該当しない場合は、権限も認証も必要ないものとする。

まずはserver.xmlの設定です。権限なし(認証のみ)の設定を行わない場合は、この設定は不要です。権限なしを利用するにはRealmタグのallRolesMode属性にauthOnlyを設定します。

次にweb.xmlの設定です。まずはこのweb.xml内で利用する権限を定義します。これは「web-app > security-role > role-name」に記述します。今回はadmin権限を定義するので<role-name>admin</role-name>とします。

最後にURLと必要な権限を設定します。「web-app > security-constraint」に記述します。URLは「web-app > security-constraint > web-resource-collection > url-pattern」に記述します。*(アスタリスク)は利用できますが先頭もしくは末尾にしか利用できません。このURLに必要な権限の設定は「web-app > security-constraint > auth-constraint > role-name」に記述します。

「url-pattern」と「role-name」は複数記述することが可能です。role-nameに*(アスタリスク)が設定されていますが、こちらは権限なし(認証のみ)の場合の設定です。上記でふれましたが*(アスタリスク)を利用する場合はserver.xmlのRealmに追加が必要です。

server.xmlに権限なし(認証のみ)の設定を加える <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase" allRolesMode="authOnly"/>
web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app> <security-constraint> <web-resource-collection> <url-pattern>/admin/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <url-pattern>/auth/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>*</role-name> </auth-constraint> </security-constraint> <security-role> <role-name>admin</role-name> </security-role> </web-app>
JavaでログインユーザIDを取得

Realm機能によってログイン時に使用したユーザIDを取得したい場合は以下のコードで取得できます。

ログインユーザを取得 request.getRemoteUser();

他にもログインユーザ関連のメソッドを紹介します。

No. シグネチャ 戻り値 概要
1 request.getRemoteUser(); String ログインに使用したユーザIDを取得
2 request.isUserInRole(権限名) boolean パラメータで渡した権限を所有しているか判定
3 request.getUserPrincipal(); Principal ログインユーザに関する詳細情報(Principal)を取得
自作HTMLを認証情報入力画面にする
Tomcat4.1 Tomcat5 Tomcat6 Tomcat7 Tomcat8

上記の「簡単な認証」ではなく自作のHTML画面を認証に用いることができます。FORM認証と呼ばれています(少なくともココではそのように呼びます)。

FORM認証を行うためには、上記のBASIC認証の箇所を以下のように変更します。認証情報を入力するwelcome.jsp、認証失敗時に表示するfail.jspとし、以下のように定義しました。

FORM認証 <login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/WEB-INF/welcome.jsp</form-login-page> <form-error-page>/WEB-INF/fail.jsp</form-error-page> </form-login-config> </login-config>

自作HTMLには以下のルールで記述します。

  • 送信先(formタグのaction属性)はj_security_checkとする。
  • ユーザIDはj_usernameというnameで送信する。
  • パスワードはj_passwordというnameで送信する。
自作HTMLの例 <html> <body> <form method="post" action="j_security_check"> ID:<input type="text" name="j_username"><br/> PW:<input type="password" name="j_password"><br/> <input type="submit"> </form> </body> </html>
認証失敗時のページは元のページに戻れないの?

上記のfail.jspをwelcome.jspにしてしまうと認証失敗理由等が表示できません。このような場合、筆者は以下のようにしています。

FORM認証 <login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/WEB-INF/welcome.jsp</form-login-page> <form-error-page>/WEB-INF/welcome.jsp?message=401</form-error-page> </form-login-config> </login-config>
自作HTMLの例 <html> <body> <% if ("401".equals(request.getParameter("message"))) { %><li>ID または PW が違います。</li><% } %> <form method="post" action="j_security_check"> ID:<input type="text" name="j_username"><br/> PW:<input type="password" name="j_password"><br/> <input type="submit"> </form> </body> </html>
認証情報をDBから取得する
Tomcat4.1 Tomcat5 Tomcat6 Tomcat7 Tomcat8

ファイルに認証情報を記述するのは、いささか不便(筆者の感想です)であり、データベースで管理したいと思うのは自然の流れかと思います。

データベースで認証情報を管理する方法はいくつかあるのですが、今回は「DataSourceレルム」を利用します。名称のとおりDataSourceを用いた認証ということになります。

DataSourceレルムではテーブルを利用します。ユーザIDとパスワード、ユーザIDと権限を保存するテーブルを用意します。実際のデータは上記の「tomcat-users.xml」と同一データをテーブルに登録してみます。

auth_tbl 定義
カラム名 その他
id varchar(20) ユーザID,PK
pass varchar(20) パスワード
auth_tbl データ
No. id pass
1 foo tomcat1
2 bar tomcat2
3 both tomcat3
role_tbl 定義
カラム名 その他
id varchar(20) ユーザID,PK
role varchar(20) 権限,PK
role_tbl データ
No. id role
1 foo tomcat
2 bar admin
3 both tomcat
4 both admin

上記テーブルと実データを用いてDataSourceレルムを利用します。

DataSourceレルムの設定例(server.xml) <?xml version="1.0" encoding="utf-8"?> <Server port="8005" shutdown="SHUTDOWN"> <GlobalNamingResources> <!-- Resourceの詳細設定は省略します(詳細はコチラ) --> <Resource name="jdbc/realm" /> </GlobalNamingResources> <Service name="Catalina"> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost" appBase="webapps"> <Realm className="org.apache.catalina.realm.DataSourceRealm" allRolesMode="authOnly" dataSourceName="jdbc/realm" userTable="auth_tbl" userNameCol="id" userCredCol="pass" userRoleTable="role_tbl" roleNameCol="role" > </Host> </Engine> </Service> </Server>
属性の解説
属性名 今回の具体値 解説
userTable auth_tbl ユーザID,パスワードを含んでいるテーブル名
userNameCol id ユーザIDのカラム名
userCredCol pass パスワードのカラム名
userRoleTable role_tbl ユーザID,権限を含んでいるテーブル名
roleNameCol both 権限のカラム名

ここで注意点として、userRoleTable属性のテーブル内のユーザIDカラム名は指定できないことです。userNameCol属性で指定したカラム名になります。

権限テーブルのユーザIDカラム名は指定できない?

このクラスを利用する限り現時点では指定できないようです。では仮に権限テーブルのカラム名が以下の様な場合はどのように対処しましょう。

role_tbl 定義
カラム名 その他
user varchar(20) ユーザID,PK
role varchar(20) 権限,PK

容易な対処法としてはデータベースでVIEWを利用することです。VIEWでuserカラムの名称をidに変えてしまえばよいのです。他に以下の様なこともできるようです。

VIEWのようなもので対応 userRoleTable="(select user as id, role from role_tbl) tmp"

ただしこの設定例は現在の実装で動作することは確認していますが、将来的な実装がどのようになるかはわかりません。VIEWで対応するのがスマートです。

権限テーブルに複数の権限をINSERTするのは面倒

全てのユーザIDに対しauthed権限を持たせることにしましょう。すると全てのユーザと同じ件数だけ権限テーブルに権限authedのレコードが必要になります。

このような場合、上記「権限テーブルのユーザIDカラム名」ような発想で乗り越えましょう。

全ユーザにauthed権限を持たせるSQL userRoleTable="(select id, role from role_tbl union all select id, 'authed' from auth_tbl) tmp" (実際には1行)
権限を詳細に分割するのは面倒

あるサイトで「参照」「更新」「印刷」という権限があるとします。この場合上記テーブルには1ユーザあたり最大3レコードINSERTしなければなりません。

いわゆるスーパーユーザ(機能制限されることのないユーザの意)を登録する場合、現在は3レコードで済みますが、権限を拡張されると難しいです。どのように対応しましょう。以下のテーブル構造で対応できそうです。

auth_tbl 定義
カラム名 その他
id varchar(20) ユーザID,PK
pass varchar(20) パスワード
role_id varchar(20) 権限ID
func_tbl 定義
カラム名 その他
role_id varchar(20) 権限ID,PK
func_id varchar(20) 機能ID,PK
auth_tbl データ
No. id pass role_id
1 foo tomcat1 view
2 bar tomcat2 edit
3 both tomcat3 super
func_tbl データ
No. role_id func_id
1 view view
2 edit edit
3 super view
4 super edit
5 super print
スーパーユーザ対応SQL userRoleTable="(select id, func_id as role from auth_table a join func_tbl f on a.role_id = f.role_id) tmp" (実際には1行)
Single Sign On の設定
Tomcat5 Tomcat6 Tomcat7 Tomcat8

1つのTomcat内で複数のWEBアプリが動作しており、全てのアプリでRealmでの認証を行っている場合の、アプリ間でSingle Sign Onする設定。

ただし同一のTomcat内であってもバーチャルドメイン等、別ドメインのものは再認証となる。

SingleSignOnの設定例(server.xml) <?xml version="1.0" encoding="UTF-8"?> <Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost" appBase="webapps"> <Valve className="org.apache.catalina.authenticator.SingleSignOn" /> </Host> </Engine> </Service> </Server>
パスワードの暗号化
Tomcat5 Tomcat6 Tomcat7 Tomcat8

Realmを用いる際に以下のようにdigest属性を用いることで、画面にて入力されたパスワードを暗号化し認証させることができる。

利用できる暗号方法は「MD5」「SHA」の2つ。ただし「BASIC」「DIGEST」「FORM」「CLIENT-CERT」のうち「DIGEST」では期待通りの動作をしないようだ。

Realm使用時に暗号化する設定の例 <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase" digest="MD5" /> <Realm className="org.apache.catalina.realm.DataSourceRealm" dataSourceName="jdbc/XXXX" userTable="users" userNameCol="id" userCredCol="password" userRoleTable="roles" roleNameCol="role" digest="SHA" />
Tomcat6 Tomcat7 Tomcat8
テキスト文字を暗号化する方法

TOMCAT_HOME/lib/catalina.jarに暗号化できるツールが存在します。UserDatabaseRealmの際、暗号化する方法がない場合、以下を参考に利用して下さい。(要ダウンロード:tomcat-juli.jarの「JULI log4j jar」)

コマンド・プロンプトより実行 Microsoft Windows [Version 6.3.9600] (c) 2013 Microsoft Corporation. All rights reserved. C:\Users\username>cd %TOMCAT_HOME%\lib C:\TOMCAT_HOME\lib>java -cp tomcat-util.jar; tomcat-juli.jar; tomcat-util-scan.jar; servlet-api.jar; catalina.jar org.apache.catalina.realm.RealmBase -a MD5 abc (実際のコマンドは1行) abc:900150983cd24fb0d6963f7d28e17f72 C:\TOMCAT_HOME\lib>
カスタムAuthenticatorに変更する
Tomcat8

どのバージョンからこの機能が有効かはわかりかねます。上記にあるものは確認がとれたバージョンになります。

自作HTMLを認証情報入力画面にする」ではFORMを用いて自作HTMLを利用しました。自作HTMLでは「j_username」「j_password」と2つのデータしか送信できませんでした。

たとえば「会社ID」「社員ID」「パスワード」のように送信する値を3つに変更。といった場合に認証情報を取得するクラスを変更することができます。ここではクラスを自作せずクラスを変更する方法を紹介します。

まずはカスタムクラスを作成します。今回は「CustomAuthenticator」というクラス名にしました。このクラスの親クラスは「catalina.jar」をクラスパスに追加することでコンパイルできるようになります。

CustomAuthenticator.java package jp.co.mclnet.tomcat8ex; import org.apache.catalina.authenticator.FormAuthenticator; public class CustomAuthenticator extends FormAuthenticator { }

つぎに「Authenticators.properties」ファイルを新規作成します。このファイルは「TOMCATホーム/lib/org/apache/catalina/startup/Authenticators.properties」になるよう配置します。

ファイルの内容ですが、今回は「CUSTOM」という名称で追加します。ファイルの内容は以下を参考にしてください。

Authenticators.properties CUSTOM=jp.co.mclnet.tomcat8ex.CustomAuthenticator

最後に「web.xml」ファイルを設定します。

web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app> <login-config> <auth-method>CUSTOM</auth-method> </login-config> </web-app>
この変更以外には?

ここに記述した方法では値を受け取る方法が変更できるのみなので、上記で設定した以下のRealm要素の変更もたいていの場合必要です(以下のものは変更していません)。

Realm設定 <Realm className="org.apache.catalina.realm.DataSourceRealm"/>
セッション関連
セッションデータの保存先をJDBCに
Tomcatの環境変数 -Dorg.apache.catalina.session.StandardSession.ACTIVITY_CHECK=true"
MySQLでのCREATEテーブル文 CREATE TABLE session_table ( session_id VARCHAR(100) NOT NULL, session_valid CHAR(1) NOT NULL, session_max_inactive INT NOT NULL, session_last_accessed BIGINT NOT NULL, session_app VARCHAR(255) DEFAULT NULL, session_data MEDIUMBLOB, PRIMARY KEY (session_id), KEY kapp_name (session_app) );
Contextの設定例 <Context> <Manager className="org.apache.catalina.session.PersistentManager" maxIdleBackup="10" > <Store className="org.apache.catalina.session.JDBCStore" connectionName="username" connectionPassword="password" connectionURL="jdbc:mysql://localhost:3306/databasename" driverName="com.mysql.jdbc.Driver" sessionTable="session_table" sessionIdCol="session_id" sessionValidCol="session_valid" sessionMaxInactiveCol="session_max_inactive" sessionLastAccessedCol="session_last_accessed" sessionAppCol="session_app" sessionDataCol="session_data" /> </Manager> </Context>
登録されるデータサンプル
session_id 6AFCADC23D18C9DE36975B6EF5B4EF63
session_valid 1
session_max_inactive 1800
session_last_accessed 1445395062842
session_app /Catalina/localhost/appname
session_data Binary
Tomcat8

どのバージョンからこの機能が有効かはわかりかねます。上記にあるものは確認がとれたバージョンになります。

MySQLで動作確認しました(必要に応じ他のデータベースでお試し下さい)。テーブルを作成し、Tomcatの環境変数を変更、Contextの設定と主に3箇所対応しました。JDBCStoreの他にFileStoreというものもあるそうです。

セッションデータの保存先をDynamoDBに
Tomcat8

どのバージョンからこの機能が有効かはわかりかねます。上記にあるものは確認がとれたバージョンになります。

JDBCにセッション情報を保存するとデータベースに負荷がとてもかかるため、KeyValueStoreにデータを保存したいと思います。AWS DynamoDB に保存してみたいと思います。

JARのダウンロード

AWS SDK」と「Tomcat向けDynamoDBセッション管理」をダウンロードします。ダウンロードしたファイルは「TOMCATホーム/lib/」に保存します。

設定

コンテキストの設定を行います。必要に応じ設定値を変更して下さい。この設定にて動作させるとDynamoDB内にTomcat_SessionStateテーブルが自動で作成されます。テーブル名を指定する場合はtable属性で自由に名称を設定下さい。

Contextの設定例 <?xml version="1.0" encoding="UTF-8"?> <Context backgroundProcessorDelay="1"> <WatchedResource>WEB-INF/web.xml</WatchedResource> <Manager className="com.amazonaws.services.dynamodb.sessionmanager.DynamoDBSessionManager" regionId="ap-northeast-1" endpoint="dynamodb.ap-northeast-1.amazonaws.com" createIfNotExist="true" saveOnRestart="true" processExpiresFrequency="1" awsAccessKey="YourAWSAccessKey" awsSecretKey="YourAWSSecretKey" /> </Context>
タイミング

DynamoDBSessionManagerクラスはTomcatのPersistentManagerBaseを継承しています。#setMaxIdleBackupやprocessExpiresFrequencyを読む限り、どんなに小さな値に設定しても、最小でも1秒後にセッションの永続化をするようです(上記のJDBCStoreも同様と思われる)。

この方法ではラウンドロビンの状態で連打されると対応できません。そのような意味ではセッションの管理はスティッキーセッションにし、被害を最小限にする必要があるようです。

上記の1秒に関係なく、saveOnRestartをtrueに設定することによって、ロードバランサー配下のインスタンスに障害が発生した場合、セッションのフェイルオーバーが行われるようです。

DynamoDB Local

endpointの指定ができるため「http://localhost:8000」を指定することで利用できます。

エラーや例外発生時にエラーページに遷移
Tomcat4.1 Tomcat5 Tomcat6 Tomcat7 Tomcat8
エラーページに遷移させる例 <?xml version="1.0" encoding="UTF-8"?> <web-app> <error-page> <error-code>403</error-code> <location>/WEB-INF/jsp/error.jsp?error=403</location> </error-page> <error-page> <exception-type>com.hogehoge.ForbiddenException</exception-type> <location>/WEB-INF/jsp/error.jsp?error=403</location> </error-page> </web-app>

HTTPレスポンスコードが200(Successful)以外であったり、例外がthrowされServlet内でcatchされずTomcatまで投げられた場合、error-pageタグを用いてエラーページに遷移させることができます。

HTTPレスポンスコードによってエラーページに遷移させるにはerror-codeタグ、例外によってエラーページに遷移させるにはexception-typeタグを利用します。エラーページの遷移先はlocationタグに記述します。

またerror-codeタグとexception-typeタグは両立できませんので、別々に定義する必要があります。

期待通りの画面が表示されない

上記ではHTTPレスポンスコードが403の場合、error.jspを表示する設定になっています。この場合のHTMLのタグとして正しく(htmlタグやbodyタグを書くという意)記述して下さい。ブラウザによってはレスポンスボディが期待通り表示されないことがあるようです。

正しいerror.jsp <html> <head> <title>OK</title> </head> <body> エラーが発生しました。 </body> </html>
誤ったerror.jsp エラーが発生しました。
throwされたExceptionを利用したい

上記設定ですと「com.hogehoge.ForbiddenException」がthrowされたら「/WEB-INF/jsp/error.jsp?error=403」が表示されます。しかしこのような例外をerror.jspに遷移させる設定が複数存在する場合、どの例外がthrowされたか判別できないと、その後のハンドリングができません。

このような場合「request.getAttribute("javax.servlet.error.exception")」にてthrowされた例外を取得することができます。

JSPページに文字コードを指定したい
Tomcat5 Tomcat6 Tomcat7 Tomcat8
JSPページの文字コード指定の例 <%@ page contentType="text/html; charset=UTF-8" %>
全ページに文字コード指定 <?xml version="1.0" encoding="UTF-8"?> <web-app> <jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <url-pattern>*.html</url-pattern> <page-encoding>UTF-8</page-encoding> </jsp-property-group> </jsp-config> </web-app>

たいていのサイトではJSPに利用する文字コードをページ毎にわけたりせず、同一文字コードを利用していると思います。このような場合、各ページに「JSPページの文字コード指定の例」のようなコードを書くと思います。しかしこの方法では労力もかかりますし、漏れも起こりえます。

このような場合、「全ページに文字コード指定」を行うことで、該当ページ全てに上記と同様のことを設定したことになります。今回例ではjspとhtmlファイルの文字コードを指定しました。

url-patternタグは複数記述することができます。url-patternタグの値は*(アスタリスク)は利用できますが先頭もしくは末尾にしか利用できません。

全てのファイルに同じファイルをincludeさせたい
Tomcat5 Tomcat6 Tomcat7 Tomcat8
タグライブラリの指定例 <%@ taglib prefix="html" uri="/tags/struts-html.tld" %>
全ページにincludeの設定例 <?xml version="1.0" encoding="UTF-8"?> <web-app> <jsp-config> <jsp-property-group> <url-pattern>/WEB-INF/jsp/body/*</url-pattern> <include-prelude>/WEB-INF/jsp/tablib.jsp</include-prelude> <include-prelude>/WEB-INF/jsp/header.jsp</include-prelude> <include-coda>/WEB-INF/jsp/footer.jsp</include-coda> </jsp-property-group> </jsp-config> </web-app>

たいていのサイトではWEBページにヘッダやフッタが存在します。また直接表示されませんがタグライブラリの指定など、必ず読み込ませたいファイルがあると思います。

このような場合、以下の対応をすることで該当ページ全てにインクルードしたことになります。今回例では「/WEB-INF/jsp/body/*」に該当するファイル全ての先頭にtablib.jsp,header.jspの両ファイルを、末尾にfooter.jspファイルをincludeしています。

include-preludeタグ、include-codaタグは複数記述することができます。記述順にincludeされるようです。

/WEB-INF/jsp/header.jsp <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> <meta http-equiv="Content-Style-Type" content="text/css"> <meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Cache-Control" content="no-cache"> <meta http-equiv="Cache-Control" content="no-store"> <meta http-equiv="Cache-Control" content="max-age=0"> <meta http-equiv="Expires" content="0"> <title>XXX</title> <link rel="stylesheet" href="<%= application.getContextPath() %>/all.css" type="text/css" /> </head> <body> <div style="float: left;">ID: <%= request.getRemoteUser() %> 氏名: XXXX</div> <div style="float: right;"><a href="logout">ログアウト</a></div> <hr style="clear: both;"> <noscript> <p>このページはJavaScriptを使用しています。JavaScriptを有効にし再度表示して下さい。</p> </noscript> <div id="main" style="display:none;">
/WEB-INF/jsp/footer.jsp </div> <hr style="clear: both;"> <div style="float: left;"> <html:link action="menu">メニュー</html:link> <html:link action="logout">ログアウト</html:link> </div> <address style="float: right; font-size: 0.8em;">copyRight</address> <script type="text/javascript"> <!-- document.getElementById("main").style.display='block'; //--> </script> </body> </html>
/WEB-INF/jsp/tablib.jsp <%@ taglib prefix="html" uri="/tags/struts-html.tld" %><-- taglib -->
/WEB-INF/jsp/body/hoge.jspが表示された際のHTMLイメージ <-- taglib --><html> <head> <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> <meta http-equiv="Content-Style-Type" content="text/css"> <meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Cache-Control" content="no-cache"> <meta http-equiv="Cache-Control" content="no-store"> <meta http-equiv="Cache-Control" content="max-age=0"> <meta http-equiv="Expires" content="0"> <title>XXX</title> <link rel="stylesheet" href="アプリケーション名/all.css" type="text/css" /> </head> <body> <div style="float: left;">ID: xxxx 氏名: XXXX</div> <div style="float: right;"><a href="logout">ログアウト</a></div> <hr style="clear: both;"> <noscript> <p>このページはJavaScriptを使用しています。JavaScriptを有効にし再度表示して下さい。</p> </noscript> <div id="main" style="display:none;"> hogehoge </div> <hr style="clear: both;"> <div style="float: left;"> <html:link action="menu">メニュー</html:link> <html:link action="logout">ログアウト</html:link> </div> <address style="float: right; font-size: 0.8em;">copyRight</address> <script type="text/javascript"> <!-- document.getElementById("main").style.display='block'; //--> </script> </body> </html>
Log4Jを利用したい
Tomcat8

Tomcat本体のログ出力は「Apache Commons Logging」を利用しています。これをLog4Jに変更します。

  1. TomcatのダウンロードページよりExtraパッケージをダウンロードする。ファイルは「tomcat-juli.jar」と「tomcat-juli-adapters.jar」の2つ。
  2. Log4Jのダウンロードページより「log4j.jar」をダウンロードする。
  3. 「tomcat-juli.jar」を「TOMCATホーム/bin/」に配置する。既存ファイルを上書きする。
  4. 「tomcat-juli-adapters.jar」と「log4j.jar」を「TOMCATホーム/lib/」に配置する。
  5. TOMCATホーム/conf/logging.properties」を削除する。
  6. TOMCATホーム/lib/」にLog4Jの設定ファイル(log4j.propertiesもしくはlog4j.xml)を配置する。

以下はシンプルなlog4j.xmlです。必要に応じ変更して下さい。

ver1.2向け log4j.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration> <appender name="CONSOLE" class="org.apache.log4j.DailyRollingFileAppender"> <param name="File" value="${catalina.base}/logs/stdout" /> <param name="DatePattern" value="'.'yyyy-MM-dd'.log'" /> <param name="Append" value="true" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d [%t] %-5p %c- %m%n" /> </layout> </appender> <appender name="CATALINA" class="org.apache.log4j.DailyRollingFileAppender"> <param name="File" value="${catalina.base}/logs/catalina" /> <param name="DatePattern" value="'.'yyyy-MM-dd'.log'" /> <param name="Append" value="true" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d [%t] %-5p %c- %m%n" /> </layout> </appender> <appender name="LOCALHOST" class="org.apache.log4j.DailyRollingFileAppender"> <param name="File" value="${catalina.base}/logs/localhost" /> <param name="DatePattern" value="'.'yyyy-MM-dd'.log'" /> <param name="Append" value="true" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d [%t] %-5p %c- %m%n" /> </layout> </appender> <logger name="org.apache.catalina.core.ContainerBase.[Catalina].[localhost]"> <level value="info" /> <appender-ref ref="LOCALHOST" /> </logger> <root> <level value="info" /> <appender-ref ref="CONSOLE" /> <appender-ref ref="CATALINA" /> </root> </log4j:configuration>

Tomcat8で動作確認を行いましたが、Tomcat6,7でも同様の方法でLog4Jに変更できると思います。

catalina.outをローテーションしたい
Tomcat4.1 Tomcat5 Tomcat6 Tomcat7 Tomcat8
/etc/logrotate.d/hoge(日時ローテの例) /var/log/catalina.out { missingok copytruncate daily sharedscripts postrotate EXT=`date +%Y%m%d` for f in $1; do mv $f.1 $f.$EXT; done endscript }

Linux環境などにTomcatをインストールし運用すると、問題となるのがcatalina.out。何の対策もとらないと肥大化してしまいます。

ここでは「logrotateコマンド」を用いたローテーションの設定を紹介します。似たようなものに「rotatelogs」がありますが、apache付属なのでtomcat単体では使えません。

一般に「/etc/logrotate.conf」にログローテーションの設定を記述するのですが、「/etc/logrotate.d」ディレクトリに新規にファイルを作り記載するのがよいでしょう。

ローテーションさせたいファイルが「/var/log/catalina.out」である場合を例に書きます。「/etc/logrotate.d/hoge(任意の名称でOK)」を新規作成します。ファイルの内容はこちらのソースを参考にして下さい。

書式エラーを発見するために「/usr/sbin/logrotate -d /etc/logrotate.d/hoge」を実行します。誤りがある場合表示されるでしょう。

次に強制実行します。「/usr/sbin/logrotate -f /etc/logrotate.d/hoge」を実行することで、ローテーションするはずです。実際にファイルがローテーションしたか確認して下さい。

最後に日時を待ちます・・・。実際にファイルがローテーションしたか確認して下さい。この作業を怠るとよくない結果が待っていますのでご注意下さい。

HttpServletRequest#getRequestURI()にて返却される値
Tomcat4.1 Tomcat5

ブラウザにて次のAPI(http://サーバ名/アプリ名/aaa.jsp)を呼び出したとします。その際、aaa.jspはbbb.jspにforwardしているとします。このときHttpServletRequest#getRequestURI()は以下の値を返します。

Tomcat getRequestURI
4.1 /アプリ名/aaa.jsp
5.5 /アプリ名/bbb.jsp

4.1系のようなクライアントから直接アクセスされたURIを取得したい場合は以下のコードで取得できます。

直接アクセスされたURI (String) request.getAttribute("javax.servlet.forward.request_uri")

と書きましたが情報として誤りのようでした。申し訳ありません。

Tomcat8で確認したのですが、bbb.jspの中で「HttpServletRequest#getRequestURI()」を行うと上記のようになるようです。

解釈としては「aaa.jsp」から「bbb.jsp」へリクエストを行ったといったところでしょうか。

Tomcat/libに自作JARを入れると関連jarファイルをScanする
Tomcat8

a.jarがb.jar内のクラスを使うとき、TOMCATホーム/lib/a.jarを配置するとb.jarがない。といったメッセージが表示されます。以下を参考に設定して下さい。

server.xmlの例 <Context> <JarScanner scanManifest="false"/> </Context>