Zend\Authentication 应用实例(Yaf框架)

#用户表SQL
SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) NOT NULL,
  `email` varchar(128) NOT NULL,
  `password` varchar(1024) NOT NULL,
  `active` tinyint(1) DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `email_idx` (`email`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'vfeelit', 'vfeelit@qq.com', '4cbfa3a5874c68e0593c7a7c5ec7d4fc6c823235b71fc7fb96db51eceb2073d5db55e20b0a22098c8440b665c462141cc7dcfe1b25ff7d2be717aacaf8578d882b869ea8d7cba9ab2f82', '0');
INSERT INTO `user` VALUES ('2', 'ifeeline', 'ifeeline@qq.com', '4cbfa3a5874c68e0593c7a7c5ec7d4fc6c823235b71fc7fb96db51eceb2073d5db55e20b0a22098c8440b665c462141cc7dcfe1b25ff7d2be717aacaf8578d882b869ea8d7cba9ab2f82', '1');


################################
<?php
use Ifeeline\Registry;
use Ifeeline\Password;

use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Storage\Session as SessionStorage;
use Zend\Authentication\Result;
use Zend\Authentication\Adapter\DbTable\CredentialTreatmentAdapter;

use Table\UserModel;

class AuthPlugin extends Yaf\Plugin_Abstract 
{
    public function routerStartup(Yaf\Request_Abstract $request, Yaf\Response_Abstract $response)
    {
    }
     
    // 命令行方式不会经过这里
    public function routerShutdown(Yaf\Request_Abstract $request, Yaf\Response_Abstract $response )
    {
        // 路由之后才能获取这三个值
        $module = strtolower($request->getModuleName());
        $controller = strtolower($request->getControllerName());
        $action = strtolower($request->getActionName());
         
        $default = new Zend\Session\Container();
        if(!$request->isPost()){
            $default->offsetSet('securityToken',md5(uniqid(rand(),true)));
        }
         
        // 可以传入Zend\Authentication\Storage\Session对象,实际关联一个SESSION容器
        $auth = new AuthenticationService();
        if($auth->hasIdentity()) {
            $storage = $auth->getStorage();
            $storageData = $storage->read();
             
            $access_time = 0;
            if(!empty($storageData->access_time)) {
                $access_time = (int)$storageData->access_time;
            }
             
            // 已经半小时没有活动了 实际SESSION可能并没有清除
            if((time() - $access_time) > 1800) {
                $auth->clearIdentity();
                $response->clearBody()->setRedirect("/login/login");
                exit;
            } else {
                $storageData->access_time = time();
                $storage->write($storageData);
            }
             
            if(($controller === "login")) {
                if($action === "logout") {
                    $auth->clearIdentity();
                    $response->clearBody()->setRedirect("/login/login");
                    exit;
                }
                if($action === "login") {
                    $response->clearBody()->setRedirect("/");
                    exit;
                }
            }
        } else if($request->isPost()) {
            // 验证token
            if(!isset($_POST['securityToken']) || ($_POST['securityToken'] !== $default->offsetGet('securityToken'))) {
                $response->clearBody()->setRedirect("/login/login");
                exit;
            }
            
            // 需要验证的数据
            $email = trim($_POST['email']);
            $password = trim($_POST['password']);
            if(empty($email) || empty($password)) {
                $default->offsetSet("freshMessage", "邮件地址或密码不能为空");
                $response->clearBody()->setRedirect("/login/login");
                exit;
            }
            
            // 匹配邮件地址 和 密码
            $user = new Table\UserModel();
            $userRow = $user->getUserByEmail($email);
            if(!empty($userRow)) {
                // 查看是否已经被禁用
                if((int)$userRow['active'] < 1) {
                    $default->offsetSet("freshMessage", "账户已经禁用.");
                    $response->clearBody()->setRedirect("/login/login");
                    exit;
                }
                
                $hashPassword = trim($userRow['password']);
                $salt = Ifeeline\Password::getPasswordSaltByHash($hashPassword);
                $nowPassword = Ifeeline\Password::getPasswordHash($salt, $password);
                
                if($nowPassword !== $hashPassword) {
                    $default->offsetSet("freshMessage", "密码不正确");
                    $response->clearBody()->setRedirect("/login/login");
                    exit;
                }
            } else {
                $default->offsetSet("freshMessage", "邮件地址不存在");
                $response->clearBody()->setRedirect("/login/login");
                exit;
            }            
            
            // 实际上,以上的密码比较已经结束  这里使用它的会话持久化功能
            $dbAdapter = Registry::get('db');
            $authAdapter = new CredentialTreatmentAdapter($dbAdapter);
            $authAdapter
                ->setTableName('user')
                ->setIdentityColumn('email')
                ->setCredentialColumn('password');
         
            // 这里应该使用自定义的密码哈希算法,然后再传递进行比较
            $authAdapter
                ->setIdentity($email)
                ->setCredential($nowPassword);
              
            $result = $auth->authenticate($authAdapter);
         
            // 这个IF应该永不会进入
            if (!$result->isValid()) {
                switch ($result->getCode()) {
                    case Result::FAILURE_IDENTITY_NOT_FOUND:
                        //break;
                    case Result::FAILURE_CREDENTIAL_INVALID:
                        //break;
                    //case Result::SUCCESS:
                    //    break;
                    default:
                        //$result->getMessages()
                        $default->offsetSet("freshMessage", "用户名或密码不正确.");
                        break;
                }
                 
                $response->clearBody()->setRedirect("/login/login");
                exit;
            } else {                
                $row = $authAdapter->getResultRowObject(null, array('password'));
                // 账户被禁用(这不会执行)
                if((int)$row->active < 1) {
                    // 清楚认证信息
                    $auth->clearIdentity();
                     
                    $default->offsetSet("freshMessage", "用户名已经被禁用.");
                     
                    $response->clearBody()->setRedirect("/login/login");
                    exit;
                } else {
                    $row->access_time = time();
                     
                    $storage = $auth->getStorage();
                    $storage->write($row);
                     
                    // 成功登录
                    $response->clearBody()->setRedirect("/");
                    exit;
                }
            }
        } else {
            if(($controller !== "login") || (($controller === "login") && ($action !== "login"))) {
                $response->clearBody()->setRedirect("/login/login");
                exit;
            }
        }
    }
     
    public function preDispatch(Yaf\Request_Abstract $request, Yaf\Response_Abstract $response)
    {
    }
     
    public function postDispatch(Yaf\Request_Abstract $request, Yaf\Response_Abstract $response)
    {
    }
}

##对应控制器 模型 和 模板
<?php
use Ifeeline\Registry;
use Ifeeline\BaseController;
 
class LoginController extends BaseController
{
    public function loginAction()
    {
        // 取回登录失败信息
        $default = new Zend\Session\Container();
        if($default->offsetExists("freshMessage")){
            $this->_view->freshMessage = $default->offsetGet("freshMessage");
            $default->offsetUnset("freshMessage");
        }
        $this->_view->securityToken = $default->offsetGet("securityToken");
         
        $this->render("login/login.phtml");
    }
     
    public function logoutAction()
    {
        return false;
    }
}
###################################
<?php
namespace Table;

use Ifeeline\Registry;
use Exception;
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\Sql\Sql;
use Zend\Db\Sql\Select;

class UserModel extends TableGateway {
	protected $table = 'user';
	public function __construct(AdapterInterface $adapter = null, $features = null, ResultSetInterface $resultSetPrototype = null, Sql $sql = null){
		if($adapter instanceof Adapter){
			parent::__construct($this->table, $adapter, $features, $resultSetPrototype, $sql);
		}else{
			$adapter = Registry::get('db');
			if($adapter instanceof Adapter){
				parent::__construct($this->table, $adapter);
			}else{
				throw new Exception("Need an Zend\Db\Adapter object.");
			}
		}
	}
	
	// 根据邮件地址返回一行
	public function getUserByEmail($email=null)
	{
	    if(!empty($email) && is_string($email)) {
	        $current = $this->select(array('email'=>$email))->current();
	        if(!empty($current)) {
	            return $current->getArrayCopy();
	        }
	    }
	    return array();
	}
}
###################################
<div>
<?php 
if($this->freshMessage){
    print_r($this->freshMessage);
}
?>
</div>
<form action="/login/login" method="post">
<table style="width:500px;">
    <tr>
        <td style="width:150px; text-align:right">邮件地址</td>
        <td><input type="input" name="email" value="" /></td>
    </tr>
    <tr>
        <td style="width:150px; text-align:right">密码</td>
        <td><input type="password" name="password" value="" /></td>
    </tr>
    </tr>
        <td>
        <input type="hidden" name="securityToken" value="<?php echo $this->securityToken;?>" />
        </td>
        <td><input type="submit" value="提交" /></td>
    </tr>
</table>
</form>

// 同时贴上Bootstrap配置
<?php
use Yaf\Application;
use Yaf\Bootstrap_Abstract as BootstrapAbstract;
use Yaf\Dispatcher;
use Yaf\Route\Regex;

use Ifeeline\Registry;

use Zend\Db\Adapter\Adapter;
use Zend\Mail\Transport\Smtp as SmtpTransport;
use Zend\Mail\Transport\SmtpOptions;
use Zend\Session\Config\SessionConfig;
use Zend\Session\SessionManager;
use Zend\Session\Validator\HttpUserAgent;

class Bootstrap extends BootstrapAbstract {
	private $_config;

	public function _init(Dispatcher $dispatcher) {
	    // 引入Composer,Yaf扩展的配置项yaf.use_spl_autoload务必设置为1
	    if(file_exists(ROOT_PATH.'/vendor/autoload.php')){
	        $loader = include ROOT_PATH.'/vendor/autoload.php';
	        // 可以手工载入一批第三方库
	        // 明确指定命名空间对应的路径,有利于提升性能
	        $loader->add("",ROOT_PATH.'/library');
	        $loader->addPsr4("Zend\\",ROOT_PATH.'/library/Zend');
	        
	        Registry::set('loader', $loader);
	    }
	    
	    // 禁止自动渲染
	    $dispatcher->autoRender(FALSE);
	    
	    // 保存配置
		$this->_config = Application::app()->getConfig();
		Registry::set('config', $this->_config);

		// 报错设置
		if($this->_config->global->showError){
			error_reporting (-1);
			ini_set('display_errors','On');
		}
		
		// 命令行方式,跳过SESSION
		if(!defined("SKIP_SESSION")) {
		    // SESSION
		    $config = new SessionConfig();
		    
		    $sessionConfig = $this->_config->session->toArray();
		    if(isset($sessionConfig['save_path'])) {
		        @mkdir($sessionConfig['save_path'],0777,true);
		    }
		    
		    $config->setOptions($sessionConfig);
		    $manager = new SessionManager($config);
		    $manager->getValidatorChain()->attach('session.validate', array(new HttpUserAgent(), 'isValid'));
		    $manager->start();
		    if(!$manager->isValid()) {
		        $manager->destroy();
		        throw new \Exception("会话验证失败");
		    }
		    Registry::set('session', $manager);
		}

		// 数据库
		Registry::set('db',function(){
			$mysqlMasterConfig = $this->_config->mysql->master->toArray();
			$adapter = new Adapter($mysqlMasterConfig);
			return $adapter;
		});
		
        //
		Registry::set('job',function(){
            $jobConfig = $this->_config->mysql->job->toArray();
            
            //$jobConfig['driver'] = 'mysqli';
            // or
            unset($jobConfig['charset']);
            $jobConfig['driver'] = 'pdo_mysql';
            $jobConfig['driver_options']['1002'] = "SET NAMES UTF8;";

		    $jobAdapter = new Adapter($jobConfig);
		    return $jobAdapter;
        });
		
		// 邮件
		Registry::set('mail',function() {
		    $options   = new SmtpOptions($this->_config->smtp->toArray());
		    $mail = new SmtpTransport();
		    $mail->setOptions($options);
		    
		    return $mail;
		});
		
		// 日志
		Registry::set('logger', function() {
		    $logger = new Zend\Log\Logger;
		    $writer = new Zend\Log\Writer\Stream($this->_config->log->path.'/'.date("Ymd").".log");
		    
		    $logger->addWriter($writer);
		    return $logger;
		});
	}
	
	public function _initRoutes() {
		//Dispatcher::getInstance()->getRouter()->addRoute("xxx", new Regex(,,));
	}
	
	public function _initPlugin(Dispatcher $dispatcher) {
		$authPlugin = new AuthPlugin();
		$dispatcher->registerPlugin($authPlugin);
	}
}