php中Zend_Auth和Zend_Acl进行登录认证及根据用户角色进行权限控制

作者:袖梨 2022-06-24

Zend_Auth_Adapter_Interface中提供了一个接口,我们需要自己去实现

代码如下:

代码如下 复制代码

require_once 'Zend/Auth/Adapter/Interface.php';
class Auth implements Zend_Auth_Adapter_Interface{
private $_useraccount;
private $_password;
private $_db;
/**
* 构造函数 设置用户名和密码 数据连接对象
*
* @return void
*/
public function __construct($useraccount,$password,$db){
$this->_useraccount = $useraccount;
$this->_password = $password;
$this->_db = $db;
}

/**
* 进行认证
* @throws Zend_Auth_Adapter_Exception
* @return Zend_Auth_Result
*
*/
public function authenticate()
{
//默认情况下是认证失败
$authResult = array(
'code' => Zend_Auth_Result::FAILURE,//详参:Zend_Auth_Result
'identity' => '',
'info' => array()
);

//获得登录用户信息
$result = $this->_getAccountData();

if(isset($result)&&!empty($result)) {//认证成功,则将用户信息存储到session中
$authResult = array(
'code' => Zend_Auth_Result::SUCCESS,
'identity' => $result['name'],
'info' => array('status'=>$result['status'])
);
//角色存储 个人缓存空间
$namespace = Zend_Auth::getInstance()//单例模式
-> getStorage()
-> getNamespace();
$_SESSION[$namespace]['role'] = $result['group_id'];//所属用户组
$_SESSION[$namespace]['userInfo'] = $result;
$_SESSION[$namespace]['userInfo']['lastLoginTime'] = $result['login_time'];
$_SESSION[$namespace]['userInfo']['lastLoginIp'] = $result['login_ip'];
// $_SESSION[$namespace]['userInfo']['password'] = $result['password'];//密码是很重要的,不要写到session中
}

return new Zend_Auth_Result($authResult['code'], $authResult['identity'], $authResult['info']);
}
/**
* 用户密码加密
* @param $pwd 原始密码
* @return string 加密后的密码字符串
*
*/
static public function encryptionType($pwd=null) {
$pwd = md5($pwd);
return $pwd;
}
/**
* 获得用户数据结构
*
* @todo 整理密码的公共类
*/
private function _getAccountData(){
$resArr = array();
$sysUserObj = Base_Dao_Factory::getObject('Admin_Models_User');
//先登录普通会员帐号
$data = array(
'login_name' => $this->_useraccount,
'login_pwd' => $this->encryptionType($this->_password)
);
$result = $sysUserObj->login($data);
//判断是否有数据,是则赋值
if ($result) {
if (!empty($result[0])) {
$resArr = $result[0];
}
}
return $resArr;
}
}

解释:在authenticate方法的实现代码中,return一个Zend_Auth_Result对象实例,而查看Zend_Auth_Result的源代码,知道实例化的时候需要传入三个参数:

@param int $code 身份认证的结果(如:Zend_Auth_Result::SUCCESS)

@param mixed $identity 用于身份认证的标示符(如:登录名(张三))

@param array $messages 认证失败的原因数组

而一旦认证成功,则将信息存储到session变量中。

以上的校验工作都是在同一个action中完成的,这里,我使用的是Zend_Auth类中的getCode方法

getCode方法:返回一个zend_auth_resulet常量标识符用来决定认证失败的类型或者是否认证成功(详参:Zend_Auth_Result)


找不到身份表示的错误:Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND

无效认证导致的错误: Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID

认证成功: Zend_Auth_Result::SUCCESS

一般错误: Zend_Auth_Result::FAILUREZend_Auth中还有其他几个方法:

getStorage方法:

getIdentity():返回认证尝试的身份

isvalid() 返回true表示一个成功的认证尝试
getcode() 返回一个zend_auth_resulet常量标识符用来决定认证失败的类型或者是否认证成功
getIdentity() 返回认证尝试的身份
getmessages() 返回关于认证尝试失败的数组

代码如下 复制代码

$auth=Zend_Auth::getInstance();
if ($auth->hasIdentity()){
echo $auth->getIdentity(); //输出:张三
$auth->clearIdentity(); //清除身份
}
echo $auth->getIdentity(); //NULL
var_dump($auth->getStorage()); //Zend_Auth_Storage_Session对象 Zend_Acl进行权限控制

实现目标:当发送一个请求(eg:/index.php/shopping/showshop/)的时候,首先应该先进行判断,看是否具有访问权限

用图表示,大概就如下所示:

在action被Zend_Controller_Dispatcher派发之前,会先调用Zend_Controller_Plugin_Abstract类中的preDispatch()方法,因此我们可以继承Zend_Controller_Plugin_Abstract类,在子类中的preDispatch方法中进行权限判断。

代码如下:

代码如下 复制代码

Verify.php


require_once 'Zend/Controller/Plugin/Abstract.php';
class Verify extends Zend_Controller_Plugin_Abstract
{
/**
* 访问控制列表对象
* @var object
*/
protected $_acl;
/**
* 登录的用户名
* @var string
*/
protected $_loginname;

/**
* 构造函数
* 初始化访问控制列表
* @param Acl $acl
* @todo 未登录的时候,$this -> _loginname是为空的
*/
public function __construct($acl)
{
$this -> _acl = $acl;
require_once('Zend/Auth.php');
$this -> _loginname = Zend_Auth::getInstance()->getIdentity();//eg:张三
}

/**
* 重写父类的preDispatch方法
*
* @param Zend_Controller_Request_Abstract $request
*/
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
//请求信息
$module = $request -> module; //模块
$controller = $request -> controller; //请求的控制器
$action = $request -> action; //请求的action

$resource = ucfirst(strtolower($controller)); //资源:一个限制访问的对象
$action = strtolower($action);
$role = $this->_acl->getRole(); //角色:一个可以发出请求去访问Resource的对象

//判断是否拥有资源
if(!($this -> _acl -> has($resource))) {
$resource = null;
}

// $this->_acl->removeAllow($role,$resource); //可以针对某个role移除权限
//判断当前用户是有权限执行某个请求
if(!($this -> _acl -> isAllowed($role, $resource, $action))) {
if (!$this -> _loginname) {//未登陆的情况
$module = 'admin';
$controller = 'login';
$action = 'view';
}else { //没有权限的情况
echo "";
exit();
}
}
// else { //认证成功
//
// }
$request -> setModuleName($module);
$request -> setControllerName($controller);
$request -> setActionName($action);
}
}

解释:思路其实很简单,首先获取当前用户的角色(或超管或访客),然后使用isAllowed方法来判断请求者在整个 web 应用里是否拥有执行功能的许可,从而执行不同的流程控制。

但是,这里涉及到几个问题:

①preDispatch方法在哪里调用呢

②Verify类在哪里进行实例化

③访问控制列表(acl)如何定义

解答:

第一个问题:preDispatch在Zend_Controller_Action的dispatch方法中被调用(502行左右),该方法先于postDispatch被调用。

第二个问题:在入口文件index.php中进行实例化

$acl = new Acl();//自定义的Acl类
$fc = Zend_Controller_Front::getInstance();//取得Zend_Controller_Front类实例
$fc -> registerPlugin(new Verify($acl));以上代码片段的$acl = new Acl()也正是第三个问题将要回答的

第三个问题:关于访问控制列表的定义,及如何添加角色,添加资源,可以仔细看看官方手册,讲得很详细

http://framework.*z**end.com/manual/1.12/zh/zend.acl.introduction.html

Zend_Acl中的几个方法:

allow:增加一条“允许”规则到acl列表

如:$acl->allow('guest', null, 'view');//允许游客具有访问的权限

deny:增加一条“禁止”规则到acl列表

如:$acl->deny('guest', null, 'view');//禁止游客具有访问的权限

isAllowed:判断某个角色(role)是否有权访问某个资源(resource)

remove:删除某个资源及其子资源

removeAll:从ACL中删除所有的资源

removeAllow:从ACL中删除某个role有权访问的资源

removeDeny:从ACL中删除某个role禁止访问的资源

removeRole:从ACL中删除某个角色role

addRole:添加一个唯一的角色到ACL注册表中

hasRole:判断某个角色role是否已注册过

getRole:返回当前用户角色

getRoles:返回一个注册角色的数组

getResources:返回注册过的资源数组

在项目中,我们可以写一个Acl类,继承自Zend_Acl,而角色,资源等的添加定义都可以在自定义类中去实现

好比如这样:

Acl.php(自定义)

代码如下 复制代码
require_once('Zend/Acl.php');
/**
* 角色权限控制
*
*/
class Acl extends Zend_Acl
{
public function __construct()
{
$role = $this -> getRole(); //获取用户角色
if ($role=='guest') { //访客角色
$roleGuest=new Zend_Acl_Role('guest'); //创建角色guest
$this->addRole($roleGuest); //将角色添加到role注册表
$this -> add(new Zend_Acl_Resource('Login'));
$this -> add(new Zend_Acl_Resource('User'));
$this -> allow('guest', null, array('login')); //允许访客到登录界面
$this -> allow('guest', null, Array('showuserbydep'));//允许guest根据部门获取用户
}else { //登录用户的权限
//如果该角色不存在,则添加到Role注册表中,否则后面的代码会报错
//eg:Fatal error: Uncaught exception 'Zend_Acl_Role_Registry_Exception' with message 'Role 'admin' not found'
if (!$this->hasRole($role)) {
$this -> addRole(new Zend_Acl_Role($role));
}

//登录的用户都有的权限
$this -> add(new Zend_Acl_Resource('Index'));
$this -> add(new Zend_Acl_Resource('Dep'));
$this -> add(new Zend_Acl_Resource('Login'));
$this -> add(new Zend_Acl_Resource('Order'));
$this -> add(new Zend_Acl_Resource('Shopping'));
$this -> add(new Zend_Acl_Resource('User'));
$this -> add(new Zend_Acl_Resource('Authgroup'));

//第三个参数不写,默认具有访问整个控制器的权限
$this -> allow($role,'Index',Array('index'));
$this -> allow($role,'Login',Array('index','login','admin','top','left','view','welcome','logout'));
$this -> allow($role,'Order',Array('orderhistory'));
$this -> allow($role,'Shopping',Array('showmenu','uploadimage','showshop','showfood'));
$this -> allow($role,'User','index');
$this -> allow($role,'Dep',Array('showdep'));
$this -> allow($role,'Authgroup','index');

$rolePurview = $this -> getRolePurview($role);//角色的权限
//判断权限数据格式是否正确
if(!is_Array($rolePurview)){
echo '权限数据格式有错误!';exit();
}else{
foreach ($rolePurview as $controller => $actionArray){
//controller资源
$resource = ucfirst($controller);

//判断是否拥有资源
if(!$this -> has($resource)){//没有资源
$this -> add(new Zend_Acl_Resource($resource));//增加资源
}

//判断资源数据格式是否正确
if(!is_Array($actionArray) || empty($actionArray)){
echo '资源数据格式有错误!';exit();
}else{
foreach ($actionArray as $action){
$this -> allow($role, $resource, $action);//允许角色访问资源
}
}
}
}
}
}

/**
* 获取用户的角色名
* @return string
*/
public function getRole()
{
$ns = Zend_Auth::getInstance() -> getStorage() -> getNamespace();

//有session信息
if(isset($_SESSION[$ns])) {//有登录
if(isset($_SESSION[$ns]['role'])){//判断有没有登录用户的角色信息
$role = $_SESSION[$ns]['role'];
}else {
$role = 'guest';
}
}else {
$role = 'guest';
}
return $role;
}

/**
* 获得登录用户的数据数据
*
*/
public function getUserInfo()
{
$namespace = Zend_Auth::getInstance() -> getStorage() -> getNamespace();
//用户认证失败则返回false
if(isset($_SESSION[$namespace]['userInfo'])) {
return $_SESSION[$namespace]['userInfo'];
}else{
return false;
}
}
/**
* 获得权限数据
* @return Array 成功返回
* @return Array 失败返回
* @todo 只有成功登陆页面的情况下才有数据
*/
public function getUserPermission()
{
$namespace = Zend_Auth::getInstance() -> getStorage() -> getNamespace();
if(isset($_SESSION[$namespace]['resourcesPurview'])){
return $_SESSION[$namespace]['resourcesPurview'];
}else{
return false;
}
}
/**
* 根据角色获取权限(获取指定角色的访问控制列表)
*
* @param Array $role
* @return unknown
*/
public function getRolePurview($role)
{
if(!is_string($role)){
return false;
}else {
$rolePurview = $this -> getAllPurview();
return $rolePurview[$role];
}
}
/**
* 获取所有角色的权限(访问控制列表)
*
* @return Array
*/
public function getAllPurview()
{
$rolePurview = Array();
//判断缓存是否存在,有则从缓存中取,否则从数据库中取
if (!empty($_SESSION['allRolePurviews'])) {
$rolePurview = $_SESSION['allRolePurviews'];
}else{
$roleDao = Base_Dao_Factory::getObject('Admin_Models_Authgroup');
$result = $roleDao -> getAllGroup(Array('id', 'group_purview'));

if(!empty($result)){
foreach ($result as $key => $value){
$purview[$value['id']] = Zend_Json::decode($value['group_purview']);
}
}
$rolePurview = $purview;
$_SESSION['allRolePurviews'] = $purview;
}
return $rolePurview;
}
}

解释:自定义的Acl类中,我们首先要判断当前登录用户是访客还是系统用户,从而针对不同的用户角色给予不同的权限。

通过allow()我们可以很容易的针对不同的角色给予不同的权限控制。

注:对于出“访客”外的其他所有用户角色,都有一些默认的权限

现在我抛出一个问题:系统用户肯定有多个分组(比如:超级管理员和普通用户的权限定然不一样)那如何针对不同的用户组分配不同的权限呢?

这个容易,可以将不同组的权限存到数据库中,要用到的时候,根据不同的用户角色查得其所有的访问权限,在循环遍历下,通过allow将角色权限添加到acl中即可,也就是以上的代码片段:

代码如下 复制代码

//判断资源数据格式是否正确
if(!is_Array($actionArray) || empty($actionArray)){
echo '资源数据格式有错误!';exit();
}else{
foreach ($actionArray as $action){
$this -> allow($role, $resource, $action);//允许角色访问资源
}
}

比如:


Array
(
[admin] => Array
(
[Login] => Array
(
[0] => index
[1] => admin
[2] => top
[3] => left
[4] => view
[5] => welcome
)

[Authgroup] => Array
(
[0] => index
[1] => getpermission
[2] => edit
)

[Order] => Array
(
[0] => orderhistory
[1] => allorders
[2] => delorder
[3] => latestorders
[4] => nopayorders
[5] => changepaystate
[6] => ordersbyrestaurant
)

[Shopping] => Array
(
[0] => showmenu
[1] => uploadimage
[2] => showshop
[3] => showfood
[4] => addshop
[5] => delshop
[6] => editshop
[7] => updateshop
[8] => changestate
[9] => addfood
[10] => editfood
[11] => delfood
[12] => updatefood
)

[User] => Array
(
[0] =>
)

[System] => Array
(
[0] =>
)

[Dep] => Array
(
[0] => showdep
[1] => adddep
[2] => deldep
[3] => viewusersbydep
)

)

)

以上是“超级管理员”所拥有的权限

注意一点:在用户登录成功后,可以讲当前用户角色所拥有的权限信息存储到session变量中,这样以后就不必每次都查询数据库去获取了

至于,如何编辑用户组的权限,将其保存到数据库中,可以有多种方法。

相关文章

精彩推荐