cakephp中acl和auth詳解

edited 十月 2013 in CakePHP
cakephp中acl和auth詳解
2009-11-10,auther:kenny,http://www.shopokey.com
自學習cakephp以來,一直沒有完成搞懂acl,對它的用法也是一知半解,acl應該是cakephp中一個比較難懂的地方,這幾天又重新看了下手冊,發現acl沒有相信中的難,但比我想像中的好用
下面讓我說說它的具體用法已經使用過程中應該注意到問題
準備前工作:
最好是能配置好bake,使bake命名有效,雖然這個不是必須的,不過有這個命令行工具以後我們會方便很多
在你的資料庫中導入下面的sql語句
CREATE TABLE users (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
	username VARCHAR(255) NOT NULL UNIQUE,
    password CHAR(40) NOT NULL,
    group_id INT(11) NOT NULL,
    created DATETIME,
    modified DATETIME
);

 
CREATE TABLE groups (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    created DATETIME,
    modified DATETIME
);


CREATE TABLE posts (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    user_id INT(11) NOT NULL,
    title VARCHAR(255) NOT NULL,
    body TEXT,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE widgets (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    part_no VARCHAR(12),
    quantity INT(11)
);
在這裏我們使用cake bake all命令工具快速生成models, controllers, 和 views(在這裏我就詳細介紹怎麼使用cakephp中的bake命令了)


下一步,準備使用auth元件認證
首先我們打開users_controller.php在UsersController類中增加登錄登出動作
function login() {
    //Auth Magic
}
 
function logout() {
    //Leave empty for now.
}
然後創建視圖文件app/views/users/login.ctp
<?php
$session->flash('auth');
echo $form->create('User', array('action' => 'login'));
echo $form->inputs(array(
	'legend' => __('Login', true),
	'username',
	'password'
));
echo $form->end('Login');

?>
下一步我們需要修改AppController(/app/app_controller.php),如果你的app目錄下面沒有app_controller.php檔的話,你可以自己創建一個
<?php
class AppController extends Controller {
    var $components = array('Acl', 'Auth');

    function beforeFilter() {
        //Configure AuthComponent
        $this->Auth->authorize = 'actions';
        $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
        $this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
        $this->Auth->loginRedirect = array('controller' => 'posts', 'action' => 'add');
    }
}
?>
接下來我們需要修改GroupsController和UsersController,這兩個檔的目錄大家應該都知道在哪里吧..
在這兩個控制器中分別添加以下代碼
function beforeFilter() {
    parent::beforeFilter(); 
    $this->Auth->allowedActions = array('*');
}
其實這段代碼的意思是允許用戶訪問所有user和group下的action,當然這個後面是要改回來的

接下來我們要初始化acl表
因為現在我們的資料庫中就只有四個表,還沒有導入acl表
我們用下面語句導入acl表到資料庫中
在命令行中輸入cake schema run create DbAcl

我們根據提示導入表

接下來我們需要修改user和group模型
首先我們打開model目錄下的user.php增加以下代碼(事實上你可以用下面代碼替換)
var $name = 'User';
var $belongsTo = array('Group');
var $actsAs = array('Acl' => 'requester');
 
function parentNode() {
    if (!$this->id && empty($this->data)) {
        return null;
    }
    $data = $this->data;
    if (empty($this->data)) {
        $data = $this->read();
    }
    if (!$data['User']['group_id']) {
        return null;
    } else {
        return array('Group' => array('id' => $data['User']['group_id']));
    }
}
然後修改group模型

var $actsAs = array('Acl' => array('requester'));
 
function parentNode() {
    return null;
}

好了,到這一步我們需要暫時停一下了,現在在流覽器中打開相應的user和group頁面,增加user和group,比如我的,我打開http://localhost/cakephp/groups增加組,打開http://localhost/cakephp/users/ 增加用戶,在這裏我們增加三個組和三個用戶

添加完以後你可以打開phpmyadmin看下aros表看下是不是多了些記錄

是不是覺得很神奇不可思議啊,哈哈,這就是框架的魅力,到目前為止和acl有關的表,就只有aros表中存在記錄

在我們以後修改每一個user用戶時,我們也必須修改aros表記錄
所有我們應該做user模型中增加下面代碼
/**    
 * After save callback
 *
 * Update the aro for the user.
 *
 * @access public
 * @return void
 */
function afterSave($created) {
        if (!$created) {
            $parent = $this->parentNode();
            $parent = $this->node($parent);
            $node = $this->node();
            $aro = $node[0];
            $aro['Aro']['parent_id'] = $parent[0]['Aro']['id'];
            $this->Aro->save($aro);
        }
}
到這裏我們aro創建已經完成,接下來我們應該創佳aco了
我們應該知道其實acl的本質是用來定義一個ARO在什麼時候可以訪問一個aco的元件,明白了這一點一切就變的很簡單了。
為了簡單我們使用命令行執行cake acl create aco root controllers

你現在再用phpmyadmin打開acors表,你應該會看到表中已經有一條記錄了,這條記錄就是剛剛執行這個命令的結果,當然我們不一定非得用命令行工具的。

接下來我們還需要修改下AppController,在裏面的beforeFilter方法中我們需要增加一行$this->Auth->actionPath = 'controllers/';

下面是重點,在我們的app項目中可能會存在很多的控制器和方法,我們需要把每個action都添加到acors表中來實現許可權控制,當然如果你不怕麻煩的話可以一個一個手動添加,但我想大多數程式師還是很懶的,所有我們這裏需要使用自動生成工具

這裏我們添加下面代碼放在users_controller.php中,當然你也可以放在其他的控制器中
下麵我就在users_controller.php中增加下面代碼
function build_acl() {
		if (!Configure::read('debug')) {
			return $this->_stop();
		}
		$log = array();

		$aco =& $this->Acl->Aco;
		$root = $aco->node('controllers');
		if (!$root) {
			$aco->create(array('parent_id' => null, 'model' => null, 'alias' => 'controllers'));
			$root = $aco->save();
			$root['Aco']['id'] = $aco->id; 
			$log[] = 'Created Aco node for controllers';
		} else {
			$root = $root[0];
		}   

		App::import('Core', 'File');
		$Controllers = Configure::listObjects('controller');
		$appIndex = array_search('App', $Controllers);
		if ($appIndex !== false ) {
			unset($Controllers[$appIndex]);
		}
		$baseMethods = get_class_methods('Controller');
		$baseMethods[] = 'buildAcl';

		$Plugins = $this->_getPluginControllerNames();
		$Controllers = array_merge($Controllers, $Plugins);

		// look at each controller in app/controllers
		foreach ($Controllers as $ctrlName) {
			$methods = $this->_getClassMethods($this->_getPluginControllerPath($ctrlName));

			// Do all Plugins First
			if ($this->_isPlugin($ctrlName)){
				$pluginNode = $aco->node('controllers/'.$this->_getPluginName($ctrlName));
				if (!$pluginNode) {
					$aco->create(array('parent_id' => $root['Aco']['id'], 'model' => null, 'alias' => $this->_getPluginName($ctrlName)));
					$pluginNode = $aco->save();
					$pluginNode['Aco']['id'] = $aco->id;
					$log[] = 'Created Aco node for ' . $this->_getPluginName($ctrlName) . ' Plugin';
				}
			}
			// find / make controller node
			$controllerNode = $aco->node('controllers/'.$ctrlName);
			if (!$controllerNode) {
				if ($this->_isPlugin($ctrlName)){
					$pluginNode = $aco->node('controllers/' . $this->_getPluginName($ctrlName));
					$aco->create(array('parent_id' => $pluginNode['0']['Aco']['id'], 'model' => null, 'alias' => $this->_getPluginControllerName($ctrlName)));
					$controllerNode = $aco->save();
					$controllerNode['Aco']['id'] = $aco->id;
					$log[] = 'Created Aco node for ' . $this->_getPluginControllerName($ctrlName) . ' ' . $this->_getPluginName($ctrlName) . ' Plugin Controller';
				} else {
					$aco->create(array('parent_id' => $root['Aco']['id'], 'model' => null, 'alias' => $ctrlName));
					$controllerNode = $aco->save();
					$controllerNode['Aco']['id'] = $aco->id;
					$log[] = 'Created Aco node for ' . $ctrlName;
				}
			} else {
				$controllerNode = $controllerNode[0];
			}

			//clean the methods. to remove those in Controller and private actions.
			foreach ($methods as $k => $method) {
				if (strpos($method, '_', 0) === 0) {
					unset($methods[$k]);
					continue;
				}
				if (in_array($method, $baseMethods)) {
					unset($methods[$k]);
					continue;
				}
				$methodNode = $aco->node('controllers/'.$ctrlName.'/'.$method);
				if (!$methodNode) {
					$aco->create(array('parent_id' => $controllerNode['Aco']['id'], 'model' => null, 'alias' => $method));
					$methodNode = $aco->save();
					$log[] = 'Created Aco node for '. $method;
				}
			}
		}
		if(count($log)>0) {
			debug($log);
		}
	}

	function _getClassMethods($ctrlName = null) {
		App::import('Controller', $ctrlName);
		if (strlen(strstr($ctrlName, '.')) > 0) {
			// plugin's controller
			$num = strpos($ctrlName, '.');
			$ctrlName = substr($ctrlName, $num+1);
		}
		$ctrlclass = $ctrlName . 'Controller';
		$methods = get_class_methods($ctrlclass);

		// Add scaffold defaults if scaffolds are being used
		$properties = get_class_vars($ctrlclass);
		if (array_key_exists('scaffold',$properties)) {
			if($properties['scaffold'] == 'admin') {
				$methods = array_merge($methods, array('admin_add', 'admin_edit', 'admin_index', 'admin_view', 'admin_delete'));
			} else {
				$methods = array_merge($methods, array('add', 'edit', 'index', 'view', 'delete'));
			}
		}
		return $methods;
	}

	function _isPlugin($ctrlName = null) {
		$arr = String::tokenize($ctrlName, '/');
		if (count($arr) > 1) {
			return true;
		} else {
			return false;
		}
	}

	function _getPluginControllerPath($ctrlName = null) {
		$arr = String::tokenize($ctrlName, '/');
		if (count($arr) == 2) {
			return $arr[0] . '.' . $arr[1];
		} else {
			return $arr[0];
		}
	}

	function _getPluginName($ctrlName = null) {
		$arr = String::tokenize($ctrlName, '/');
		if (count($arr) == 2) {
			return $arr[0];
		} else {
			return false;
		}
	}

	function _getPluginControllerName($ctrlName = null) {
		$arr = String::tokenize($ctrlName, '/');
		if (count($arr) == 2) {
			return $arr[1];
		} else {
			return false;
		}
	}

/**
 * Get the names of the plugin controllers ...
 * 
 * This function will get an array of the plugin controller names, and
 * also makes sure the controllers are available for us to get the 
 * method names by doing an App::import for each plugin controller.
 *
 * @return array of plugin names.
 *
 */
	function _getPluginControllerNames() {
		App::import('Core', 'File', 'Folder');
		$paths = Configure::getInstance();
		$folder =& new Folder();
		$folder->cd(APP . 'plugins');

		// Get the list of plugins
		$Plugins = $folder->read();
		$Plugins = $Plugins[0];
		$arr = array();

		// Loop through the plugins
		foreach($Plugins as $pluginName) {
			// Change directory to the plugin
			$didCD = $folder->cd(APP . 'plugins'. DS . $pluginName . DS . 'controllers');
			// Get a list of the files that have a file name that ends
			// with controller.php
			$files = $folder->findRecursive('.*_controller\.php');

			// Loop through the controllers we found in the plugins directory
			foreach($files as $fileName) {
				// Get the base file name
				$file = basename($fileName);

				// Get the controller name
				$file = Inflector::camelize(substr($file, 0, strlen($file)-strlen('_controller.php')));
				if (!preg_match('/^'. Inflector::humanize($pluginName). 'App/', $file)) {
					if (!App::import('Controller', $pluginName.'.'.$file)) {
						debug('Error importing '.$file.' for plugin '.$pluginName);
					} else {
						/// Now prepend the Plugin name ...
						// This is required to allow us to fetch the method names.
						$arr[] = Inflector::humanize($pluginName) . "/" . $file;
					}
				}
			}
		}
		return $arr;
	}

增加好以後我們打開流覽器訪問剛才的方法
比如我的http://localhost/cakephp/users/build_acl
運行後程式會自動把所有controller下的action都添加到acos表中,你現在打開acos表應該就會看到很多記錄了,如


接下來應該是做我們最興奮的事情了,就是實現許可權控制,因為我們前期準備工作都做好了,最終我們都是為了實現可以控制許可權

1. 先來介紹下語法,允許訪問$this->Acl->allow($aroAlias, $acoAlias);
拒絕訪問$this->Acl->deny($aroAlias, $acoAlias);

你先打開aros_acos表中看下,到目前為止該表應該還是沒有記錄的
我們可以寫一個初始化函數
我同樣把這段代碼放在user控制器中
function initDB() {
    $group =& $this->User->Group;
    //Allow admins to everything
    $group->id = 1;     
    $this->Acl->allow($group, 'controllers');
 
    //allow managers to posts and widgets
    $group->id = 2;
    $this->Acl->deny($group, 'controllers');
    $this->Acl->allow($group, 'controllers/Posts');
    $this->Acl->allow($group, 'controllers/Widgets');
 
    //allow users to only add and edit on posts and widgets
    $group->id = 3;
    $this->Acl->deny($group, 'controllers');        
    $this->Acl->allow($group, 'controllers/Posts/add');
    $this->Acl->allow($group, 'controllers/Posts/edit');        
    $this->Acl->allow($group, 'controllers/Widgets/add');
    $this->Acl->allow($group, 'controllers/Widgets/edit');
}
接下來我們在流覽器中訪問這個action

這個是時候你就應該發現你的aros_acos表中多了很多條記錄,哈哈,這個就是acl的秘密所在.

接下來你可以使用不同組的用戶名登錄訪問,你還可以修改initDB裏面的代碼進行測試,至於實際工作中如何使用,那就要看你自己的了,最後祝大家工作愉快!
http://www.shopokey.com/download/cakephp_acl.doc

原始討論: http://twpug.net/x/modules/newbb/viewtopic.php?topic_id=4650

評論

  • edited 十一月 2009
    感謝提供,不過提到的網址看樣子是個購物網站,不像是技術交流的地方...

    這篇文章提到的應該跟官方文件相近,也許可以試著提供翻譯:
    http://book.cakephp.org/cn/view/641/Simple-Acl-controlled-Application
  • edited 十一月 2009
    這裏我接著上面的做一個很簡單的後臺管理,讓我們可以從後臺修改用戶資料的時候也可以編輯修改權限
    先編輯修改UsersController,在beforeFilter方法中增加下面幾句
    $this->set("Acl",&$this->Acl);
    	$userinfo = $this->Auth->user();
    	$this->set('userinfo',$userinfo);
    
    這幾句話的目的是為了讓我們可以在views裏面中可以訪問到acl以及user等信息

    我現在要做的就是在修改用戶信息的時候也可以修改該用戶的權限
    於是我修改edit
    function edit($id = null) {
    		if (!$id && empty($this->data)) {
    			$this->Session->setFlash(__('Invalid User', true));
    			$this->redirect(array('action'=>'index'));
    		}
    
    		if (!empty($this->data)) {
    		   
    		$this->User->id=$this->data['User']['id'];
    		 //$this->Acl->deny($this->User, '*');
    		  $controller=$this->Aco->find("all",array('conditions' => array("parent_id=1")));
    		 foreach($controller as $k=>$v){
    			 $controller[$k]['Aco']['children']=$this->Aco->children($v['Aco']['id']);
    			foreach($controller[$k]['Aco']['children'] as $k2=> $v2){
    				$kk="controllers/".$v['Aco']['alias']."/".$v2['Aco']['alias'];
    				if(in_array($kk,$this->data['aco'])){
    					$this->Acl->allow($this->User, $kk);
    				}else{
    					$this->Acl->deny($this->User, $kk);
    				}
    			}
    		 }
    
    			if ($this->User->save($this->data)) {
    				$this->Session->setFlash(__('The User has been saved', true));
    				$this->redirect(array('action'=>'index'));
    			} else {
    				$this->Session->setFlash(__('The User could not be saved. Please, try again.', true));
    			}
    		}
    		if (empty($this->data)) {
    			$this->data = $this->User->read(null, $id);
    
    
    		 $this->Aco->Behaviors->attach('Containable');
    		 $this->Aco->contain();
    		 $controller=$this->Aco->find("all",array('conditions' => array("parent_id=1")));
    		$this->User->id=$id;
    		 foreach($controller as $k=>$v){
    			
    			$controller[$k]['Aco']['children']=$this->Aco->children($v['Aco']['id']);
    			foreach($controller[$k]['Aco']['children'] as $k2=> $v2){
    			 $c=$this->Acl->check(array('model' => 'User', 'foreign_key' => $id), "controllers/".$v['Aco']['alias']."/".$v2['Aco']['alias']);
    
    			 if($c){
    				 $controller[$k]['Aco']['children'][$k2]['Aco']['checked']=1;
    			 }else{
    				$controller[$k]['Aco']['children'][$k2]['Aco']['checked']=0;
    			 }
    			}
    		 }
    
    		$this->set("aco",$controller);
    		}
    		$groups = $this->User->Group->find('list');
    		$this->set(compact('groups'));
    	}
    
    然後修改app\views\users\edit.ctp
    <div class="users form">
    <?php echo $form->create('User');?>
    	<fieldset>
     		<legend><?php __('Edit User');?></legend>
    	<?php
    		echo $form->input('id');
    		echo $form->input('username');
    		echo $form->input('password');
    		echo $form->input('group_id');
    	?>
    	</fieldset>
    	<?php
    	foreach($aco as $v){
    	echo "<div>";
    	echo "<div>".$v['Aco']['alias']."</div>";
    		foreach($v['Aco']['children'] as $vv){
    		$cc=$vv['Aco']['checked']?" checked ":"";
    			echo "<div><input name=\"data[aco][]\" type='checkbox'".$cc." value='"."controllers/".$v['Aco']['alias']."/".$vv['Aco']['alias']."'>".$vv['Aco']['alias']."</div>";
    		}
    	echo "</div>";
    	}
    	?>
    <?php echo $form->end('Submit');?>
    </div>
    <div class="actions">
    	<ul>
    		<li><?php echo $html->link(__('Delete', true), array('action' => 'delete', $form->value('User.id')), null, sprintf(__('Are you sure you want to delete # %s?', true), $form->value('User.id'))); ?></li>
    		<li><?php echo $html->link(__('List Users', true), array('action' => 'index'));?></li>
    	</ul>
    </div>
    
    最後我們修改下views\users\index.ctp
    <div class="users index">
    <h2><?php __('Users');?></h2>
    <p>
    <?php
    echo $paginator->counter(array(
    'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true)
    ));
    ?></p>
    <table cellpadding="0" cellspacing="0">
    <tr>
    	<th><?php echo $paginator->sort('id');?></th>
    	<th><?php echo $paginator->sort('username');?></th>
    	<th><?php echo $paginator->sort('password');?></th>
    	<th><?php echo $paginator->sort('group_id');?></th>
    	<th><?php echo $paginator->sort('created');?></th>
    	<th><?php echo $paginator->sort('modified');?></th>
    	<th class="actions"><?php __('Actions');?></th>
    </tr>
    <?php
    $i = 0;
    foreach ($users as $user):
    	$class = null;
    	if ($i++ % 2 == 0) {
    		$class = ' class="altrow"';
    	}
    ?>
    	<tr<?php echo $class;?>>
    		<td>
    			<?php echo $user['User']['id']; ?>
    		</td>
    		<td>
    			<?php echo $user['User']['username']; ?>
    		</td>
    		<td>
    			<?php echo $user['User']['password']; ?>
    		</td>
    		<td>
    			<?php echo $user['User']['group_id']; ?>
    		</td>
    		<td>
    			<?php echo $user['User']['created']; ?>
    		</td>
    		<td>
    			<?php echo $user['User']['modified']; ?>
    		</td>
    		<td class="actions">
    			<?php if($Acl->check(array('model' => 'User', 'foreign_key' => $userinfo['User']['id']), "controllers/Users/view")) echo $html->link(__('View', true), array('action' => 'view', $user['User']['id'])); ?>
    			<?php if($Acl->check(array('model' => 'User', 'foreign_key' => $userinfo['User']['id']), "controllers/Users/edit")) echo $html->link(__('Edit', true), array('action' => 'edit', $user['User']['id'])); ?>
    			<?php if($Acl->check(array('model' => 'User', 'foreign_key' => $userinfo['User']['id']), "controllers/Users/delete")) echo $html->link(__('Delete', true), array('action' => 'delete', $user['User']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $user['User']['id'])); ?>
    		</td>
    	</tr>
    <?php endforeach; ?>
    </table>
    </div>
    <div class="paging">
    	<?php echo $paginator->prev('<< '.__('previous', true), array(), null, array('class'=>'disabled'));?>
     | 	<?php echo $paginator->numbers();?>
    	<?php echo $paginator->next(__('next', true).' >>', array(), null, array('class' => 'disabled'));?>
    </div>
    <div class="actions">
    	<ul>
    		<li><?php if($Acl->check(array('model' => 'User', 'foreign_key' => $userinfo['User']['id']), "controllers/Users/add")) echo $html->link(__('New User', true), array('action' => 'add')); ?></li>
    	</ul>
    </div>
    
    裏面很多代碼我用的都是默認的,如果大家用的也都是默認的話可以直接復制過去看效果了,相信大家現在可以在後臺自由編輯user的權限了,當然group的權限編輯原理也是一樣的,我就不重復了,呵呵,是不是很簡單啊?
Sign In or Register to comment.