[ホーム] -> [Aache + PHP + PostgreSQL 実験室] |
先ほど説明したパスワードファイルを使ったユーザ認証ですが、ユーザ数が多くなるとだんだんパフォーマンスが悪くなるのでお勧めできません。基本認証の仕組みから言うと、ユーザがユーザ名/パスワードを入力した時ではなく、ユーザ認証が必要な部分へアクセスするたびにパスワードファイルがチェックされます。したがって何千人ものユーザがいる場合は、ページにアクセスするたびにパスワードファイルをすべて検索しユーザが存在するかを調べるのです。これはかなりまずいことなので、Apache のマニュアルだと DBM ファイルを使ったユーザ管理をするようにと書いてあります。が、ここでは PostgreSQL を使った説明をしているので、PostgreSQL を使ったユーザ認証について説明したいと思います。
ユーザ認証を PostgreSQL でするようにするために、mod_auth_pgsql というモジュールが公開されています(いや、別に自分で CGI 作ってもいいですけどね)。使い方は簡単で、ドキュメントを読めば出来ると思いますが、初期設定が手間なので、サンプルがてら説明したいと思います。
まず、当然モジュールを手に入れてコンパイルし、Apache に組み込みます。
次に、このモジュールを利用できるように、テーブルを作る必要があります。このモジュールが利用するテーブルは3つあります。ユーザテーブル、グループテーブル、ログテーブルです。ユーザテーブルは必須ですが、他の2つは任意です。まずユーザテーブルの説明を行いましょう。
create table auth_user ( userid varchar(40) primary key, password varchar(32), lock integer default 1 );
必須のカラムは、ユーザ名(例では userid
)と、パスワード(例では password
)です。テーブル名、カラム名は何でもいいし、カラムのデータ型も文字列系であれば大丈夫です。ユーザ名は重複してはいけないので、primary key
を設定しておきます。lock
というカラムを付けましたが、これは後の説明の為に付けました(本当は必要ない)。
格納しておくパスワードは、クリアテキスト(パスワードそのまま)、crypt 暗号、MD5 の3種類から出来ます。crypt の場合は13バイト、MD5の場合は32バイト必要なので、ここでは 32 バイトにしました。
次に、このテーブルを認証で使うように、mod_auth_pgsql の設定を行います。
## ユーザ認証をするときに必要な設定 AuthName "PostgreSQL Authenticator" AuthType Basic # 認証可能なら誰でも。(もちろん、「user ユーザ名」も使えます) Require valid-user ## mod_auth_pgsql の設定 # PostgreSQL のサーバ(UNIX ドメイン経由の場合は必要ない) #Auth_PG_host localhost # PostgreSQL のポート(特に変更してなければ必要ない) #Auth_PG_port 5432 # データベース名 Auth_PG_database wwwdb # データベースに接続するユーザ名 Auth_PG_user wwwuser # データベースに接続するユーザのパスワード(設定してなければ必要ない) Auth_PG_pwd wwwpass # 先ほど作成したテーブル名 Auth_PG_pwd_table auth_user # 先ほど作成したテーブルのユーザ名が入っているカラム名 Auth_PG_uid_field userid # 先ほど作成したテーブルのパスワードが入っているカラム名 Auth_PG_pwd_field password # パスワードがクリアテキストの場合は Off(デフォルトOn) Auth_PG_encrypted Off
一応上記の設定をすれば、ユーザ認証が出来るようになると思います。試しにデータを追加してログインしてみましょう。
insert into auth_user values ('Clear', 'Pass' , 0);
出来ましたか? クリアテキストの設定になっているので、ユーザ名「Clear
」パスワード「Pass
」でログインできるはずです。
内部的な処理ですが、入力されたユーザ名を元にユーザテーブルを検索し、存在した場合はそのパスワードと入力されたパスワードが一致するかを調べているようです。従って、ユーザ名が一致しない限り、パスワードは評価されないため、パスワードにはインデックスは必要ありません。
クリアテキストで格納しておくのはちょっと、と言う人のために、crypt と MD5 のデータを作ってみます。パスワードを「Pass
」とした場合、どういう風に作ればいいかの一例を示します。
crypt の場合(Perl を使う) > perl -e "print crypt('Pass',join('',('.','/',0..9,'A'..'Z','a'..'z')[rand 64, rand 64]))."'"\n";' MD5 の場合(md5sum を使う、echo -n が改行されないのが前提) > echo -n 'Pass' | md5sum
MD5 は毎回同じ値になりますが、crypt は salt をランダムで求めているので、毎回違う値になると思います。また、MD5 は大文字小文字どちらでも大丈夫です(大文字小文字無視して一致するかを調べている)。作成した結果をテーブルに登録しておきます。
insert into auth_user values ('Crypt', 'XaeZe7PpKnmJc' , 0); insert into auth_user values ('MD5' , 'b9b57aae83585e17ede4570dcede353c', 0);
次に mod_auth_pgsql の設定を変更します
# 暗号化パスワードを使う場合は On Auth_PG_encrypted On # パスワードの暗号化方法を指定(MD5 か Crypt) Auth_PG_hash_type Crypt #Auth_PG_hash_type MD5
暗号化方法を変えてログインして試してみてください。どちらでもいいのでしたら、当然 MD5 をお勧めします。Crypt はパスワードを 8 文字までしか意味がないし、最近のマシンパワーを考えると総当たりで調べ切れてしまうでしょうからね。
グループテーブルを使った認証方法の説明です。今までの説明は、ユーザテーブルに存在するユーザならすべてがログイン可能でしたが、今度はグループに登録してあるユーザのみ許可、と言う風にしてみようと思います。
create table auth_group ( groupid varchar(40), userid varchar(40) references auth_user(userid) on delete cascade, lock integer default 1, primary key (groupid, userid) );
必要な項目は、グループ名(例では groupid
)とユーザ名(例では userid
)で、ユーザ名はユーザテーブルに使ったのと同じカラム名でなければなりません。テーブル名やグループ名は何でも構いません。
ユーザ名は、ユーザテーブルのユーザ名と関連づけられているので、references auth_user(userid) on delete cascade
というオプションを付けました。これは、この auth_group
テーブルに、auth_user
テーブルの userid
カラムに存在するデータしか入力することが出来ないような設定です(references auth_user(userid)
の部分)。さらに、auth_user
テーブルから同じユーザ名が削除されたら、auth_group
テーブルからも自動で削除します(on delete cascade
)。
また、プライマリーキーは、グループ名とユーザ名のペアです。あるグループに対してユーザを複数登録できますが、ある一つのグループに対しては、ユーザが重複することはあり得ません(あっても無意味)。なので、この様に組み合わせてプライマリーキーにしています。組み合わせる場合は、この様にすべてのカラム定義の最後に、primary key (groupid, userid)
と、組み合わせる順でカラム名を指定します。
テーブル登録が終わったら、ユーザとグループを登録してみます。
insert into auth_user values ('Gp1User1', 'Pass', 0); insert into auth_user values ('Gp1User2', 'Pass', 0); insert into auth_user values ('Gp1User3', 'Pass', 0); insert into auth_user values ('Gp2User1', 'Pass', 0); insert into auth_user values ('Gp2User2', 'Pass', 0); insert into auth_user values ('Gp3User1', 'Pass', 0); insert into auth_group values ('Gp1', 'Gp1User1', 0); insert into auth_group values ('Gp1', 'Gp1User2', 0); insert into auth_group values ('Gp1', 'Gp1User3', 0); insert into auth_group values ('Gp2', 'Gp2User1', 0); insert into auth_group values ('Gp2', 'Gp2User2', 0); insert into auth_group values ('Gp3', 'Gp2User1', 0);
試しに次のようにして、references auth_user(userid) on delete cascade
がうまく動いているか調べてみてください。
存在しないユーザをグループテーブルに登録してみる => insert into auth_group values ('Gp5', 'UnknownUser', 0); ERROR: <unnamed> referential integrity violation - key referenced from auth_group not found in auth_user グループテーブルを確認し、ユーザテーブルからユーザを削除 => select * from auth_user where userid like 'Gp2%'; userid | password | lock ----------+----------+------ Gp2User1 | Pass | 0 Gp2User2 | Pass | 0 (2 rows) => delete from auth_user where userid = 'Gp2User1'; DELETE 1 => select * from auth_user where userid like 'Gp2%'; userid | password | lock ----------+----------+------ Gp2User2 | Pass | 0 (1 rows)
次にグループテーブルを利用するように Apache の設定を変更します。
# グループテーブル名 Auth_PG_grp_table auth_group # グループテーブルのグループ名が入ったカラム名 Auth_PG_gid_field groupid # グループ名 Gp1 のユーザのみ許可 Require group Gp1
これでグループ名指定で許可を行えるようになりました。
mod_auth_pgsql には、ユーザがアクセスしたログを、テーブルに登録する機能も備えています。ログを格納するテーブルは、次のような感じになります。
create table auth_log ( userid varchar(40), accessdate timestamp, accessuri varchar(256), remoteaddr varchar(128), password varchar(32) );
今回は特にプライマリーキーにするようなカラムはありませんので、特に何も付けずに作成します。これも他と変わらず、テーブル名、カラム名は何でも構いません。サイズは適当です。カラムの目的は順番に、ユーザ名、アクセスした日時、アクセス先の URI(メソッド パス プロトコルバージョン)、クライアントのアドレス、パスワードです。パスワードは暗号化しているかに関係なくクリアテキストで保存されるので、このカラムはなくてもいいでしょう。
このログデータですが、認証が成功したアクセスだけ記録されるということに、気を付けてください。失敗は記録されません。
Apache の設定は次のようになります。
# ログテーブル名 Auth_PG_log_table auth_log # ログテーブルのユーザ名 Auth_PG_log_uname_field userid # ログテーブルのアクセス日付 Auth_PG_log_date_field accessdate # ログテーブルのアクセス URI Auth_PG_log_uri_field accessuri # ログテーブルのクライアントアドレス Auth_PG_log_addrs_field remoteaddr # ログテーブルのパスワード Auth_PG_log_pwd_field password
記録が必要な項目だけを定義すればいいです。このログテーブルですが、Apache のログファイルと重複するので、どちらか一方だけ取ればいいと思いますが・・・。
説明が後回しになっていた lock
フィールドについて説明します。mod_auth_pgsql には Auth_PG_pwd_whereclause
というディレクティブがあり、このディレクティブに設定した文字列が、テーブルを検索するときの where
句に追加されます。例えば次のように設定したとします。
# ユーザテーブル検索時に追加されるオプション Auth_PG_pwd_whereclause " and lock != 1" # グループテーブル検索時に追加されるオプション Auth_PG_grp_whereclause " and lock != 1"
すると、この設定がない場合は「select password from auth_user where userid = 'User'
」という SQL 文でユーザが検索されますが、この設定値が加わるため、「select password from auth_user where userid = 'User' and lock != 1
」という SQL 文が実行されます。したがって、lock
カラムに 1
が設定されていると、検索条件に合わないためユーザが存在しないと判断されます。
この仕組みにより、この例のようにユーザをデータベースに登録したままで利用不可にしたりできます。あるいは有効期限を設定しておく expire_date
というカラムを用意し、「and expire_date >= current_date
」とかして有効期限までしかログインできなくしたりも出来ます。複雑にしたい場合はカッコでくくり、「and ((expire_date is null or expire_date >= current_date) and (lock is null or lock != 1)
」とか指定することも出来ます。
ちなみに、今まで Auth_PG_pwd_table, Auth_PG_grp_table
にテーブル名を指定していましたが、user_base_data b left join user_ext_data e on (b.userid = e.userid)
とか指定して、複数のテーブルを結合させることも出来ます。
実際のところ、ユーザテーブルを検索した結果、1 件だけ返すような SQL 文を作成できればいいのです。もし、2 件以上返してしまうと、エラーになってしまいますので注意してください。
残りのオプションも説明しておきましょう。ちなみに、Auth_PG_options
以外は、すべてデフォルト Off
です。
# 接続時にバックエンドに渡すオプション Auth_PG_options "--debug_level=3" # パスワードが空でも接続を許可する Auth_PG_nopasswd on # ユーザ名をすべて小文字で検索 Auth_PG_lowercase_uid on # ユーザ名をすべて大文字で検索 Auth_PG_uppercase_uid on # パスワードを大文字小文字の区別無く検索(クリアテキストのみ) Auth_PG_pwd_ignore_case on # パスワードをキャッシュ Auth_PG_cache_passwords on
Auth_PG_options
は、これは PostgreSQL に接続したときに渡すオプションです。postgresql.conf
で定義するオプションの一部を指定できます。どれが利用できるかはマニュアルを参照してください。
Auth_PG_lowercase_uid, Auth_PG_uppercase_uid
は、ユーザが入力したユーザ名をそれぞれ大文字・小文字に変換してユーザテーブルを検索します。両方指定してある場合は、大文字小文字の両方検索してみてどちらかのパスワードが正しければ認証許可されます。ただし、どちらかでも指定されていると、ユーザが入力した文字そのものでは検索しないので、ユーザテーブル内のユーザ名が、大文字か小文字に統一されているときのみ指定した方がいいでしょう。
Auth_PG_pwd_ignore_case
ですが、On
の時はパスワードの比較を行うときに大文字小文字を無視して比較します。
Auth_PG_cache_passwords
はパスワードをキャッシュする設定です。ユーザ名とパスワードが一致したものを内部で保持していて、それ以降はデータベースを見に行かなくなります。ただし、この数はデフォルト 50 ユーザ分で、それ以上になると一度キャッシュは破棄されます。デフォルトの 50 を変更したい場合は、apxs -DMAX_TABLE_LEN=100 ...
とかオプションを付けてコンパイルし直す必要があります。キャッシュを多くすると効率が良くなりますが、パスワードの変更を行ったときなどなかなか反映されなくなるので注意してください。
mod_auth_pgsql は、PostgreSQL によるユーザ管理が出来、各テーブルの柔軟に対応できるモジュールです。すでに PostgreSQL でユーザ管理を行っている場合や、そのようなシステムを作る場合は一考の価値があるかもしれません。
ただし、負荷の高いサイトは、きちんと負荷テストを行った方がいいと思います。と言うのも、パスワードを検索するたびに PostgreSQL に接続・切断するためです。基本認証を使っているので、認証を必要としているすべてのページにアクセスするたびに繰り返されることになります(Auth_PG_cache_passwords
が Off
の場合)。当然、画像やスタイルシートのファイルなどを認証付きのディレクトリ下に置いてある場合、それらすべてに対してこの作業が行われます。これを避けるには、mod_auth_pgsql を改造するしかないですね。ただ、手軽に PostgreSQL 認証を行えるという点で、非常に有用です。