それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Apacheのmod_rewriteによるURL書換にまとめ(4)使い方:mod_rewrite でBasic認証の代わりにフォーム認証

Rewiteでフォーム認証

mod_rewrite の仕組みがわかったところで、使い方を考えていきます。

mod_rewriteで認証をかける

目標: 指定IP以外はファイルにアクセスできなくする。
  • データベースに登録したIPアドレスが許可アドレス
  • 登録日時から一定時間有効
  • パスワードを入力したら、許可IPアドレスを追加できる。
  • データベースはphpで更新
  • sqliteで認証

準備

IPアドレスを保存して、Apacheに参照させる。

フォルダ構造を準備した。

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がチェックするデータベースをPHPrailsと共通にしておけば、ファイルの配信をチェックすることが出来る。

もっとも、コネクション+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で打ち止めにならず、他に動くんだろうな。