上下文:
我的问题与我正在开发的论坛非常相似,其中有:
我希望在站点范围内应用此 ACL,并默认拒绝所有资源.
我阅读了使用 Zend_Acl 的基础知识 - 您基本上可以创建角色(来宾、成员、管理员)并拒绝或允许资源(控制器、方法)分配给这些角色.该文档并未具体说明您应该如何在应用程序中实际实现 acl 代码,因此我继续查看 SO..
遇到了一个非常有用的 stackoverflow 来自 marek 的回答,它揭示了一些亮点在这个问题上,但是由于我不熟悉,我仍然无法完全理解如何在考虑最佳实践的情况下正确实施.
发布者在应用程序根目录中有一个静态文件 configAcl.php
,它初始化 acl 对象、添加角色、从每个控制器中创建资源、授予 admin
访问权限对所有内容,允许 normal
访问除管理员之外的所有内容,并将 acl 对象存储在注册表中以备后用.
$acl = new Zend_Acl();$roles = array('admin', 'normal');//控制器脚本名称.如果凭据检查,您必须添加所有这些//对您的应用程序来说是全局的.$controllers = array('auth', 'index', 'news', 'admin');foreach ($roles 作为 $role) {$acl->addRole(new Zend_Acl_Role($role));}foreach ($controllers 作为 $controller) {$acl->add(new Zend_Acl_Resource($controller));}//这是管理员用户的凭据定义.$acl->allow('admin');//可以访问所有内容.//这里是普通用户的凭证定义.$acl->allow('正常');//可以访问所有东西...$acl->deny('normal', 'admin');//... 除了管理控制器.//最后我将整个 ACL 定义存储到注册表中以供使用//在 AuthPlugin 插件中.$registry = Zend_Registry::getInstance();$registry->set('acl', $acl);
问题 #1 - 此代码应该在引导程序中,还是在像这样的独立文件中?如果是这样,在里面说,库目录会更好吗?
它的第二部分是一个扩展 Zend Controller Plugin Abstract 类的新类,它允许它挂钩到 auth/login
中,逻辑基本上是如果登录失败,它会重定向..否则它从注册表中获取 acl 对象,获取身份,并确定是否允许用户查看此资源.
$identity = $auth->getIdentity();$frontController->registerPlugin(new AuthPlugin());
问题#2 - 我究竟将如何编码实际返回用户身份的身份验证插件部分?我意识到他下面有一些代码生成了一个 Auth 适配器 db 表对象,该对象将通过用户 ID 和凭据(散列通过检查)查询数据库表的列./p>
假设我的用户表由这些数据组成:
user_id user_name 级别1 超级管理员 32 约翰 23 example.com 1
其中级别 3 = 管理员,2 = 成员,1 = 访客.
问题 #3 - 究竟在哪里放置上述身份验证代码的好地方?登录控制器内部?
问题 #4 - 另一张海报 回复他关于如何在模型内部完成acl逻辑的文章,但他使用的特定方法不受本机支持并且需要解决方法,这可行吗?这真的是理想的做法吗?
我的实现:
问题 #1
class App_Model_Acl 扩展 Zend_Acl{const ROLE_GUEST = '客人';const ROLE_USER = '用户';const ROLE_PUBLISHER = '出版商';const ROLE_EDITOR = '编辑';const ROLE_ADMIN = '管理员';const ROLE_GOD = '上帝';受保护的静态 $_instance;/* 单例模式 */受保护的函数 __construct(){$this->addRole(new Zend_Acl_Role(self::ROLE_GUEST));$this->addRole(new Zend_Acl_Role(self::ROLE_USER), self::ROLE_GUEST);$this->addRole(new Zend_Acl_Role(self::ROLE_PUBLISHER), self::ROLE_USER);$this->addRole(new Zend_Acl_Role(self::ROLE_EDITOR), self::ROLE_PUBLISHER);$this->addRole(new Zend_Acl_Role(self::ROLE_ADMIN), self::ROLE_EDITOR);//超级管理员的独特角色$this->addRole(new Zend_Acl_Role(self::ROLE_GOD));$this->allow(self::ROLE_GOD);/* 添加新资源 */$this->add(new Zend_Acl_Resource('mvc:users'))->add(new Zend_Acl_Resource('mvc:users.auth'), 'mvc:users')->add(new Zend_Acl_Resource('mvc:users.list'), 'mvc:users');$this->allow(null, 'mvc:users', array('index', 'list'));$this->allow('guest', 'mvc:users.auth', array('index', 'login'));$this->allow('guest', 'mvc:users.list', array('index', 'list'));$this->deny(array('user'), 'mvc:users.auth', array('login'));/* 添加新资源 */$moduleResource = new Zend_Acl_Resource('mvc:snippets');$this->add($moduleResource)->add(new Zend_Acl_Resource('mvc:snippets.crud'), $moduleResource)->add(new Zend_Acl_Resource('mvc:snippets.list'), $moduleResource);$this->allow(null, $moduleResource, array('index', 'list'));$this->allow('user', 'mvc:snippets.crud', array('create', 'update', 'delete', 'read', 'list'));$this->allow('guest', 'mvc:snippets.list', array('index', 'list'));返回 $this;}受保护的静态 $_user;公共静态函数 setUser(Users_Model_User $user = null){if (null === $user) {throw new InvalidArgumentException('$user is null');}self::$_user = $user;}/**** @return App_Model_Acl*/公共静态函数 getInstance(){if (null === self::$_instance) {self::$_instance = new self();}返回 self::$_instance;}公共静态函数 resetInstance(){self::$_instance = null;self::getInstance();}}class Smapp extends Bootstrap//类 Bootstrap 扩展 Zend_Application_Bootstrap_Bootstrap{/*** @var App_Model_User*/受保护的静态 $_currentUser;公共函数 __construct($application){parent::__construct($application);}公共静态函数 setCurrentUser(Users_Model_User $user){self::$_currentUser = $user;}/*** @return App_Model_User*/公共静态函数 getCurrentUser(){if (null === self::$_currentUser) {self::setCurrentUser(Users_Service_User::getUserModel());}返回 self::$_currentUser;}/*** @return App_Model_User*/公共静态函数 getCurrentUserId(){$user = self::getCurrentUser();返回 $user->getId();}}
在类引导程序
受保护的函数 _initUser(){$auth = Zend_Auth::getInstance();如果 ($auth->hasIdentity()) {if ($user = Users_Service_User::findOneByOpenId($auth->getIdentity())) {$userLastAccess = strtotime($user->last_access);//在5分钟内更新上次登录时间的日期如果((时间() - $userLastAccess)> 60 * 5){$date = new Zend_Date();$user->last_access = $date->toString('YYYY-MM-dd HH:mm:ss');$user->save();}Smapp::setCurrentUser($user);}}返回 Smapp::getCurrentUser();}受保护的函数 _initAcl(){$acl = App_Model_Acl::getInstance();Zend_View_Helper_Navigation_HelperAbstract::setDefaultAcl($acl);Zend_View_Helper_Navigation_HelperAbstract::setDefaultRole(Smapp::getCurrentUser()->role);Zend_Registry::set('Zend_Acl', $acl);返回 $acl;}
和 Front_Controller_Plugin
class App_Plugin_Auth 扩展 Zend_Controller_Plugin_Abstract{私人 $_identity;/*** acl 对象** @var zend_acl*/私人 $_acl;/*** 如果有当前页面,则指向该页面* 用户,但他们无权访问* 资源** @var 数组*/私人 $_noacl = array('module' => 'admin','控制器' =>'错误','动作' =>'无认证');/*** 没有当前用户的页面** @var 未知类型*/private $_noauth = array('module' => 'users','控制器' =>'认证','动作' =>'登录');/*** 验证当前用户的请求** @param zend_controller_request $request*/公共函数 preDispatch(Zend_Controller_Request_Abstract $request){$this->_identity = Smapp::getCurrentUser();$this->_acl = App_Model_Acl::getInstance();如果 (!empty($this->_identity)) {$role = $this->_identity->role;} 别的 {$角色=空;}$controller = $request->controller;$module = $request->module;$controller = $controller;$action = $request->action;//从更具体到不太具体$moduleLevel = 'mvc:'.$module;$controllerLevel = $moduleLevel .'.'.$控制器;$privelege = $action;如果 ($this->_acl->has($controllerLevel)) {$resource = $controllerLevel;} 别的 {$resource = $moduleLevel;}if ($module != 'default' && $controller != 'index') {if ($this->_acl->has($resource) && !$this->_acl->isAllowed($role, $resource, $privelege)) {如果 (!$this->_identity) {$request->setModuleName($this->_noauth['module']);$request->setControllerName($this->_noauth['controller']);$request->setActionName($this->_noauth['action']);//$request->setParam('authPage', 'login');} 别的 {$request->setModuleName($this->_noacl['module']);$request->setControllerName($this->_noacl['controller']);$request->setActionName($this->_noacl['action']);//$request->setParam('authPage', 'noauth');}throw new Exception('拒绝访问.' . $resource . '::' . $role);}}}}
最后 - Auth_Controller` :)
class Users_AuthController 扩展了 Smapp_Controller_Action{//会话受保护的 $_storage;公共函数 getStorage(){if (null === $this->_storage) {$this->_storage = new Zend_Session_Namespace(__CLASS__);}返回 $this->_storage;}公共函数 indexAction(){返回 $this->_forward('登录');}公共函数 loginAction(){$openId = null;if ($this->getRequest()->isPost() and $openId = ($this->_getParam('openid_identifier', false))) {//没做什么} elseif (!isset($_GET['openid_mode'])) {返回;}//$userService = $this->loadService('User');$userService = new Users_Service_User();$result = $userService->authenticate($openId, $this->getResponse());如果 ($result->isValid()) {$identity = $result->getIdentity();if (!$identity['Profile']['display_name']) {返回 $this->_helper->redirector->gotoSimpleAndExit('update', 'profile');}$this->_redirect('/');} 别的 {$this->view->errorMessages = $result->getMessages();}}公共函数 logoutAction(){$auth = Zend_Auth::getInstance();$auth->clearIdentity();//Zend_Session::destroy();$this->_redirect('/');}}
问题 2
将其保存在 Zend_Auth
中.
成功验证后 - 在存储中写入身份.$auth->getStorage()->write($result->getIdentity());
identity
- 只是 user_id
数据库设计
创建表`用户`(`id` bigint(20) NOT NULL AUTO_INCREMENT,`open_id` varchar(255) 非空,`role` varchar(20) 不为空,`last_access` 日期时间非空,`created_at` 日期时间非空,主键(`id`),唯一键`open_id`(`open_id`)) 引擎=InnoDB 默认字符集=utf8创建表`user_profile`(`user_id` bigint(20) 非空,`display_name` varchar(100) 默认为空,`email` varchar(100) 默认为空,`real_name` varchar(100) 默认为空,`website_url` varchar(255) 默认为空,`location` varchar(100) 默认为空,`生日` 日期默认为空,`about_me` 文本,`view_count` int(11) NOT NULL DEFAULT '0',`updated_at` 日期时间非空,主键(`user_id`)) 引擎=InnoDB 默认字符集=utf8;
一些糖
/*** SM的代码库** @类别* @包裹* @子包* @copyright 版权所有 (c) 2009 Pavel V Egorov* @author Pavel V Egorov* @link http://epavel.ru/* @自 2009 年 9 月 8 日起*/类 Smapp_View_Helper_IsAllowed 扩展 Zend_View_Helper_Abstract{受保护的 $_acl;受保护的 $_user;公共函数 isAllowed($resource = null, $privelege = null){return (bool) $this->getAcl()->isAllowed($this->getUser(), $resource, $privelege);}/*** @return App_Model_Acl*/公共函数 getAcl(){if (null === $this->_acl) {$this->setAcl(App_Model_Acl::getInstance());}返回 $this->_acl;}/*** @return App_View_Helper_IsAllowed*/公共函数 setAcl(Zend_Acl $acl){$this->_acl = $acl;返回 $this;}/*** @return Users_Model_User*/公共函数 getUser(){if (null === $this->_user) {$this->setUser(Smapp::getCurrentUser());}返回 $this->_user;}/*** @return App_View_Helper_IsAllowed*/公共函数 setUser(Users_Model_User $user){$this->_user = $user;返回 $this;}}
对于任何视图脚本中的类似内容
<?php if ($this->isAllowed('mvc:snippets.crud', 'update')) : ?><a title="编辑 «<?=$this->escape($snippetInfo['title'])?>» snippet">Edit</a><?php endif?>
有问题吗?:)
Context:
My questions pertain to a forum I'm developing pretty much exactly like SO, where there are:
I would want this ACL to be applied site-wide, and by default deny all resources.
I read the basics of using Zend_Acl - in that you basically create roles ( guest, member, admin ) and either deny or allow resources ( controllers, methods ) to those roles. The documentation isn't very specific on how you should actually implement the acl code in your application, so I went looking on SO..
Came across a pretty useful stackoverflow answer from marek which sheds some light on the issue, however due to my unfamiliarity I still can't fully grok how to properly implement this with best practices in mind.
The poster has a static file configAcl.php
in the application root which initializes the acl object, adds roles, creates a resource out of every controller, gives admin
access to everything, gives normal
access to everything but the admin and stores the acl object in the registry for later use.
$acl = new Zend_Acl();
$roles = array('admin', 'normal');
// Controller script names. You have to add all of them if credential check
// is global to your application.
$controllers = array('auth', 'index', 'news', 'admin');
foreach ($roles as $role) {
$acl->addRole(new Zend_Acl_Role($role));
}
foreach ($controllers as $controller) {
$acl->add(new Zend_Acl_Resource($controller));
}
// Here comes credential definiton for admin user.
$acl->allow('admin'); // Has access to everything.
// Here comes credential definition for normal user.
$acl->allow('normal'); // Has access to everything...
$acl->deny('normal', 'admin'); // ... except the admin controller.
// Finally I store whole ACL definition to registry for use
// in AuthPlugin plugin.
$registry = Zend_Registry::getInstance();
$registry->set('acl', $acl);
Question #1 - Should this code be in the bootstrap, or in a standalone file such as this? If so would it be better if it was inside say, the library directory?
The second part of it is a new class extending the Zend Controller Plugin Abstract class which allows it to be hooked into auth/login
, the logic is basically if the login fails, it redirects.. otherwise it grabs the acl object from the registry, grabs the identity, and determines if the user is allowed to view this resource.
$identity = $auth->getIdentity();
$frontController->registerPlugin(new AuthPlugin());
Question #2 - How exactly would I code the auth plugin part that actually returns the identity of the user? I realize that he had some code below that generated a Auth adapter db table object which would query a database table's column by user id and credential ( hashed pass check ).. I'm confused on where this fits in with the getIdentity part.
Let's say my users table was composed of this data:
user_id user_name level
1 superadmin 3
2 john 2
3 example.com 1
Where level 3 = admin, 2 = member, 1 = guest.
Question #3 - where exactly is a good place to put the above auth code in? Inside of the login controller?
Question #4 - another poster replies with his article on how the acl logic should be done inside models, yet the specific method which he uses is not natively supported and requires a workaround, is this feasible? And is this really how it ideally should be done?
My implementation:
Question #1
class App_Model_Acl extends Zend_Acl
{
const ROLE_GUEST = 'guest';
const ROLE_USER = 'user';
const ROLE_PUBLISHER = 'publisher';
const ROLE_EDITOR = 'editor';
const ROLE_ADMIN = 'admin';
const ROLE_GOD = 'god';
protected static $_instance;
/* Singleton pattern */
protected function __construct()
{
$this->addRole(new Zend_Acl_Role(self::ROLE_GUEST));
$this->addRole(new Zend_Acl_Role(self::ROLE_USER), self::ROLE_GUEST);
$this->addRole(new Zend_Acl_Role(self::ROLE_PUBLISHER), self::ROLE_USER);
$this->addRole(new Zend_Acl_Role(self::ROLE_EDITOR), self::ROLE_PUBLISHER);
$this->addRole(new Zend_Acl_Role(self::ROLE_ADMIN), self::ROLE_EDITOR);
//unique role for superadmin
$this->addRole(new Zend_Acl_Role(self::ROLE_GOD));
$this->allow(self::ROLE_GOD);
/* Adding new resources */
$this->add(new Zend_Acl_Resource('mvc:users'))
->add(new Zend_Acl_Resource('mvc:users.auth'), 'mvc:users')
->add(new Zend_Acl_Resource('mvc:users.list'), 'mvc:users');
$this->allow(null, 'mvc:users', array('index', 'list'));
$this->allow('guest', 'mvc:users.auth', array('index', 'login'));
$this->allow('guest', 'mvc:users.list', array('index', 'list'));
$this->deny(array('user'), 'mvc:users.auth', array('login'));
/* Adding new resources */
$moduleResource = new Zend_Acl_Resource('mvc:snippets');
$this->add($moduleResource)
->add(new Zend_Acl_Resource('mvc:snippets.crud'), $moduleResource)
->add(new Zend_Acl_Resource('mvc:snippets.list'), $moduleResource);
$this->allow(null, $moduleResource, array('index', 'list'));
$this->allow('user', 'mvc:snippets.crud', array('create', 'update', 'delete', 'read', 'list'));
$this->allow('guest', 'mvc:snippets.list', array('index', 'list'));
return $this;
}
protected static $_user;
public static function setUser(Users_Model_User $user = null)
{
if (null === $user) {
throw new InvalidArgumentException('$user is null');
}
self::$_user = $user;
}
/**
*
* @return App_Model_Acl
*/
public static function getInstance()
{
if (null === self::$_instance) {
self::$_instance = new self();
}
return self::$_instance;
}
public static function resetInstance()
{
self::$_instance = null;
self::getInstance();
}
}
class Smapp extends Bootstrap // class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
/**
* @var App_Model_User
*/
protected static $_currentUser;
public function __construct($application)
{
parent::__construct($application);
}
public static function setCurrentUser(Users_Model_User $user)
{
self::$_currentUser = $user;
}
/**
* @return App_Model_User
*/
public static function getCurrentUser()
{
if (null === self::$_currentUser) {
self::setCurrentUser(Users_Service_User::getUserModel());
}
return self::$_currentUser;
}
/**
* @return App_Model_User
*/
public static function getCurrentUserId()
{
$user = self::getCurrentUser();
return $user->getId();
}
}
in class bootstrap
protected function _initUser()
{
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
if ($user = Users_Service_User::findOneByOpenId($auth->getIdentity())) {
$userLastAccess = strtotime($user->last_access);
//update the date of the last login time in 5 minutes
if ((time() - $userLastAccess) > 60*5) {
$date = new Zend_Date();
$user->last_access = $date->toString('YYYY-MM-dd HH:mm:ss');
$user->save();
}
Smapp::setCurrentUser($user);
}
}
return Smapp::getCurrentUser();
}
protected function _initAcl()
{
$acl = App_Model_Acl::getInstance();
Zend_View_Helper_Navigation_HelperAbstract::setDefaultAcl($acl);
Zend_View_Helper_Navigation_HelperAbstract::setDefaultRole(Smapp::getCurrentUser()->role);
Zend_Registry::set('Zend_Acl', $acl);
return $acl;
}
and Front_Controller_Plugin
class App_Plugin_Auth extends Zend_Controller_Plugin_Abstract
{
private $_identity;
/**
* the acl object
*
* @var zend_acl
*/
private $_acl;
/**
* the page to direct to if there is a current
* user but they do not have permission to access
* the resource
*
* @var array
*/
private $_noacl = array('module' => 'admin',
'controller' => 'error',
'action' => 'no-auth');
/**
* the page to direct to if there is not current user
*
* @var unknown_type
*/
private $_noauth = array('module' => 'users',
'controller' => 'auth',
'action' => 'login');
/**
* validate the current user's request
*
* @param zend_controller_request $request
*/
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$this->_identity = Smapp::getCurrentUser();
$this->_acl = App_Model_Acl::getInstance();
if (!empty($this->_identity)) {
$role = $this->_identity->role;
} else {
$role = null;
}
$controller = $request->controller;
$module = $request->module;
$controller = $controller;
$action = $request->action;
//go from more specific to less specific
$moduleLevel = 'mvc:'.$module;
$controllerLevel = $moduleLevel . '.' . $controller;
$privelege = $action;
if ($this->_acl->has($controllerLevel)) {
$resource = $controllerLevel;
} else {
$resource = $moduleLevel;
}
if ($module != 'default' && $controller != 'index') {
if ($this->_acl->has($resource) && !$this->_acl->isAllowed($role, $resource, $privelege)) {
if (!$this->_identity) {
$request->setModuleName($this->_noauth['module']);
$request->setControllerName($this->_noauth['controller']);
$request->setActionName($this->_noauth['action']);
//$request->setParam('authPage', 'login');
} else {
$request->setModuleName($this->_noacl['module']);
$request->setControllerName($this->_noacl['controller']);
$request->setActionName($this->_noacl['action']);
//$request->setParam('authPage', 'noauth');
}
throw new Exception('Access denied. ' . $resource . '::' . $role);
}
}
}
}
and finnaly - Auth_Controller` :)
class Users_AuthController extends Smapp_Controller_Action
{
//sesssion
protected $_storage;
public function getStorage()
{
if (null === $this->_storage) {
$this->_storage = new Zend_Session_Namespace(__CLASS__);
}
return $this->_storage;
}
public function indexAction()
{
return $this->_forward('login');
}
public function loginAction()
{
$openId = null;
if ($this->getRequest()->isPost() and $openId = ($this->_getParam('openid_identifier', false))) {
//do nothing
} elseif (!isset($_GET['openid_mode'])) {
return;
}
//$userService = $this->loadService('User');
$userService = new Users_Service_User();
$result = $userService->authenticate($openId, $this->getResponse());
if ($result->isValid()) {
$identity = $result->getIdentity();
if (!$identity['Profile']['display_name']) {
return $this->_helper->redirector->gotoSimpleAndExit('update', 'profile');
}
$this->_redirect('/');
} else {
$this->view->errorMessages = $result->getMessages();
}
}
public function logoutAction()
{
$auth = Zend_Auth::getInstance();
$auth->clearIdentity();
//Zend_Session::destroy();
$this->_redirect('/');
}
}
Question #2
keep it inside Zend_Auth
.
after succesfull auth - write identity in storage. $auth->getStorage()->write($result->getIdentity());
the identity
- is simply user_id
DB design
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`open_id` varchar(255) NOT NULL,
`role` varchar(20) NOT NULL,
`last_access` datetime NOT NULL,
`created_at` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `open_id` (`open_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `user_profile` (
`user_id` bigint(20) NOT NULL,
`display_name` varchar(100) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`real_name` varchar(100) DEFAULT NULL,
`website_url` varchar(255) DEFAULT NULL,
`location` varchar(100) DEFAULT NULL,
`birthday` date DEFAULT NULL,
`about_me` text,
`view_count` int(11) NOT NULL DEFAULT '0',
`updated_at` datetime NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
some sugar
/**
* SM's code library
*
* @category
* @package
* @subpackage
* @copyright Copyright (c) 2009 Pavel V Egorov
* @author Pavel V Egorov
* @link http://epavel.ru/
* @since 08.09.2009
*/
class Smapp_View_Helper_IsAllowed extends Zend_View_Helper_Abstract
{
protected $_acl;
protected $_user;
public function isAllowed($resource = null, $privelege = null)
{
return (bool) $this->getAcl()->isAllowed($this->getUser(), $resource, $privelege);
}
/**
* @return App_Model_Acl
*/
public function getAcl()
{
if (null === $this->_acl) {
$this->setAcl(App_Model_Acl::getInstance());
}
return $this->_acl;
}
/**
* @return App_View_Helper_IsAllowed
*/
public function setAcl(Zend_Acl $acl)
{
$this->_acl = $acl;
return $this;
}
/**
* @return Users_Model_User
*/
public function getUser()
{
if (null === $this->_user) {
$this->setUser(Smapp::getCurrentUser());
}
return $this->_user;
}
/**
* @return App_View_Helper_IsAllowed
*/
public function setUser(Users_Model_User $user)
{
$this->_user = $user;
return $this;
}
}
for things like this in any view script
<?php if ($this->isAllowed('mvc:snippets.crud', 'update')) : ?>
<a title="Edit «<?=$this->escape($snippetInfo['title'])?>» snippet">Edit</a>
<?php endif?>
Questions? :)
这篇关于实用 Zend_ACL + Zend_Auth 实现和最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持html5模板网!