Zend_Auth

http://framework.zend.com/manual/ja/zend.auth.html

事前のフローを決めておくと

/user/login

でログインフォームを表示し、 認証に成功したら

/user/view

に移動するものとする。

userコントローラとloginおよびviewアクションを事前定義しておくものとする。

事前準備

バージョンは1.11.11

% zf create project auth
% cd auth
% zf create controller user 
% zf create action login user
% zf create action view user

コントローラ名はcanonicalにしろとかいわれるが、まあそれはともあれ。

public/.htaccessでdevelopment環境変数をつっこんでおく

# ↓これ
SetEnv APPLICATION_ENV development
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]

で、認証バックエンドというかストレージにはsqlite3を利用する。

DB格納先

% mkdir -p data/db 

初期データを以下のように定義しておく。docs/init.sqliteに置いておくと仮定(場所はどこでもよい)

DROP TABLE IF EXISTS users;
CREATE TABLE users (
    id INTEGER  PRIMARY KEY
  , login VARCHAR(255) NOT NULL UNIQUE
  , password VARCHAR(255) NOT NULL
  , email VARCHAR(255) NOT NULL UNIQUE
  , dispname VARCHAR(255)
);
INSERT INTO users VALUES (NULL, "test1" , "098f6bcd4621d373cade4e832627b4f6" , "test1@example.com", "テスト1");
INSERT INTO users VALUES (NULL, "test2" , "098f6bcd4621d373cade4e832627b4f6" , "test2@example.com", "テスト2");
INSERT INTO users VALUES (NULL, "test3" , "098f6bcd4621d373cade4e832627b4f6" , "test3@example.com", "テスト3");

「098f6bcd4621d373cade4e832627b4f6」とは、単純にすなわち「test」のmd5ハッシュである。

設定

% zf configure db-adapter 'adapter=PDO_SQLITE&dbname=APPLICATION_PATH "/../data/db/auth.sqlite"'
% sqlite3 data/db/auth.sqlite < docs/init.sql

今回はDbTableをアダプタに利用するため、models/DbTableも作成しておく

% zf create db-table.from-database

ログインフォーム作る

フォームジェネレータを使ってもいいが、ここは自前で作るぞ、と。

application/views/scripts/user/login.phtml

<form method="post" action="<?php echo $this->url(array('action' => 'login'))?>">
 
  <div>
    <label for="user-login">Login</label>
    <input id="user-login" name="login"/>
  </div>
 
  <div>
    <label for="user-password">Password</label>
    <input id="user-login" name="password"/>
  </div>
 
  <input type="submit" value="Login" />
 
</form>

これで、ログインフォームが描かれるわけだが、postされた時の処理を入れておく。

application/controllers/UserController.php

    public function loginAction()
    {
        if ($this->getRequest()->isPost()) {
            Zend_Debug::dump($_POST); exit;
        }
        // action body
    }

まあこれで大体準備はOKやろうか。

Zend_Auth(_Adapter_DbTable) の処理を書く

さて、isPost()で、POSTされてきた値が取得できるはず。今回は「login」および「password」という値を投げているので、これを取得する。

    public function loginAction()
    {
        if ($this->getRequest()->isPost()) {
            $login    = $this->getRequest()->getPost('login');
            $password = $this->getRequest()->getPost('password');
            Zend_Debug::dump($_POST); exit;
        }

無論の事ながら、空がポストされた時は100%エラーになるだろうと。従ってそういった場合は適時validationを入れるなり、フロントエンドでチェックするなりといった処理が必要であろうとは思う、が、ここでは関知しない。

今回はデータベースを用いた認証とするため、Zend_Auth_Adapter_DbTableを積極的に利用する。 http://framework.zend.com/manual/ja/zend.auth.adapter.dbtable.html

このクラスはDb Adapterを生成しておき、それに対してどうこうするブツなのだが、(ここまででやったように)configの設定がキまっているのであれば内部的にgetDefaultAdapter()で取れるようになっているのであまり意識しなくてよい、と。

ま、ともあれやってみる。

        if ($this->getRequest()->isPost()) {
            $login    = $this->getRequest()->getPost('login');
            $password = $this->getRequest()->getPost('password');
 
            $adapter = new Zend_Auth_Adapter_DbTable();
            $adapter->setTableName('users'); // テーブルの指定
            $adapter->setIdentityColumn('login'); // IDのカラム
            $adapter->setCredentialColumn('password'); // パスワードのカラム
            $adapter->setIdentity($login); // IDの値
// ...

さて、ここでpasswordの値が入っていないので、与える。今回はmd5ハッシュを与えているのでphp側でハッシュしてぶつける事にする。credentialTreatmentというメソッドでDBエンジン自体のファンクションを利用する事もできるのだが、sqliteにはそんなもンない、と。

            $hash = md5($password);
            $adapter->setCredential($hash);

さて、準備は整った。いざauthenticationしてみよう。

            $auth     = Zend_Auth::getInstance();
            $result   = $auth->authenticate($adapter);
            Zend_Debug::dump($result);

なんとこれだけ。さらに

            $auth   = Zend_Auth::getInstance();
            $result = $auth->authenticate($adapter);
            Zend_Debug::dump($adapter->getResultRowObject()); 

などとして結果のrowを取得する事も可能。

結果

object(stdClass)#44 (5) {
  ["id"] => string(1) "1"
  ["login"] => string(5) "test1"
  ["password"] => string(32) "098f6bcd4621d373cade4e832627b4f6"
  ["email"] => string(17) "test1@example.com"
  ["dispname"] => string(10) "テスト1"
}

なお、空のIDを入れるとexceptionが発生する。しかしこれはここでcatchするほどのものでは無いのかもしれない。。(その前にvalidationをキめておくべきやろうな..)

authentication以降の処理

認証しっぱなしというわけにはもちろん行かないので、認証成功後の処理などを書いたりする。

            if ($result->isValid()) {
                // Authentication success
                $storage = $auth->getStorage();
                // $row = $adapter->getResultRowObject(); // これだとパスワードフィールドが取れて邪魔
                $row = $adapter->getResultRowObject(
                    array('id', 'login', 'email', 'dispname')); // こうする事でパスワード以外を指定している
                $storage->write($row);
            }

上記のように、Zend_AuthのインスタンスにはgetStorage()というメソッドが付いってきて、このインスタンスに対して(stdClassを)writeすれば、認証情報を別ページなどでも持ち回せるというわけですな。getStorageは別段特別なものでもなく、単純にZend_Sessionでしかない。名前空間にZend_Authを利用しているが、何のこっちゃと思う人は$_SESSIONでもダンプしてみよう。

Zend_Sessionなので呼び出しもZend_Sessionから出来なくもないが作法としてはZend_Authのインスタンスから利用すべき。

後ほどreadの方法も書いてみます。

さらに複雑な認証

先程のDBを拡張し、is_suspendなるフィールドを定義してみた。

DROP TABLE IF EXISTS users;
CREATE TABLE users (
    id INTEGER  PRIMARY KEY
  , login VARCHAR(255) NOT NULL UNIQUE
  , password VARCHAR(255) NOT NULL
  , email VARCHAR(255) NOT NULL UNIQUE
  , dispname VARCHAR(255)
  , is_suspended BOOLEAN
);
 
INSERT INTO users VALUES (NULL, "test1" , "098f6bcd4621d373cade4e832627b4f6" , "test1@example.com", "テスト1", 0);
INSERT INTO users VALUES (NULL, "test2" , "098f6bcd4621d373cade4e832627b4f6" , "test2@example.com", "テスト2", 1);
INSERT INTO users VALUES (NULL, "test3" , "098f6bcd4621d373cade4e832627b4f6" , "test3@example.com", "テスト3", 0);

「test2」のみis_suspended = 1とし、アカウント停止状態を表現してみた例。

これを認証させないようにするには、以下のようにwhere条件を加える事で可能となる

            $adapter = new Zend_Auth_Adapter_DbTable();
            $adapter->setTableName('users');
            $adapter->setIdentityColumn('login');
            $adapter->setCredentialColumn('password');
            $adapter->setIdentity($login);
            $select = $adapter->getDbSelect();
            $select->where('is_suspended != 1'); // <- これ

しかし、これには1つ問題があり、認証をぶつける時に単純にwhere条件で除外されるため、たとえばtest2を認証しようとすると「そんなユーザ知らん」と言われるわけ。すなわち、ユーザがDBにいなかった時と同じ扱いになるわけだから「suspendされてるからログインできません」的な切り分けは不能。単純に「ログインに失敗しました」くらいのメッセージしか出せないというわけだ。

そういうわけで、suspendされているかどうかを見るには一度認証を通したのち、rowを精査した方がいいのかもしれない。 この辺はどういう運用にするか次第。ただ、問題なくauthenticate()ではinvalidになる。

複数のテーブルに跨がる認証

一言で言うとできないっぽい。。ネットを見る限りだと「そういうのはview使ったらどうか」みたいな感じだった。なのでそういうニーズがあるならVIEW(sqlのね)を使ってみては。。。 なお、storageに仕込む時に再度fetchするとかいうのであればそれは問題なくできるんでしょうがね。

最終的に

認証情報を詰めこんだ後viewへリダイレクトする、と。

なお、セッションのライフタイム等に関しては「Zend_Session」に完全に依存するので、そちらを参考の事。 ちなみに、viewへリダイレクトしてもその先で認証チェックなどはかけていない。すなわち現状ではloginしなくてもviewにアクセスできてしまうのである。これに関して、単純なものであればセッションの有無を見ればよいかとは思うが、複雑なものに関してはZend_Aclを参照せよ

(スタブw)

zendframework/zend_auth.txt · 最終更新: 2012/05/05 00:51 by admin
www.chimeric.de Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0