更新:本文现已提供法语版本,由FrédéricBlanc提供。
我已经回答了一些想知道如何在ZendFramework中处理身份验证和身份持久性的人提出的问题。典型的问题是他们不确定如何组合:
- 身份验证适配器
- 登录表单
- 用于登录/注销操作的控制器
- 在后续请求中检查经过身份验证的用户
这并不难,但确实需要了解MVC的各个部分如何组合在一起,以及如何使用Zend_Auth。让我们来看看。
身份验证适配器
要使所有这些工作正常,您需要一个身份验证适配器。我不打算对此进行详细说明,因为文档涵盖了它们,并且您的需求会因您的站点而异。但是,我将假设您的身份验证适配器需要用户名和密码来进行身份验证。
我们的登录控制器将使用适配器,但只是有一个用于检索它的占位符。
登录表单
登录表单本身非常简单。您可以设置一些基本的验证规则,以防止数据库或其他服务命中,但要保持相对简单。出于本教程的目的,我们将定义以下标准:
- 用户名只能是字母字符,并且必须包含3到20个字符
- 密码必须只能是字母数字字符,并且必须是6到20个字符
表单看起来像这样:
class LoginForm extends Zend_Form
{
public function init()
{
$username = $this->addElement('text', 'username', array(
'filters' => array('StringTrim', 'StringToLower'),
'validators' => array(
'Alpha',
array('StringLength', false, array(3, 20)),
),
'required' => true,
'label' => 'Your username:',
));
$password = $this->addElement('password', 'password', array(
'filters' => array('StringTrim'),
'validators' => array(
'Alnum',
array('StringLength', false, array(6, 20)),
),
'required' => true,
'label' => 'Password:',
));
$login = $this->addElement('submit', 'login', array(
'required' => false,
'ignore' => true,
'label' => 'Login',
));
// We want to display a 'failed authentication' message if necessary;
// we'll do that with the form 'description', so we need to add that
// decorator.
$this->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form')),
array('Description', array('placement' => 'prepend')),
'Form'
));
}
}
登录控制器
现在,让我们创建一个控制器来处理登录和注销操作。典型的流程是:
- 用户点击登录表单
- 用户提交表单
- 控制器处理表单
- 验证错误重新显示带有错误消息的表单
- 成功验证重定向到主页
- 登录用户被重定向到主页
- 注销操作注销用户并重定向到登录表单
李>
LoginController将使用您选择的身份验证适配器以及登录表单。我们将把表单和方法传递给登录表单构造函数(因为我们现在知道它们对于表单的这种用法是什么)。当我们有有效值时,我们会将它们传递给我们的身份验证适配器。
那么,让我们来创建控制器。首先,我们将为表单和身份验证适配器创建访问器。
class LoginController extends Zend_Controller_Action
{
public function getForm()
{
return new LoginForm(array(
'action' => '/login/process',
'method' => 'post',
));
}
public function getAuthAdapter(array $params)
{
// Leaving this to the developer...
// Makes the assumption that the constructor takes an array of
// parameters which it then uses as credentials to verify identity.
// Our form, of course, will just pass the parameters 'username'
// and 'password'.
}
}
接下来,我们需要在派发任何操作之前进行一些检查,以确保:
- 如果用户已经通过认证,但是没有请求退出,我们应该重定向到首页
- 如果用户没有通过认证,但是已经请求退出,我们应该重定向到登录页面
下面的preDispatch()例程将为我们做这件事:
class LoginController extends Zend_Controller_Action
{
// ...
public function preDispatch()
{
if (Zend_Auth::getInstance()->hasIdentity()) {
// If the user is logged in, we don't want to show the login form;
// however, the logout action should still be available
if ('logout' != $this->getRequest()->getActionName()) {
$this->_helper->redirector('index', 'index');
}
} else {
// If they aren't, they can't logout, so that action should
// redirect to the login form
if ('logout' == $this->getRequest()->getActionName()) {
$this->_helper->redirector('index');
}
}
}
}
现在,我们需要做我们的登录表单。这是我们最简单的方法——我们只需检索表单并将其分配给视图:
class LoginController extends Zend_Controller_Action
{
// ...
public function indexAction()
{
$this->view->form = $this->getForm();
}
}
处理表单涉及的逻辑稍微多一些。我们需要验证我们有一个发布请求,然后验证表单是否有效,最后验证凭据是否有效。
class LoginController extends Zend_Controller_Action
{
// ...
public function processAction()
{
$request = $this->getRequest();
// Check if we have a POST request
if (!$request->isPost()) {
return $this->_helper->redirector('index');
}
// Get our form and validate it
$form = $this->getForm();
if (!$form->isValid($request->getPost())) {
// Invalid entries
$this->view->form = $form;
return $this->render('index'); // re-render the login form
}
// Get our authentication adapter and check credentials
$adapter = $this->getAuthAdapter($form->getValues());
$auth = Zend_Auth::getInstance();
$result = $auth->authenticate($adapter);
if (!$result->isValid()) {
// Invalid credentials
$form->setDescription('Invalid credentials provided');
$this->view->form = $form;
return $this->render('index'); // re-render the login form
}
// We're authenticated! Redirect to the home page
$this->_helper->redirector('index', 'index');
}
}
最后,我们可以处理注销操作。这几乎和显示登录表单一样简单;我们只需从身份验证对象中清除身份,然后重定向:
class LoginController extends Zend_Controller_Action
{
// ...
public function logoutAction()
{
Zend_Auth::getInstance()->clearIdentity();
$this->_helper->redirector('index'); // back to login page
}
}
好的,这就是我们的登录/注销例程。让我们看一下我们拥有的一个关联视图,形式为:
<? // login/index.phtml ?> <h2>Please Login</h2> <?= $this->form ?>
就是这样。真的。Zend_Form使视图脚本变得简单。:-)
检查经过身份验证的用户
问题区的最后一部分是:如何判断用户是否已通过身份验证,如果没有则限制访问?
如果您仔细查看上面的preDispatch()方法,您就会明白这是如何完成的。Zend_Auth在会话中保留身份,允许您使用此构造直接查询它:
Zend_Auth::getInstance()->hasIdentity()
你可以用它来判断用户是否登录,如果没有登录则使用redirector重定向到登录页面。您也可以从auth对象中提取身份:
$identity = Zend_Auth::getInstance()->getIdentity();
这可以添加到帮助程序中以在您的布局中显示登录状态,例如:
/**
* ProfileLink helper
*
* Call as $this->profileLink() in your layout script
*/
class My_View_Helper_ProfileLink
{
public $view;
public function setView(Zend_View_Interface $view)
{
$this->view = $view;
}
public function profileLink()
{
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
$username = $auth->getIdentity()->username;
return '<a href=\"/profile' . $username . '\">Welcome, ' . $username . '</a>';
}
return '<a href=\"/login\">Login</a>';
}
}
结论
Zend_Auth做了很多幕后工作,使在会话中持久化身份变得微不足道。将它与Zend_Form相结合,您就拥有了一个非常易于实施的检索和验证凭据的解决方案;在Zend_Controller组件中添加标准挂钩以在发送之前过滤操作,并且您可以根据身份验证状态轻松限制对应用程序的访问。
