PHPによるユーザ認証

 セキュリティを確保するために、 PHPでパスワードをユーザに入力してもらって認証を行う方法には、 大きく2種類があります。 1つはHTTPの基本認証を使う方法、2つめはPHP独自の認証を行う方法です。 HTTPの基本認証のスタイルは、 認証が必要なURLにアクセスしようとすると、Webブラウザの上に、 ユーザ名とパスワードを求めるダイアログがポップアップするもので、 このダイアログのデザインに特別労力を割く必要がない代わりに、 独自の注意書きなどを付加した画面をデザインすることはできません。 PHP独自の認証の場合はこの逆で、自分でパスワードの入力フォームを書かねばならず、 また書くことができます。(-_-;)


原理的なところから

 まずは原理的なところから。とても基本的な、HTTP基本認証のサンプルです。

<?php
if (!isset($_SERVER["PHP_AUTH_USER"])) {
  # このセッションでログインしたことがないなら、認証ダイアログを
  # 表示させるヘッダを送信します。
  header('WWW-Authenticate: Basic realm="For Administrators Only"');
  header('HTTP/1.0 401 Unauthorized');
  die("ユーザーの認証が必要です。");
}
else if (isset($_SERVER["PHP_AUTH_USER"])) {
  if($_SERVER["PHP_AUTH_USER"] != "admin" ||
     $_SERVER["PHP_AUTH_PW"]   != "goma") {
    # 認証に失敗。再度ダイアログを出します。
    header('WWW-Authenticate: Basic realm="For Administrators Only"');
    header('HTTP/1.0 401 Unauthorized');
    die("ユーザーの認証が必要です。");
  }
  else{
    print "<p>認証に成功しました!</p>";
  }
}
?>

 PHPは、HTTP認証の入力情報をCGI変数PHP_AUTH_USERPHP_AUTH_PWに持っています。 この例では、HTTP認証のダイアログにユーザ名とパスワードが入力されるとそれらが CGI変数の値に入って渡されるので、 固定でユーザ名「admin」パスワード「goma」であれば 「認証に成功しました」と表示し、そうでなければ再びダイアログを表示する、 という動作になります。また、ダイアログでキャンセルボタンが押されると 「ユーザーの認証が必要です。」とだけ書かれた文書を表示します。
 この例を実用的なものにするには、「認証に成功しました」 を表示している部分にこのWebサイトの目的となる処理を書くことになりますね。


ユーザー表をRDBMSに持たせる

 上の例は、ユーザ名とパスワードをスクリプトの中に固定で書いているというところがとても原始的ですね。 それで済むサイトもないわけではないのですけど、 普通はログインユーザによって実行可能な処理に差をつけたり、 過去のそのユーザの実績などを表示したりするために、 複数のユーザを管理する必要があるはずです。 そのために、RDBMSにユーザ表を作って、RDBMSに問い合わせることで認証を試す方法がよくとられます。 以下はその簡単なサンプルです。

<?php
require_once 'DB.php';
if (!isset($_SERVER["PHP_AUTH_USER"])) {
  # このセッションでログインしたことがないなら、認証ダイアログを
  # 表示させるヘッダを送信します。
  header('WWW-Authenticate: Basic realm="For Administrators Only"');
  header('HTTP/1.0 401 Unauthorized');
  die("ユーザーの認証が必要です。");
}
elseif(isset($_SERVER["PHP_AUTH_USER"])){
  $username = $_SERVER["PHP_AUTH_USER"];
  $password = $_SERVER["PHP_AUTH_PW"];

  $dsn = "mysql://urano:urano398@localhost/udb";
  $con = DB::connect($dsn);

  $s = "SELECT * FROM USERS WHERE USERNAME = ? AND PASSWORD = ?";
  $result = $con->getAll($s, array($username, $password));
  if($con->isError($result)){
    print "エラーです:" . $result->getMessage();
  }
  elseif(count($result) == 0){
    header('WWW-Authenticate: Basic realm="For Administrators Only"');
    header('HTTP/1.0 401 Unauthorized');
    die("ユーザーの認証が必要です。");
  }
  else{
    print "<p>認証に成功しました!</p>";
  }
}
?>

 この例では$dsn= のところに書いてある通り、RDBMSとしてMySQLを使いますが、 PEARのDBパッケージを使用しているので、この$dsnの記述を変えれば、 PEARがサポートしている他のRDBMSにユーザ表を作ることもできます。 この例の$dsnは、DBパッケージをご存知の方には説明は不要と思いますが、 MySQLの、このPHPスクリプトが載っているWebサーバと同じサーバ機で動作しているプロセスが管理する、 「udb」という名前のデータベースに、 データベースユーザ「urano」パスワード「urano398」で接続する、という意味になります。 また中央のSQL文の通り、「USERNAME」「PASSWORD」というカラムを(最低でも)持つ 「USERS」という表にユーザ情報を入れておくことを想定しています。 この例では、USERS表のカラムPASSWORDにパスワードをべたで格納しますが、 暗号化などをかける応用ももちろん考えられます。


PEARとPHP独自形式の認証

 PHP独自形式といっても、原理はとても基本的なものです。つまり、 ユーザ名とパスワードを入力するためのHTMLフォームを表示し、 SUBMITボタンが押されたらRDBMSなどの何らかのユーザーを管理している記憶資源にアクセスし、 パスワードの検証を行い、 OKであれば以降の文書を表示する、認証できなければ再び入力フォームを表示する、 という一連の流れをラッピングしたものが、独自形式と呼ばれているだけです。
 この一連の流れを手作りするのは少し大変そうですが、 この処理はある程度パターン化が可能なため、PHPのクラスライブラリ?であるPEARで 「Auth」としてパッケージ化が図られていますので、 これを活用しない手はありません。ということでこのページでは、 PEARのパッケージを利用した方法に特化してみます。

Authパッケージの入手とインストール
 AuthパッケージはPEAR CVSリポジトリ で最新版が入手できます。Tar+Gzip形式のアーカイブをダウンロードして展開したら、 Auth-1.0.* というディレクトリができるので、 これを $PHPHOME/pear の下に Auth という名前にして移動します。 インストール作業はこれだけ。これですぐに使えるようになります。

<?php
require_once "Auth/Auth.php";
function loginFunction($username, $status){
  global $PHP_SELF;
  print <<<FORM
<form method="post" action="$PHP_SELF">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="ログイン">
</form>
FORM;
}

$params = array(
  "dsn" => "mysql://urano:urano398@localhost/udb",
  "table" => "users",
  "usernamecol" => "username",
  "passwordcol" => "password"
);
$a = new Auth("DB", $params, "loginFunction");
$a->start();

if($a->getAuth()){
  print $a->getUsername();
  print "<p>ログインに成功しました。おめでとうさん。</p>";
}
?>

 Authパッケージは、デフォルトでパスワードをRDBMSの特定のカラムに「MD5」変換をかけて格納することを想定していますが、 MySQLのmd5関数はPHPのmd5関数と出力結果に差があるようで、 うまく認証できません。 そこで、AuthパッケージのAuth/Container/DB.php の関数fetchData()の中を改造して、パスワードをRDBMSにべたで格納できるようにしています。 改造箇所は次のところです。

function fetchData($username, $password)
{
   # 中略...
   if (is_array($entry)) {
#    if ($entry[$this->options['passwordcol']] == md5($password)) {
     if ($entry[$this->options['passwordcol']] == $password) {
       Auth::setAuth($entry[$this->options['usernamecol']]);
       $res->free();
       return true;
     } else {
   # 中略...
}

Open Source Web Architecture Top

(first uploaded 2002/07/16 last updated (not ever), URANO398)

テレワークならECナビ Yahoo 楽天 LINEがデータ消費ゼロで月額500円〜!
無料ホームページ 無料のクレジットカード 海外格安航空券 海外旅行保険が無料! 海外ホテル