Apache 2.3/2.4系に実装中の新機能をちょっと先取りして見てみよう その2
さて、先日のエントリでmod_auto_formとmod_sessionを用いたApacheでの新しい認証機構について紹介しましたが、今回はこれを実際に設定して動作を確認してみることにしましょう。
(※以下、バージョンは当記事執筆現在の最新です)
ビルド・インストール
まずはApache 2.3.5のビルドです。http://httpd.apache.org/から、"Apache 2.3.5-alpha Released"の欄を見つけたら、"Download"のリンクをたどってtarballを取得します。なお、従来であれば一緒に入っていたAPR(Apache Portable Runtime)とAPR-utilがありません。バージョン指定もそれぞれ1.3.0以上を要求しますので、インストール済みでなければそれぞれhttp://apr.apache.org/から個別に取ってくる必要があります。今回は、このライブラリ2つともhttpd本体にバンドルした形でビルドを進行することとします。ダウンロードが完了したら、srclibディレクトリ内に解凍して配置してください。
$ tar jxvf httpd-2.3.5-alpha.tar.bz2 $ cd httpd-2.3.5-alpha/srclib $ tar jxvf ../../apr-1.4.2.tar.bz2 $ mv apr-1.4.2 apr $ tar jxvf ../../apr-util-1.3.9.tar.bz2 $ mv apr-util-1.3.9 apr-util
ビルドは定番のconfigure & makeですが、configureの引数には--with-included-aprと--with-mysqlを最低限指定します。これは、APRとAPR-utilをsrclib配下に置いてビルドするのと、後述するユーザ認証用DBをmysqlを用いて作成するためです。あとは必要があれば適宜お好みで追加してください。既にApacheがインストールしてあるサーバで行う時は、--prefixオプション等を利用して、インストール先が衝突しないように調整してください。
$ ./configure --enable-mods-shared=all --with-included-apr --prefix=/usr/local/app/apache-2.3.5 --with-mysql $ make # make install
make installまで来たら、httpd.confを調整して起動を確認できればOKです。
設定・仮サイト構築
さて、いよいよ本題、mod_auto_form周りの設定から入りましょう。前に挙げたページ遷移図のおさらいです。

それぞれ、1)から5)までのページ(URL)を下記の通りとします。前回その1の例でのファイル名と同じですね。マイページについては、認証による保護がなされ、未ログインの状態でのアクセスは許可しないものとします。
- 1) /login.html
- 2) /doauth
- 3) /mypage/(index.html)
- 4) /dologout
- 5) /logout.html
続いてHTMLファイルを作ります。ページ遷移の再現ができればいいので、必要最小限の内容しか置きません。見栄えは気にしないで下さい;-)。
<html> <body> <form method="POST" action="/doauth"> Username: <input type="text" name="httpd_username" value="" /> Password: <input type="password" name="httpd_password" value="" /> <input type="submit" name="login" value="Login" /> </form> </body> </html>
<html> <body> <a href="/dologout">logout</a> <br> <a href="news.html">news</a> </body> </html>
<html> <body> <a href="login.html">login</a> </body> </html>
3)のマイページに"news"というリンクがありますが、これはマイページからログアウト以外の遷移を行う為のものです。つまり、認証による保護を受けた状態のままでのページ遷移を再現したいという意図です。内容は何でも構わないので、下記のようにしておきます。
<html> <body> <a href="index.html">index</a> <br> </body> </html>
HTMLファイルは以上で揃いました。続いてmod_auth_formの設定に移りましょう。
<Location /doauth> SetHandler form-login-handler AuthFormLoginRequiredLocation /login.html AuthFormLoginSuccessLocation /mypage/ AuthFormProvider dbd AuthDBDUserPWQuery "SELECT password FROM user WHERE user = %s" AuthType form AuthName realm </Location> <Location /dologout> SetHandler form-logout-handler AuthFormLogoutLocation /logout.html AuthName realm </Location>
/doauthおよび/dologoutについて、SetHandler、AuthFormLoginRequiredLocationおよびAuthFormLoginSuccessLocationの指定までは前出の通りです。
AuthFormProviderは、この認証でユーザID・パスワードの組をどのストレージで保持するかを指定するものです。dbdはデータベースですが、他にもfile(htpasswdで生成するおなじみのパスワードファイル形式)やldapがあります。データベースにユーザテーブルを作成する場合、mod_authn_dbdやmod_dbdの設定がさらに必要になります。DBDriverでデータベースの種類を選び、DBParamsで接続パラメータ(DB名、DBユーザ名やパスワード、サーバアドレスなど)を指定します。
DBDriver mysql DBDParams "dbname=apache user=apache pass=xxxxxx host=localhost"
各パラメータは必要に応じて適宜修正してください(DBユーザは、指定のデータベースに対してSELECT,INSERT,UPDATE,DELETE権限をGRANTされているものと仮定します)。そして前出のAuthDBDUserPWQueryと合わせて、ユーザテーブルをCREATE TABLEします。mysqlコマンドからサーバに接続して、以下のSQL文を発行してください。
CREATE TABLE user (user CHAR(32) PRIMARY KEY, password CHAR(32)); INSERT INTO user (user, password) VALUES ('hoge', '****');
****の部分は、実際にはhtpasswdでエンコードされたパスワード文字列が入ります。htpasswdで通常作成するパスワードファイルが、各行を':'で区切ってテーブルに登録することと同じ、という風に考えて頂ければ良いと思います。
# htpasswd -nb hoge fuga hoge:**** ← 「ユーザID:パスワード」の形式
さて、ここで一度設定を生かして動作を見てみましょう。
ユーザIDとパスワードを正しく入力してログインボタンを押すと、マイページに遷移します。アクセスログで確認しても、想定通りになっていますね。
192.168.0.201 - - [25/Feb/2010:14:03:39 +0900] "GET /login.html HTTP/1.1" 200 257 192.168.0.201 - hoge [25/Feb/2010:14:03:42 +0900] "POST /doauth HTTP/1.1" 301 216 192.168.0.201 - - [25/Feb/2010:14:03:42 +0900] "GET /mypage/ HTTP/1.1" 200 95 192.168.0.201 - - [25/Feb/2010:14:05:16 +0900] "GET /dologout HTTP/1.1" 307 222 192.168.0.201 - - [25/Feb/2010:14:05:16 +0900] "GET /logout.html HTTP/1.1" 200 61
また、誤ったユーザIDないしパスワードを入れると、マイページへは遷移せずに再度ログインページに戻っています。
次に試しにログアウトした状態で、マイページに直接アクセスしてみましょう。認証で保護されなければいけませんから、ログインページに飛ばされて欲しいところですが...。
見えちゃいますね。
これはhttpd.conf中で/mypage/配下の設定をしていませんから、mod_auth_formの働きようがないためです。というわけで、追加しましょう。
<LocationMatch "/mypage/*"> AuthFormProvider dbd AuthDBDUserPWQuery "SELECT password FROM user WHERE user = %s" AuthFormLoginRequiredLocation /login.html AuthType form AuthName realm </LocationMatch>
内容は/doauthに似ています。mod_authn_dbdでユーザ認証を通すという仕組みは一緒だからです。しかし、/doauthではHTTPレスポンスが/mypage/へのリダイレクトとして固定されていたのに対し、ここではHTTPリクエストでのページをそのまま表示させるという挙動を取りますので、AuthFormLoginSuccessLocationは設定しません。SetHandlerも不要です。さて、これでどうでしょうか。
192.168.0.201 - hoge [25/Feb/2010:14:25:47 +0900] "POST /doauth HTTP/1.1" 301 216 192.168.0.201 - - [25/Feb/2010:14:25:47 +0900] "GET /mypage/ HTTP/1.1" 301 219 192.168.0.201 - - [25/Feb/2010:14:25:47 +0900] "GET /login.html HTTP/1.1" 200 257 192.168.0.201 - hoge [25/Feb/2010:14:25:49 +0900] "POST /doauth HTTP/1.1" 301 216 192.168.0.201 - - [25/Feb/2010:14:25:49 +0900] "GET /mypage/ HTTP/1.1" 301 219 192.168.0.201 - - [25/Feb/2010:14:25:49 +0900] "GET /login.html HTTP/1.1" 200 257 192.168.0.201 - hoge [25/Feb/2010:14:25:51 +0900] "POST /doauth HTTP/1.1" 301 216 192.168.0.201 - - [25/Feb/2010:14:25:51 +0900] "GET /mypage/ HTTP/1.1" 301 219 192.168.0.201 - - [25/Feb/2010:14:25:51 +0900] "GET /login.html HTTP/1.1" 200 257
おかしなことになりました。こんどは、ログインフォームでどんなユーザID・パスワードを入れても、ずっと同じログインフォームに戻って来てしまいます。上はそのときのアクセスログです。一体どうなっているのでしょう。
…しらじらしかったですね;-)。はい、大変お待たせしました。ここでようやくmod_sessionの出番です。下のような設定をそれぞれ追加してください。
DBDPrepareSQL "DELETE FROM session WHERE session_key = %s" deletesession DBDPrepareSQL "UPDATE session SET session_value = %s, session_expire_time = %lld WHERE session_key = %s" updatesession DBDPrepareSQL "INSERT INTO session (session_value, session_expire_time, session_key) values (%s, %lld, %s)" insertsession DBDPrepareSQL "SELECT session_value FROM session WHERE session_key = %s AND (session_expire_time = 0 OR session_expire_time > %lld)" selectsession DBDPrepareSQL "DELETE FROM session WHERE session_expire_time != 0 AND session_expire_time < %lld" cleansession
<Location /doauth> ... Session On SessionDBDCookieName session path=/ </Location> <Location /dologout> ... Session On SessionDBDCookieName session path=/ </Location> <LocationMatch "/mypage/*"> ... Session On SessionEnv On SessionDBDCookieName session path=/ </LocationMatch>
一緒に、先ほどユーザテーブルを作ったデータベースにやはりSQL文を発行し、セッションデータ保持のためのテーブルを作成します。
CREATE TABLE session (session_key CHAR(250) PRIMARY KEY, session_value TEXT, session_expire_time BIGINT);
これで期待通りの挙動をするようになりました。ユーザID・パスワードの判定およびマイページの認証保護も正規の動作になりました。マイページからニュースページ(/mypage/news.html)への遷移も試してみて下さい、マイページと同じように、認証状態であれば普通に遷移し、未認証状態であればログインページに飛ぶのが確認できると思います。
192.168.0.201 - - [25/Feb/2010:14:44:10 +0900] "GET /login.html HTTP/1.1" 200 257 192.168.0.201 - - [25/Feb/2010:14:44:10 +0900] "GET /favicon.ico HTTP/1.1" 404 209 192.168.0.201 - hoge [25/Feb/2010:14:44:18 +0900] "POST /doauth HTTP/1.1" 301 216 192.168.0.201 - hoge [25/Feb/2010:14:44:18 +0900] "GET /mypage/ HTTP/1.1" 200 95 192.168.0.201 - hoge [25/Feb/2010:14:44:26 +0900] "GET /mypage/news.html HTTP/1.1" 200 66 192.168.0.201 - hoge [25/Feb/2010:14:44:27 +0900] "GET /mypage/index.html HTTP/1.1" 200 95 192.168.0.201 - - [25/Feb/2010:14:44:50 +0900] "GET /dologout HTTP/1.1" 307 222 192.168.0.201 - - [25/Feb/2010:14:44:50 +0900] "GET /logout.html HTTP/1.1" 200 61
アクセスログにもご注目ください。/doauth以降、マイページ内の遷移でユーザID"hoge"が保持されたままページ遷移を続けており、/dologoutでそれが消えていることが確認できます。
Apacheからは、ブラウザに対してセッションIDをCookieとして発行します。このIDに紐づけられた内容はsessionテーブル内に保持されています。そしてこのセッションIDは、SessionEnvディレクティブをOnにしておくことで環境変数HTTP_SESSIONとしてCGI/Webアプリケーションにも引き渡すことができます。CGIスクリプトを使って試してみましょう。
mysql> SELECT * FROM session; +--------------------------------------+-------------------------------+---------------------+ | session_key | session_value | session_expire_time | +--------------------------------------+-------------------------------+---------------------+ | e5c971ba-c6c4-472e-95ec-f9a541125791 | realm-user=hoge&realm-pw=**** | 0 | +--------------------------------------+-------------------------------+---------------------+ 1 row in set (0.00 sec)
#!/usr/bin/env perl print "Content-type: text/plain; charset=utf-8\n\n"; foreach $var (sort(keys(%ENV))) { $val = $ENV{$var}; $val =~ s|\n|\\n|g; $val =~ s|"|\\"|g; print "${var}=\"${val}\"\n"; }
注意点
注意しなければならないのは、上記のsession_valueカラムにはパスワードが生の状態で入ってしまっている点です。目的としてはセッションIDが発行できればよく、パスワードをここに入れておく必要はないので設定でこれを回避したい所ですが、現時点で解決法は見つかっておりません。
また、さらに重要な点がありまして、Apacheがmod_dbdを使ってデータベースとの間でコネクションを張る場合、そのコネクション数はWebアプリケーションとの間で取り合いになるということです。mod_dbdとWebアプリケーションとの間でDBコネクションを共用したり、コネクションプーリングをしたりといった統合的な仕組みはありませんので、それぞれにコネクション数の上限やタイムアウト時間などのDBチューニングを別個に行う必要があります。そのためどっちかがコネクションを取りすぎて無駄にしたり、逆にDBの処理能力を上回るコネクション要求を出してしまったりなど、DBとWebサーバとの間の調整がさらに難しくなることでしょう。
この問題によって支障が発生する場合は、認証・セッション用のDBはWebアプリケーション用のDBとは別に構築するということも考慮すべきかと思います。
まとめ
このように、まだ基本的な動作検証のみで実用的な構成案まではまだまだといったところですが、Apacheにおける認証機構の新しい形態を見ることができました。従来であればBasic認証やDigest認証といった、HTTPプロトコルそのものの認証への対応のみでしたが、一般的なWebサービスで使われているログインフォームを利用した形態にも対応可能となったことは、サイト構築の選択肢をさらに広げることになるのではないでしょうか。