Rewiteでフォーム認証
mod_rewrite の仕組みがわかったところで、使い方を考えていきます。
mod_rewriteで認証をかける
目標: 指定IP以外はファイルにアクセスできなくする。
準備
フォルダ構造を準備した。
rewrites/ex1
takuya@~/Sites/rewrites$ tree rewrites/ ├── ex2 │ ├── index.php │ └── .htaccess ├── login.php └── users.db
あと、httpd.conf も使います。
.htaccess
.htaccessで、Rewriteをガンガン書いていきます。
RewriteBase /~takuya/rewrites/ex2/ RewriteCond ${login:%{REMOTE_ADDR}|null} NG RewriteRule ^ ../login.php?redirect_to=%{REQUEST_URI} [L]
ここでは、未認証IPアドレスからのリクエストはlogin.php を表示して拒否しています。
login.php
Basic認証の代わりにログインするための、フォームを作ります。認証のPOST情報をデータベースに書き込む処理もphp に記述していきました。
<?php session_start(); if( !empty( $_GET["redirect_to"]) ){ $_SESSION["redirect_to"] = $_GET["redirect_to"]; } if( !empty( $_POST["user"] ) && $_POST["user"] == "okawari" ) {//合い言葉チェック login(); if( !empty( $_SESSION["redirect_to"]) ) { header("Location: {$_SESSION['redirect_to']}"); }else{ header("Location: /~takuya/rewrites/ex2/ "); } return ; } function login(){ $ip = $_SERVER["REMOTE_ADDR"]; $dsn = "sqlite:/Users/takuya/Sites/rewrites/users.db"; $pdo = new PDO($dsn); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $smt = $pdo->prepare("select * from users where ip_addr = ? "); $smt->execute(array($ip)); $ret = $smt->fetchAll(); if(sizeof($ret)>0){ $smt = $pdo->prepare("update users set login_at = ? where ip_addr = ? ; "); }else{ $smt = $pdo->prepare("insert into users (login_at,ip_addr ) values ( ? , ? ) ;"); } $ret = $smt->execute(array(date('c'), $ip)); } ?> 現在のIPは認証されてないです。<br/> <form action="" method=post > <input name="user" value="" placeholder="合い言葉" > <input type="submit" value="送信"/> </form>
users.db データベースを作成
IPアドレスを登録して、ファイルを閲覧可能なIPアドレスをルック・アップ・テーブルとして使います。
-- Loading resources from /Users/takuya/.sqliterc CREATE TABLE users ( id integer primary key not null, ip_addr text uniq not null, created_at timestamp default current_timestamp not null, login_at timestamp );
最後に、dbd の設定 httpd.conf
ここまで準備出来たら、dbd を設定しておきます。
### ロード部分 LoadModule dbd_module libexec/apache2/mod_dbd.so ### データベース設定 DBDriver sqlite3 DBDParams "/Users/takuya/Sites/rewrites/users.db" ### RewriteMapを定義 RewriteMap login "dbd:select case count(*) when 1 then 'ON' else 'NG' end from users where current_timestamp < datetime(login_at,'+1 days') and ip_addr = %s " <Directory "/Users/takuya/Sites/rewrites/ex2" > RewriteEngine On </Directory>
ポイントはSQLの部分です。
RewriteCondは空文字列が来ると面倒なので、CASE句を使って「文字列」に変換しています。
また、登録したIPアドレスの有効期限をsqliteの日付計算で出しています。
%s がプリペアド・ステイトメントに相当します。
select case count(*) when 1 then 'ON' else 'NG' end from users where current_timestamp < datetime(login_at,'+1 days') and ip_addr = %s
apacheの再起動
ここで、Apacheを再起動する。
sudo apachectl -k restart
リクエストを送ってみる
未認証のIPアドレスからリクエストを送ってみます。するとURLのリダイレクトはなく、代わりに login.php の中身が表示される。
curl http://192.168.2.104/~takuya/rewrites/ex2/sample.jpg 現在のIPは認証されてないです。 <form action="" method=post > <input name="user" value="" placeholder="合い言葉" > <input type="submit" value="送信"/> </form>
認証リクエストを送信する。
curl でPOSTを送信してみます。
curl -L -d user=okawari http://192.168.2.104/~takuya/rewrites/ex1/sampledata.txt Hello World
無事にデータが表示されます。便利ですね。
データベースの状態
データベースには次のようにデータが登録されています。分かりやすいですね。
takuya@~/Sites/rewrites$ sqlite3 users.db -- Loading resources from /Users/takuya/.sqliterc SQLite version 3.8.11.1 2015-07-29 20:00:57 Enter ".help" for usage hints. >> select count(id) from users where current_timestamp < datetime(login_at,'+2 days'); count(id) ---------- 2 >> select * from users; id ip_addr created_at login_at ---------- ------------- ------------------- ------------------------- 1 192.168.2.104 2015-11-20 14:42:19 2015-11-20T23:42:19+09:00 2 192.168.2.105 2015-11-20 15:35:33 2015-11-21T00:35:33+09:00 >>
ハマった点
手作業でsqlite データベースをtruncate( delete from users ;vacuum ) すると、Apacheの再起動が必要な点が不便だった。
> sqlite3 users.db 'delete from users ;vacuum' > sudo apachectl -k restart
手作業でSqliteデータベースをいじったらApache再起動が必須でした。Rewriteのログ見て気づいた。
Basic(Digest)認証とかオワコン?
Basic(Digest)認証で、Apacheに保護をかけるといえば、鉄板のツールでしょう。でも、iphoneでパスワードの自動保存が動かなかったり、ダイアログが美しくないとか。いろいろと設計が古いんです。HTTPのプロトコル設計が認証にDigestしかないのです。CookieやFormでPOSTは一般で使われる。けどPOSTパスワードは平文で流れるし、決して美しい設計ではないのですね。その点Digest認証はマシな設計で終わらないコンテンツです。
だけどPOST+Cookieで殆どの認証が行われる現実はある。
Rewriteで認証すると嬉しいこと
mod_rewriteを使って、Basic認証の代わりに、フォーム認証(php)へリダイレクトしてみる。
カンタンのためにAllow IPと同じことを試してみます。
ファイル配信(ディレクトリ一覧)へ保護をかける。
ApacheでDigest認証を使うのは、Apacheでファイルを配信する時だと思う。
指定のIPアドレスへファイル配信を許可する。
.htaccessに allow IP を記述することが多いのですが、これをRewriteとデータベースで実現してみたいと思う。
データベースで実現することで、動的にIPアドレス認証の中身を変えられますね。
データベースで認証をかける。
データベースに登録することで、動的に許可・拒否を変更できるようになって便利になる。
動的に認証するメリット
ファイル配信をRefererや、リクエスト元IPアドレスで拒否できる。CookieをDBに入れていれば、セッションキーを元にSSOだって実現できる。
ということは、ファイルをプログラム経由無しでApache配信が安心できるようになるかも。
Apacheからのファイル配信
ファイルを配信するときに便利ですね。mod_rewriteがチェックするデータベースをPHPやrailsと共通にしておけば、ファイルの配信をチェックすることが出来る。
もっとも、コネクション+SQL発行+切断というオーバーヘッドはかかるので遅いといえば遅いけど。PHPを起動してフレームワークを起動して・・・みたいなファイル配信よりは速度が速そう。
速度測定
ファイルへのアクセスで認証ありとなしの場合を速度測定しておいた。
ファイルに認証した場合
takuya@atom:~$ for i in {1..100}; do curl -s -o /dev/null $DL_TARGET -w '%{speed_download}\n' ; done | awk '{sum += $1; count +=1; } END {print (sum/1024/1024) }' 0.275557
認証なしの場合
takuya@atom:~$ for i in {1..100}; do curl -s -o /dev/null $DL_TARGET -w '%{speed_download}\n' ; done | awk '{sum += $1; count +=1; } END {print (sum/1024/1024) }' 0.292685
認証用のデータベースが小さいの(件数1)で、とっても早いですね。認証なしのほうが遅いのは、rewriteで打ち止めにならず、他に動くんだろうな。