Zend Framework入門

第2章 Zend Frameworkの基本

前へ | 目次へ |次へ  | Yamada-Lab

2.5 セキュリティの基本

 Webアプリケーションでのセキュリティ機能の大きな役割として認証(Authentication)と認定(Authorization)があります。認定はアクセス制御(Access Contorol)ともいいます。認証は、ユーザの特定を主にユーザ名とパスワードで行います。アクセス制御は、ユーザが特定のリソースにアクセス可能かどうかを制御するものです。

 認証とアクセス制御は一般に以下の手順で実施します。

■認証
@ユーザが認証済みかどうかを確認する
A未確認である場合はログインフォーム(ユーザ名とパスワード入力用)を表示する
Bログインフォームからのデータを受信する
C受信データに基づきユーザが認証処理を行う
D認証に成功した場合は、アクセス制御に移行する
■アクセス制御
@ユーザがアクセス可能かどうかを確認する
Aアクセス可能の場合は、要求されたのリソースにアクセスする
Bアクセスが許容されていない場合は、再度認証に戻る

2.5.1 認証

 Zend FrameworkではZend_Authコンポーネントが認証機能を提供します。認証方式には、ユーザのデータをどのように管理するかでいくつかの方式があります。まず、Webアプリケーション側でデータベースで管理するデータベース認証方式の基本について述べます。

データベース認証

 データベースにユーザ情報を管理するユーザテーブルtbl_userを作成しておきます。

フィールド名 データ型 概要
user_id CHAR(8)

ユーザ名(半角英数字:必須:主キー)

password VARCHAR(32) パスワード(半角英数字:必須)
name

VARCHAR(64)

ユーザ名(漢字)
roles VARCHAR(64) ユーザ権限

 一般に、user_idフィールドとpasswordフィールドは認証用に必須です。その他nameフィールドやrolesフィールドなどはアプリケーションごとに必要に応じて追加します。

 ユーザテーブルtbl_userの生成用SQLは以下のようになります。サンプルユーザデータを3件登録しています。ここで、パスワードは暗号化して登録します。暗号化には一般にMD5ハッシュ値を用います。PHPでは標準のMD5()関数を利用します。

暗号化しないで登録すると、システム運用者などに盗み見られる可能性を残すことになり、セキュリティ上好ましくありません。

CREATE TABLE `db_zend`.`tbl_user` (
`user_id` CHAR( 8 ) NOT NULL ,
`password` VARCHAR( 32 ) NOT NULL ,
`name` VARCHAR( 64 ) NOT NULL ,
`roles` VARCHAR( 64 ) NULL ,
PRIMARY KEY ( `user_id` )
) ENGINE = InnoDB

INSERT INTO `db_zend`.`tbl_user` (
`user_id` ,
`password` ,
`name` ,
`roles`
)
VALUES (
'administrator', '8117d1f5cb79fdab5ee9a293578245bd ', '管理者', 'administrator'
), (
'yamada', '8117d1f5cb79fdab5ee9a293578245bd ', '山田太郎', 'member'
);
VALUES (
'aoki', '8117d1f5cb79fdab5ee9a293578245bd ', '青木和夫', 'guest'
);

 データベース認証のサンプルスクリプトを以下に示します。

c:\Apache2.2\zendapps\zf\ac\01\auth01.php

<?php
// Zend_Authコンポーネントのロード
require_once 'Zend/Auth.php';
// Zend_Auth_Adapter_DbTableコンポーネントのロード
require_once 'Zend/Auth/Adapter/DbTable.php';
//アプリケーションルートパスの定義
define('APP_DIR','../../../../zendapps/ac/01/');
//データベース接続クラス(ユーザ定義)のロード
require_once APP_DIR . 'DbManager.class.php';

//Zend_Authオブジェクト(インスタンス)を生成
$auth = Zend_Auth::getInstance();

// カレントユーザが認証済みかどうかを確認
if(!$auth->hasIdentity()) {
  // データベースに接続
  $db = DbManager::getConnection();
  // DbTableアダプタを生成
  $adapter = new Zend_Auth_Adapter_DbTable(
        $db      // DBアダプタクラス
       ,'tbl_user'    // ユーザテーブル名
       ,'user_id'    // ユーザ名フィールド名
       ,'password'    // パスワードフィールド名
       ,'MD5(?)'     // パスワード暗号化式
      );
  // 認証のためのユーザ名、パスワードをセット
  $adapter->setIdentity('yamada')
      ->setCredential('pass6789');
  // 認証処理を実行
  $result = $auth->authenticate($adapter);
  // 認証の成否を確認
  if(!$result->isValid()) {
    // 認証に失敗した場合はエラーメッセージを表示
    foreach($result->getMessages() as $msg) {
      print($msg.'<br />');
    }
  } else {
    // 認証に成功した場合は、必要な情報を取得
    $contents = $adapter->getResultRowObject(NULL,'password');
    // セッションに登録
    $auth->getStorage()->write($contents);
    print('認証に成功しました:'.$auth->getIdentity()->user_id);
  }
} else {
  print('認証済みです。<br />');
  print_r($auth->getIdentity());
}

データベース接続のコードは、DbManager.class.phpクラスとして外部化し、簡潔にデータベースに接続できるようにします。

c:\Apache2.2\htdocs\zf\ac\01\DbManager.class.php

<?php
//Zend_Configコンポーネントの呼び出し
require_once 'Zend/Config/Ini.php';
//Zend_Dbコンポーネントの呼び出し
require_once 'Zend/Db.php';

class DbManager {
  public static function getConnection() {
    $db = NULL;
    try {
      //外部ファイルzend.iniをロードしZend_Configオブジェクトを生成
      $config = new Zend_Config_Ini('../../../../zendapps/ac/zend.ini','db_zend');
      //Zend_Configオブジェクトを使って、アダプタクラスを生成
      $db = Zend_Db::factory($config->db);

      //データベース接続時の文字コードをutf8に設定
      $db->query('SET CHARACTER SET utf8');
    } catch (Zend_Exception $e) {
      //例外発生時のエラーメッセージを表示
      die($e->getMessage());
    }
    return $db;
  }
}

 設定ファイルzend_db.iniは以下の内容です。

c:\Apache2.2\zendapps\ac\zend_db.ini

[db_zend]
db.adapter = Pdo_Mysql
db.params.host = localhost
db.params.dbname = db_zend
db.params.username = webapl
db.params.password = pass1234

 Zend_authコンポーネントの基本を示すために、ここでは上記に示した認証手順のうちの@ユーザが認証済みかどうかを確認するとC受信データに基づきユーザが認証処理を行う、のみのスクリプトを示しました。

 サンプルスクリプトにアクセスした結果を示します。

■基本認証

 基本認証では、以下のようなパスワードファイルhttp.txtを用意しておく必要があります。

c:\Apaxhe2.2\zendapps\zf\ac\02\http.txt

administrator:MyAuth:pass6789
yamada:MyAuth:pass6789
aoki:MyAuth:pass6789

 パスワードファイルの各行の構文は次のとおりです。パスワードは暗号化しないで平文のままです。

ユーザ名:レルム値:パスワード\n

 この基本認証アプリケーションbasic_authをMVCモデルでコード化した例を以下に示します。ここでは、ディレクトリ構造を以下のようにしています。

c:\Apache2.2\htdocs\zf\ac\02
  .htaccess          
  index.php   
       
c:\Apache2.2\zendapps\ac
  \02
    http.txt
    \controllers        
      HttpController.php

公開ディレクトリ
Rewriteエンジン制御ファイル
フロントコントローラファイル

アプリケーションディレクトリ
アプリケーションルートディレクトリ
パスワードファイル
コントローラディレクトリ
httpアクションコントローラファイル

c:\Apache2.2\htdocs\zf\ac\02\.htaccess

RewriteEngine on
RewriteBase /zf/ac/02
RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php

 フロントコントローラindex.phpファイルは次のようにします。

c:\Apache2.2\htdocs\zf\ac\02\index.php

<?php
//アプリケーションルートパスの定義
define('APP_DIR','../../../../zendapps/ac/02/');
//フロントコントローラ用のコンポーネントをロード
require_once 'Zend/Controller/Front.php';
/**フロントコントローラのインスタンスの取得
*コントローラのフォルダ指定
*ディスパッチ
*/
Zend_Controller_Front::run(APP_DIR . 'controllers');

 HttpアクションコントローラファイルHttpController.phpは次のようになります。

c:\Apache2.2\zendapps\zf\ac\02\controllers\HttpController.php

<?php
// Zend_Authコンポーネントのロード
require_once 'Zend/Auth.php';
//
require_once 'Zend/Controller/Action.php';
//
require_once 'Zend/Auth/Adapter/Http.php';
//
require_once 'Zend/Auth/Adapter/Http/Resolver/File.php';

class HttpController extends Zend_Controller_Action {
  // http/indexアクションの定義
  public function indexAction() {
    // 自動レンダリングモードを無効化
    $this->_helper->ViewRenderer->setNoRender();
    //Zend_Authオブジェクト(インスタンス)を生成
    $auth = Zend_Auth::getInstance();

    // カレントユーザが認証済みかどうかを確認
    if(!$auth->hasIdentity()) {
      // 未認証の場合の処理
      // リクエストオブジェクトの生成
      $req = $this->getRequest();
      // レスポンスオブジェクトの生成
      $res = $this->getResponse();
      // パスワードファイル読み込みのためのリゾルバを取得
      $resolver
        = new Zend_Auth_Adapter_Http_Resolver_File(APP_DIR.'http.txt');

      // Httpアダプタを生成
      $adapter = new Zend_Auth_Adapter_Http(
              array('accept_schemes' => 'basic'
                 ,'realm' => 'MyAuth')
             );

      // 使用するリゾルバ
      $adapter->setBasicResolver($resolver);
      // 使用するリクエストオブジェクト
      $adapter->setRequest($req);
      // 使用するレスポンスオブジェクト
      $adapter->setResponse($res);

      // 認証処理を実行
      $result = $auth->authenticate($adapter);
      // 認証の成否を確認
      if(!$result->isValid()) {
        // 認証に失敗した場合はエラーメッセージを表示
        foreach($result->getMessages() as $msg) {
          $res->appendBody($msg.'<br />');
        }
      } else {
        // 認証に成功した場合は、必要な情報を取得
        $data = $auth->getIdentity();
        $res->setBody('認証に成功しました:'.$data['username']);
      }
    } else {
      // 認証済みの場合の処理
      $data = $auth->getIdentity();
      // $res->setBody('認証済みです:'.$data['username']);
      print('認証済みです:'.$data['username']);
    }
  }
}

 「http://localhost:8080/zf/ac/02/http」にアクセスした結果を示します。

■認証機能のプラグイン化

 実用アプリケーションでは、認証機能をプラグイン化して使用します。プラグイン化することで、フロントコントローラがアクションコントローラにディスパッチする前に認証の是非を確認し、未承認の場合は自動的にログインフォームにリダイレクトさせます。認証が確認されれば、ユーザがアクセスしたもともとのアクションにリダイレクトします。

c:\Apache2.2\htdocs\zf\ac\02
  .htaccess          
  index.php   
       
c:\Apache2.2\zendapps\ac
  \02
    AuthPlugin.class.php
    DbManager.class.php
    MyFunction.class.php
    \controllers        
      IndexController.php
      AuthController.php
    \views
      \index
        index.phtml
      \auth
        index.phtml

公開ディレクトリ
Rewriteエンジン制御ファイル
フロントコントローラファイル

アプリケーションディレクトリ
アプリケーションルートディレクトリ
認証処理用クラス
データベース接続用クラス
ユーザ定義関数クラス
コントローラディレクトリ
デフォルトアクションコントローラ
認証用アクションコントローラ
ビュースクリプト用ディレクトリ
デフォルトアクションコントローラ用ディレクトリ
認証確認画面用ビュースクリプト
authアクションコントローラ用ディレクトリ
ログイン画面用ビュースクリプト

 フロントコントローラは次のようにします。

c:\Apache2.2\htdocs\zf\ac\03\index.php

<?php
//アプリケーションルートパスの定義
define('APP_DIR','../../../../zendapps/ac/03/');
//公開ディレクトリのフルパスの定義
define('BASE_DIR','/zf/ac/03/');
//AuthPlugin.classクラス(ユーザ定義)のロード
require_once APP_DIR.'AuthPlugin.class.php';
//データベース接続クラス(ユーザ定義)のロード
require_once APP_DIR.'DbManager.class.php';
//ユーザ定義関数クラスのロード
require_once APP_DIR.'MyFunction.class.php';
//フロントコントローラ用のコンポーネントをロード
require_once 'Zend/Controller/Front.php';
//Zend_Controller_Frontオブジェクト(インスタンス)生成
$front = Zend_Controller_Front::getInstance();
//アクションコントローラのディレクトリ設定
$front->setControllerDirectory(APP_DIR . 'controllers');
//認証用AuthPluginプラグインを登録
$front->registerPlugin(new AuthPlugin());
//アクションコントローラへのデスパッチ
$front->dispatch();

 認証用AuthPluginプラグインを以下の構文で登録します。$frontはZend_Controller_Frontオブジェクト(インスタンス)です。

$front->registerPlugin(new AuthPlugin());

 認証用AuthPluginプラグインのサンプルスクリプトを以下に示します。

c:\Apache2.2\zendapps\zf\ac\03\AuthPlugin.class.php

<?php
require_once 'Zend/Controller/Plugin/Abstract.php';
require_once 'Zend/Auth.php';

// 認証処理を行うAuthPluginプラグインクラスを定義
class AuthPlugin extends Zend_Controller_Plugin_Abstract {
  
// デスパッチの前に認証処理
  public function dispatchLoopStartup($req) {
    
// Zend_Authオブジェクト(インスタンス)を生成
    $auth = Zend_Auth::getInstance();
    
// カレントユーザが認証済みか否かを確認
    if (!$auth->hasIdentity()) {
                         
----@
      
// 未認証の場合
      if($req->getControllerName() != 'auth' ||
        $req->getActionName() != 'process') {
                         
----C
        
// auth/processアクションの場合
        
// セッションオブジェクト(インスタンス)の生成
        $sess = new Zend_Session_Namespace('myApp');

                           ----B
        
// もともとのコントローラ名をセッションに保存
        $sess->currentController = $req->getControllerName();
                         
----B
        
// もともとのアクション名をセッションに保存
        $sess->currentAction = $req->getActionName();
        
// auth/indexアクションに強制的に変更
        $req->setControllerName('auth'); 
----A
        $req->setActionName('index');   
----A
      }
    }
  }
}

 認証用AuthPluginプラグインは、Zend_Controller_Plugin_Abstractクラスを継承したクラスとして定義します。hasIdentityメソッドでカレントユーザが認証済みか否かを確認し(@)、未認証の場合は自動的にログインフォーム(auth/indexアクション)にディスパッチ先を変更しています(A)。また、認証が確認された場合に、もともとのアクションにリダイレクトで戻れるように、もともとのコントローラ/アクション名をセッションに保存しておきます(B)。なお、未認証の場合の処理は、ログイン処理を行うauth/processアクションはスキップしておくようにしないと再度ログイン画面が表示されて、ログイン画面から抜け出せないという無限ループに陥ってしまいます。

 ここで、アプリケーション共通で使用するユーザ定義関数をMyFunctionクラスの中に集約して定義しておき、使用することとします。とりあえずアンエスケープ処理を伴うPOSTデータ受信関数(メソッド)を定義します。

c:\Apache2.2\zendapps\zf\ac\03\MyFunction.class.php

<?php
// ユーザ定義関数クラスの定義
class MyFunction {
  // アンエスケープ付ポストデータ受信
  public function myGetPost($req,$varName) {
    if(get_magic_quotes_gpc()) {
      $varValue = stripslashes(trim($req->getPost($varName)));
    } else {
      $varValue = trim($req->getPost($varName));
    }
    return $varValue;
  }
}

 AuthPluginプラグインと連動した認証用authアクションコントローラのサンプルスクリプトを以下に示します。なお、ログアウト用アクションauth/logoutも定義してあります。

c:\Apache2.2\zendapps\zf\ac\03\controllers\AuthController.php

<?php
require_once 'Zend/Auth.php';
require_once 'Zend/Auth/Adapter/DbTable.php';
require_once 'Zend/Controller/Action.php';
require_once 'Zend/Session/Namespace.php';

// 認証処理を行うauthコントローラを定義
class AuthController extends Zend_Controller_Action{
  // デフォルトアクション
  public function indexAction(){
    // 自動レンダリングモードでログインフォームを表示
    // (auth/index.phtml)
  }

  // auth/processアクションの定義
  // データベース認証方式により認証処理を行う
  public function processAction(){
    // リクエストオブジェクトの生成
    $req = $this->getRequest();
    $myFunc = new MyFunction();
    // データベース接続
    $db = DbManager::getConnection();
    // DbTableアダプタを生成
    $a_db = new Zend_Auth_Adapter_DbTable(
          $db     // DBアダプタクラス
         ,'tbl_user'  // ユーザテーブル名
         ,'user_id' // ユーザ名フィールド名
         ,'password' // パスワードフィールド名
         ,'MD5(?)' // パスワード暗号化式
        );
    // POSTメソッドでユーザ名とパスワードを受信し、設定
    $user_id = $myFunc->myGetPost($req,'user_id');
    $password = $myFunc->myGetPost($req,'password');
    if($user_id == NULL || $user_id == ''
      || $password == NULL || $password == '') {
      $res = $this->getResponse();
      $res->appendBody('<div style="color:Red">認証に失敗しました。</div>');
      $this->_forward('index', 'auth'); // アクション名、コントローラ名
    } else {
      $a_db->setIdentity($user_id)
         ->setCredential($password);
      //Zend_Authオブジェクト(インスタンス)を生成
      $auth = Zend_Auth::getInstance();
      // 認証処理を実行
      $result = $auth->authenticate($a_db);
      // 認証結果の確認
      if($result->isValid()) {
        // 認証が成功した場合
        // パスワードフィールドを除くすべてのユーザ情報を取得
        $contents = $a_db->getResultRowObject(NULL, 'password');
        // 取得したユーザ情報をセッションに保存
        $auth->getStorage()->write($contents);
        // セッションオブジェクト(インスタンス)の生成
        $sess = new Zend_Session_Namespace('myApp');
        // セッションに保存しておいたコントローラ/アクション名を取得
        $action = $sess->currentAction;
        $controller = $sess->currentController;
        $sess->currentAction = NULL;
        $sess->currentController = NULL;
        // もともとのアクションにリダイレクト
        $this->_redirect($controller.'/'.$action);
      } else {
        // 認証に失敗した場合
        $res = $this->getResponse();
        $res->appendBody('<div style="color:Red">認証に失敗しました。</div>');
        $this->_forward('index', 'auth'); // アクション名、コントローラ名
      }
    }
  }
  // auth/logoutアクションの定義
  public function logoutAction() {
    Zend_Auth::getInstance()->clearIdentity();
    $this->_redirect('/index/index');
  }
}

 auth/logoutアクションでは、index/indexアクションにリダイレクトしていますが、その前に以下により

Zend_Auth::getInstance()->clearIdentity();

認証成功時のセッション情報をすべてクリアしていますので、未認証状態に戻っていますので、実際にはAuthPluginプラグインによりログイン画面(auth/indexアクション)にリダイレクトされ、ログインフォームが表示されます。

 ログインフォーム用のatu/index.phtmlビュースクリプトは以下のようになります。

c:\Apache2.2\zendapps\zf\ac\03\views\scripts\auth\index.phtml

<html>
<head>
<title>ユーザ認証</title>
</head>
<body>
<form method="POST" action="<?php print BASE_DIR ?>auth/process">
<table border="0" align="center">
 <tr>
  <th align="right">ユーザ名:</th>
  <td><input type="text" name="user_id" size="10" maxlength="15" /></td>
 </tr><tr>
  <th align="right">パスワード:</th>
  <td><input type="password" name="password" size="10" maxlength="15" /></td>
 </tr><tr>
  <td rowspan="2"><input type="submit" name="submit" value="ログイン" /></td>
 </tr>
</table>
</form>
</body>
</html>

 認証成功時にアクセスするデフォルトアクションを以下に示します。つまり、このアクションコントローラにアクセスするためにはあらかじめ認証に成功している必要があるというわけです。

c:\Apache2.2\zendapps\zf\ac\03\controllers\IndexController.php

<?php
require_once 'Zend/Controller/Action.php';

class IndexController extends Zend_Controller_Action{
  public function indexAction() {
    // 自動レンダリングモード
    // (index/index.phtml)
  }
}

 認証成功時の画面表示のためのindex/index.phtmlビュースクリプトです。

c:\Apache2.2\zendapps\zf\ac\03\views\scripts\index\index.phtml

<html>
<head>
<title>認証に成功しました。</title>
</head>
<body>
認証に成功しました。
[<a href="<?php print BASE_DIR ?>/auth/logout">ログアウト</a>]
</body>
</html>

 「http://localhost:8080/zf/ac/03/」、つまり「http://localhost:8080/zf/ac/03/index/index」にアクセスした結果を示します。

2.5.2 アクセス制御

■Zend_Aclの基本概念

 たとえば、電子掲示板(BBS)において、記事の閲覧は誰でもできるが、BBS記事の書き込みはメンバー登録されたものができ、記事の削除は管理者のみができる、というような管理方法はよくあることです。ここで、BBS記事のようなアクセス対象を「リソース」といいます。BBS記事の閲覧・書き込み・削除などリソースへのアクセスの方法を「権限」といいます。閲覧権限を「Read権限」、書き込み権限(ここでは更新権限も含む)を「write権限」、削除権限を「delete権限」とします。また、メンバーや管理者などアクセスする主体のグループのことを「ロール」といいます。ここでは、メンバーのロールを「member」、管理者のロールを「administrator」、そして一般の閲覧者をのロールを「Guest」とします。

基本概念
概要
リソース アクセスの対象 BBSの記事
権限 アクセスの方法 閲覧権限、書き込み権限
ロール アクセス主体のグループ メンバー、管理者

 Zend_Aclでは、このリソース、権限、ロールの概念を使って次の手順でアクセス制御を行います。なお、各ユーザはシステムに登録する時点でどのロールに属するかを登録されている必要があります。

@ロールを登録
Aリソースを登録
B各ロールにアクセスを許可するリソースと権限を登録
Cカレントユーザがアクセスしたリソースに指定の権限があるか否かを判定

■ロールと権限の登録

 Zend_Aclには、ロールと権限を定義する機能があります。ロールは、権限の集合となります。ここでは、以下のようなロールと権限を定義することとします。

ロール 権限
guest read
member read,write
administrator read,write,delete

 ロールと権限を定義するサンプルスクリプトを以下に示します。

c:\Apache2.2\zendapps\zf\ac\04\acl01.php

<?php
require_once 'Zend/Acl.php';
require_once 'Zend/Acl/Role.php';
// Zend_Aclクラスのオブジェクト(インスタンス)生成
$acl = new Zend_Acl();
// guestロールの定義
$acl->addRole(new Zend_Acl_Role('guest'));
// memberロールはguestロールの権限を継承
$acl->addRole(new Zend_Acl_Role('member'),'guest');
// administratorロールはmemberロールの権限を継承
$acl->addRole(new Zend_Acl_Role('administrator'),'member');
// 各ロールに許可する権限を登録
$acl->allow('guest', NULL, 'read');
$acl->allow('member', NULL, 'write');
$acl->allow('administrator', NULL, 'delete');
// guestロールの権限を確認
print('guest.read:');
print($acl->isAllowed('guest', NULL, 'read') ? "許可" : "禁止");
print('<br />guest.read:');
print($acl->isAllowed('guest', NULL, 'write') ? "許可" : "禁止");

 ロールの登録のaddRoleメソッドを使用します。構文は次のとおりです。$aclはZend_Aclクラスのオブジェクト(インスタンス)です。

$acl->addRole(new Zend_Acl_Role('ロール名'),['継承元のロール名']);

【例】

$acl->addRole(new Zend_Acl_Role('member'),'guest');

 ロール名は、任意です。第2引数に、継承元のロール名を指定すると、継承元の権限をそのまま引き継ぐことができます。

 リソースの登録は、必須ではありません。すべての画面に対して同一のアクセス制御を行うのであれば、あえてリソースの定義を省略できます。その場合、このあと出てくるリソースの引数の値は「NULL」にしておきます。つまりリソースの値がNULLの場合、すべてのリソースを対象にするという意味になります。

 ロールにアクセスを許可するリソースと権限を登録はallowメソッドを使います。構文は次のとおりです。$aclはZend_Aclクラスのオブジェクト(インスタンス)です。

$acl->allow('ロール名',['リソース名'],['権限']]);

【例】

$acl->allow('member', NULL, 'write');

 リソース名、権限の名称は任意です。サンプルスクリプトでは、guestロールにread権限を、memberロールにwrite権限を、adminstratorロールにはDelete権限を登録しています。ただし、memberロールはguestロールを継承しているので、結果としてwrite権限のほかにread権限も有していることになります。同様にadminstrator権限は、delete権限のほかにwrite権限とread権限を有していることになります。なお、リソース名はNULLなので、全てのリソースに対して以上の権限が適用されます。

 指定のロールが指定のリソースに対して指定の権限を有するかどうかを判定する場合はisAllowedメソッドを使用します。構文は次のとおりです。

$acl->isAllowed('ロール名',['リソース名'],['権限']]);

【例】

$acl->isAllowed('member', NULL, 'write');

 サンプルスクリプトにアクセスした結果は次のようになります。

■リソースごとの権限の登録

 リソースごとの権限の登録サンプルスクリプトを以下に示します。リソースとして、公開用画面の「open」リソースと編集用画面の「edit」を登録しています。

c:\Apache2.2\zendapps\zf\ac\05\acl02.php

<?php
require_once 'Zend/Acl.php';
require_once 'Zend/Acl/Role.php';
require_once 'Zend/Acl/Resource.php';
// Zend_Aclクラスのオブジェクト(インスタンス)生成
$acl = new Zend_Acl();
// guestロールの定義
$acl->addRole(new Zend_Acl_Role('Guest'));
// memberロールはguestロールの権限を継承
$acl->addRole(new Zend_Acl_Role('member'),'guest');
// administratorロールはmemberロールの権限を継承
$acl->addRole(new Zend_Acl_Role('administrator'),'member');
// リソースを登録
$acl->add(new Zend_Acl_Resource('open'));
$acl->add(new Zend_Acl_Resource('edit'));
// 各ロールに許可する権限を登録
$acl->allow('guest', 'open', 'read');
$acl->allow('member','edit', 'write');
$acl->allow('administrator','edit', 'delete');
// guestロールの権限を確認
print('guest.read(open):');
print($acl->isAllowed('guest', 'open', 'read') ? "許可" : "禁止");
print('<br />guest.write(edit):');
print($acl->isAllowed('guest', 'edit', 'write') ? "許可" : "禁止");

 リソースの登録には、addメソッドを使用します。構文は次のとおりです。$aclはZend_Aclクラスのオブジェクト(インスタンス)です。

$acl->add(new Zend_Acl_Resource('リソース名'),['継承元のリソース名']);

【例】

$acl->add(new Zend_Acl_Resource(open'));

 ロール名は、任意です。第2引数に、継承元のロール名を指定すると、継承元の権限をそのまま引き継ぐことができます。

 サンプルスクリプトにアクセスした結果は次のようになります。

■アクセス制御のためのアクションヘルパー

 Zend_Aclは、ロール、権限、リソースの登録を行いますが、これらの情報を使って具体的なアクセス制御を行うのはアプリケーション側にゆだねられています。アクションコントローラ側でアクセス制御を行うためにアクションヘルパーを利用すると便利です。

 ディレクトリとファイル構成は以下とします。

c:\Apache2.2\htdocs\zf\ac\06
  .htaccess          
  index.php   
       
c:\Apache2.2\zendapps\ac
  \06
    AuthPlugin.class.php
    DbManager.class.php
    MyFunction.class.php
    \controllers
      CheckController.php
      AuthController.php
      \helpers       
        AclCheck.php 
    \views
      \auth
        index.phtml

公開ディレクトリ
Rewriteエンジン制御ファイル
フロントコントローラファイル

アプリケーションディレクトリ
アプリケーションルートディレクトリ
認証処理用クラス
データベース接続用クラス
ユーザ定義関数クラス
コントローラディレクトリ
アクセス制御用アクションコントローラ
認証用アクションコントローラ
アクションヘルパー用ディレクトリ
AclCheckアクションヘルパー
ビュースクリプト用ディレクトリ
authアクションコントローラ用ディレクトリ
ログイン画面用ビュースクリプト

 ここでは、アクセスしたリソース(アクションコントローラ/アクション)に対してカレントユーザがリソース側で登録された権限を有しているか否かを確認するAclCheckヘルパーのサンプルスクリプトを示します。ユーザ定義のアクションヘルパークラスを定義するファイルは「\controllers」の下に「\helpers」サブディレクトリを作成し、その中に配置することとします。

c:\Apache2.2\zendapps\zf\ac\06\controllers\helpers\AclCheck.php

<?php
require_once 'Zend/Acl.php';
require_once 'Zend/Acl/Role.php';
require_once 'Zend/Acl/Resource.php';
require_once 'Zend/Auth.php';
require_once 'Zend/Controller/Action/Helper/Abstract.php';

// アクセス制御を行うAclCheckヘルパークラスを定義
class My_Action_Helper_AclCheck extends Zend_Controller_Action_Helper_Abstract {
  // Zend_Aclオブジェクト
  private $_acl;

  // コンストラクタ
  public function __construct() {

    // アクセス制御リストを定義
    // Zend_Aclクラスのオブジェクト(インスタンス)生成
    $this->_acl = new Zend_Acl();
    // ロールguestの定義
    $this->_acl->addRole(new Zend_Acl_Role('guest'));
    // ロールmemberはロールguestの権限を継承
    $this->_acl->addRole(new Zend_Acl_Role('member'),'guest');
    // ロールadministratorはロールmemberの権限を継承
    $this->_acl->addRole(new Zend_Acl_Role('administrator'),'member');
    // リソースを登録
    $this->_acl->add(new Zend_Acl_Resource('open'));
    $this->_acl->add(new Zend_Acl_Resource('edit'));
    // 各ロールに許可する権限を定義
    $this->_acl->allow('guest', 'open', 'read');
    $this->_acl->allow('member', 'edit', 'write');
    $this->_acl->allow('administrator', 'edit', 'delete');
  }

  // デフォルトメソッドを定義
  public function check($resource,$priv) {
    /*
    * $resource :アクセス対象のリソース
    * $priv :要求される権限
    */
    // カレントユーザの権限を取得
    $auth = Zend_auth::getInstance();
    // userテーブルのrolesフィールドの値を取得
    $roles = explode(',',$auth->getIdentity()->roles);
    // アクセスを許可するか否かを示すフラグ変数
    $flag = FALSE;
    // rolesフィールドから順にロールを取得
    for($i = 0; $i < count($roles); $i++) {
      $role = trim($roles[$i]);
      if($this->_acl->isAllowed($role,$resource,$priv)) {
        $flag = TRUE;
      }
    }
    // フラグ変数がFALSEの場合、アクセス禁止メッセージを表示
    if(!$flag) {
      $res = $this->getResponse();
      $res-> setHttpResponseCode(403);
      $res-> setBody('アクセスが拒否されました');
      $res-> sendResponse();
      exit();
    }
    return;
  }

  // デフォルトメソッドを呼び出すためのdirectメソッドを定義
  public function direct($resource,$priv) {
    return $this->check($resource,$priv);
  }
}

AclCheckアクションヘルパー内で、ロール名、リソース名、権限に登録されている名称以外の名称が参照されると、Zend Frameworkシステムの致命的なエラーが発生します。たとえば、「member」を「Member」としたり、「edit」を「Edit」としたりすると致命的なエラーが発生します。

 フロントコントローラは次のように記述します。

c:\Apache2.2\htdocs\zf\ac\06\index.php

<?php
//アプリケーションルートパスの定義
define('APP_DIR','../../../../zendapps/ac/06/');
//公開ディレクトリのフルパスの定義
define('BASE_DIR','/zf/ac/06/');
//AuthPlugin.classクラス(ユーザ定義)のロード
require_once APP_DIR.'AuthPlugin.class.php';
//データベース接続クラス(ユーザ定義)のロード
require_once APP_DIR.'DbManager.class.php';
//ユーザ定義関数クラスのロード
require_once APP_DIR.'MyFunction.class.php';
//フロントコントローラ用のコンポーネントをロード
require_once 'Zend/Controller/Front.php';
//Zend_Controller_Frontオブジェクト(インスタンス)生成
$front = Zend_Controller_Front::getInstance();
//アクションコントローラのディレクトリ設定
$front->setControllerDirectory(APP_DIR . 'controllers');
//認証用AuthPluginプラグインを登録
$front->registerPlugin(new AuthPlugin());
//アクションヘルパーの配置先を登録
Zend_Controller_Action_HelperBroker::addPath(
   APP_DIR . 'controllers/helpers','My_Action_Helper');

//アクションコントローラへのデスパッチ
$front->dispatch();

 以下の構文で、アクションヘルパークラスを定義したファイルへのパスを通すことで、アクションヘルパーが有効になります。

Zend_Controller_Action_HelperBroker::addPath(
   APP_DIR . 'controllers/helpers','My_Action_Helper');

 以上のようにアクセス制御用のAclCheckアクションヘルパーを準備した上で、アクセス制御が必要なアクション(メソッド)の中で、アクセス制御を実施するスクリプトを記述します。

c:\Apache2.2\zendapps\zf\ac\06\controllers\CheckController.php

<?php
require_once 'Zend/Controller/Action.php';

class CheckController extends Zend_Controller_Action {
  public function indexAction() {
    // AclCheckヘルパーのデフォルトメソッドcheckの呼び出し
    // (このアクションにアクセスするには、カレントユーザが
    //  editリソースのwrite権限を有していることが必要)
    $this->_helper->AclCheck('edit','write');
    // 自動レンダリングモードを無効化
    $this->_helper->ViewRenderer->setNoRender();
    // アクセス成功のメッセージ表示
    $res = $this->getResponse();
    $res->setBody('アクセスに成功しました。');
  }
}

 アクセス制御は、以下の構文により行います。

$this->_helper->AclCheck('このアクションのリソース名','要求される権限');

【例】

$this->_helper->AclCheck('edit', 'write');

 上記のサンプルスクリプトでは、check/indexアクションのリソース名を、AclCheckアクションヘルパー内で登録済みのリソース名の一つである「edit」とし、要求される権限を「これも登録済みである「write」としています。

 サンプルスクリプトへのアクセス結果を以下に示します。まず、yamadaユーザでログインしてみます。

 yamadaユーザは、ロールがtbl_userテーブルのrolesカラムで「member」として登録してあり、memberロールは、editリソースに対してwrite権限を持つと登録されているので、次のように、check/indexアクションにはアクセス制御の結果、check/indexアクションへのアクセスに成功します。

 次に、aokiユーザでログインしてみます。

 aokiユーザはguestロールで登録されており、guestロールはopenリソースに対するread権限しかなく、editリソースのwrite権限は有していません。したがって、aokiユーザはアクセス制御の結果、check/indexアクションへのアクセスに失敗します。

 なお、administratorユーザはadministratorロールで登録されており、administratorロールはmemmberロールを継承していますので、check/indexアクションへのアクセスに成功します。

■電話帳アプリケーションへのアクセス制御の導入

ディレクトリおよびファイル構成は以下のようにします。

c:\Apache2.2\htdocs\zf\ac\07
  .htaccess          
  index.php
  \css
    common.css   
       
c:\Apache2.2\zendapps\ac
  zend.ini
  \07
    DbManager.class.php
    MySmarty.class.php
    MyValidate.class.php
    MyPager.class.php
    MyFunction.class.php
    AuthPlugin.class.php
    \controllers        
      IndexController.php
      AuthController.php
      \helpers       
        AclCheck.php     
    \models   
      TelModel.class.php
    \views
      \smarty
        \templates
          head.tpl
          open-list.tpl
          edit-list.tpl
          input.tpl
          \index
            index.tpl
            search.tpl
          \edit
            index.tpl 
            search.tpl
            pre-update.tpl
            pre-insert.tpl
            pre-delete.tpl
          \auth
            index.tpl
        \templates_c
   \templates
   \templates_c  

公開ディレクトリ
Rewriteエンジン制御ファイル
フロントコントローラファイル
スタイルシート用ディレクトリ
共通スタイルシートファイル

アプリケーションディレクトリ
設定ファイル
アプリケーションルートディレクトリ
データベース接続用クラス
MySmartyクラス
MyValidateクラス
MyPagerクラス
ユーザ定義関数クラス
認証処理用クラス
コントローラディレクトリ
デフォルトアクションコントローラファイル
認証用アクションコントローラ
アクションヘルパー用ディレクトリ
AclCheckアクションヘルパー
モデル用ディレクトリ
電話帳モデル
ビュー用ディレクトリ
Smarty用ディレクトリ
テンプレート用ディレクトリ
head用共通テンプレート
公開リスト用共通テンプレート
編集リスト用共通テンプレート
入力用共通テンプレート
indexアクションコントローラ用ディレクトリ
index/indexアクション用テンプレート
index/searchアクション用テンプレート
editアクションコントローラ用ディレクトリ
indexアクションコントローラ用ディレクトリ
edit/searchアクション用テンプレート
edit/updateアクション用テンプレート
edit/pre-insertアクション用テンプレート
edit/pre-deleteアクション用テンプレート
authアクションコントローラ用ディレクトリ
ログイン画面用テンプレート
コンパイル済みテンプレート用ディレクトリ
テンプレート用ディレクトリ(デフォルト)
コンパイル済みテンプレート用ディレクトリ(デフォルト)

 MVCモデルにおける相互関連は、次のようになります。

アクションコントローラ
/アクション(C)
モデル(M) ビュー(V)
(->:フォアワード)
(=>:リダイレクト)
概要
index/index searchNamePageTel
totalNameTel
index/index.tpl 初期画面
(検索用画面表示)
index/search searchNmaePageTel
totalNameTel
index/search.tpl 検索結果表示
edit/index searchNamePageTel
totalNameTel
edit/index.tpl 編集用開始画面表示
auth/index なし auth/index.tpl ログイン画面表示
auth/process なし ・認証成功時
 ->もともとのアクション
・認証失敗時
 ->auth/index
・認証成功時
 もともとのアクション処理
・認証失敗時
 ログイン画面表示
auth/logout なし =>index/index 初期画面へ
edit/search searchNmaePageTel
totalNameTel
edit/search.tpl 変種用検索結果表示
edit/pre-insert なし edit/pre-insert.tpl 電話帳データ入力画面表示
edit/insert insertTel

・入力データOK
 ->edit/index
・入力データエラー
 ->edit/pre-insert

・入力データOK
 電話帳データ登録
・入力データエラー
 電話帳データ入力画面表示
edit/pre-update searchIdTel edit/pre_upadate.tpl 電話帳データ更新画面表示
edit/update updateTel ・入力データOK
 ->edit/index
・入力データエラー
 ->edit/pre-update
・入力データOK
 電話帳データ更新
・入力データエラー
 電話帳データ更新画面表示
edit/pre-delete searchIdTel edit/pre-delete.tpl 電話帳データ削除確認画面表示
edit/delete deleteTel ->edit/index 電話帳データ削除

 フロントコントローラは次のようにします。

c:\Apache2.2\htdocs\zf\ac\03\index.php

<?php
// 【システム個別環境】
//アプリケーションルートパスの定義
define('APP_DIR','../../../../zendapps/ac/07/');
//公開ディレクトリのフルパスの定義
define('BASE_DIR','/zf/ac/07/');
// MyValidateクラス(ユーザ定義)のロード
require_once APP_DIR . 'MyValidate.class.php';
// MyPagerクラス(ユーザ定義)のロード
require_once APP_DIR . 'MyPager.class.php';
//ユーザ定義関数クラスのロード
require_once APP_DIR.'MyFunction.class.php';
// 【Zend Framework基本(1)】
//アクションコントローラ用のコンポーネントをロード
require_once 'Zend/Controller/Action.php';
//フロントコントローラ用のコンポーネントをロード
require_once 'Zend/Controller/Front.php';
//Zend_Controller_Frontオブジェクト(インスタンス)生成
$front = Zend_Controller_Front::getInstance();
//アクションコントローラのディレクトリ設定
$front->setControllerDirectory(APP_DIR . 'controllers');
// 【データベース利用時】
//データベース接続クラス(ユーザ定義)のロード
require_once APP_DIR.'DbManager.class.php';
// 【Smarty利用時】
// MySmartyクラス(ユーザ定義)のロード
require_once APP_DIR . 'MySmarty.class.php';
// 【認証利用時】
//AuthPlugin.classクラス(ユーザ定義)のロード
require_once APP_DIR.'AuthPlugin.class.php';
//認証用AuthPluginプラグインを登録
$front->registerPlugin(new AuthPlugin());
// 【アクセス制御利用時】
//アクションヘルパーの配置先を登録
Zend_Controller_Action_HelperBroker::addPath(
    APP_DIR . 'controllers/helpers','My_Action_Helper');

// 【Zend Framework基本(2)】
//アクションコントローラへのデスパッチ
$front->dispatch();

 

 


前へ | 目次へ |次へ  | Yamada-Lab

執筆 山田豊通
更新日: 2009年4月14日