2010年02月26日

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周りの設定から入りましょう。前に挙げたページ遷移図のおさらいです。

chart_auth

それぞれ、1)から5)までのページ(URL)を下記の通りとします。前回その1の例でのファイル名と同じですね。マイページについては、認証による保護がなされ、未ログインの状態でのアクセスは許可しないものとします。

  • 1) /login.html
  • 2) /doauth
  • 3) /mypage/(index.html)
  • 4) /dologout
  • 5) /logout.html

続いてHTMLファイルを作ります。ページ遷移の再現ができればいいので、必要最小限の内容しか置きません。見栄えは気にしないで下さい;-)。

1) /login.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>
3) /mypage/(index.html)
<html>
<body>
<a href="/dologout">logout</a>
<br>
<a href="news.html">news</a>
</body>
</html>
5) /logout.html
<html>
<body>
<a href="login.html">login</a>
</body>
</html>

3)のマイページに"news"というリンクがありますが、これはマイページからログアウト以外の遷移を行う為のものです。つまり、認証による保護を受けた状態のままでのページ遷移を再現したいという意図です。内容は何でも構わないので、下記のようにしておきます。

3.1) /mypage/news.html (ニュースページ)
<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:パスワード」の形式

さて、ここで一度設定を生かして動作を見てみましょう。

login

ユーザ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ないしパスワードを入れると、マイページへは遷移せずに再度ログインページに戻っています。

次に試しにログアウトした状態で、マイページに直接アクセスしてみましょう。認証で保護されなければいけませんから、ログインページに飛ばされて欲しいところですが...。

mypage

見えちゃいますね。

これは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でそれが消えていることが確認できます。

cookie

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)
printenv.cgi
#!/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サービスで使われているログインフォームを利用した形態にも対応可能となったことは、サイト構築の選択肢をさらに広げることになるのではないでしょうか。

klab_gijutsu2 at 02:48│Comments(0)TrackBack(0)apache 

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント