`
helpbs
  • 浏览: 1163381 次
文章分类
社区版块
存档分类
最新评论

安全与表单验证

 
阅读更多
回顾

在我们第五天的学习中,我们已经习惯于操作模板与动作:表单与分页对于我们而言已不在神秘。但是在构建登陆表单之后,我们也许希望演示一下如何限制非授权用户对特定功能的访问。这就是我们今天所要学习的内容,以及一些表单验证的内容。因为我们要使用自定义的类来扩展程序,所以我们会对Symfony一书的自定义扩展一节的内容有更深的理解。

登陆表单验证

验证规则

登陆表单有一个nickname与password域。但是如果用户提交了不正确的数据时会发生什么情况呢?为了能够处理这种情况,在/frontend/modules/user/validate目录下(login是要验证的动作名)创建一个login.yml文件,并且添加下面的内容:

methods:
post: [nickname, password]

names:
nickname:
required: true
required_msg: your nickname is required
validators: nicknameValidator

password:
required: true
required_msg: your password is required

nicknameValidator:
class: sfStringValidator
param:
min: 5
min_error: nickname must be 5 or more characters

首先,在methods头下,定义了表单方法要进行验证的域列表(在这里我们只定义了POST方法,因为GET方法用于显示登陆表单而不需要验证)。然后,在names头下,列出了要检测的每一个表单域的需求,同时列出了相应的错误信息。实际上,因为nickname域声明为具有一个特殊的验证规则集合,所以在相应的头部下进行详细描述。在这个例子中,sfStringValidator是一个Symfony内建的验证器,用来检测一个字符串的格式(默认的Symfony验证器在Symfony一书的如何验证表单一节进行详细描述)。

错误处理

那么当用户输入错误的数据时会发生什么呢?如果并不满足login.yml文件中所写的条件,那么Symfony控制器就会将这个请求传递给userActions类的handleErrorLogin()方法,而不是form_tag参数中所设计的executeLogin()方法。如果这个方法不存在,默认行为则会显示loginError.php模板。这是因为默认的handleError()方法返回:

public function handleError()
{
return sfView::ERROR;
}

这是要编写的一个全新的模板。但是我们更希望重新显示登陆表单,并且将错误信息显示在相关的表单域附近。所以我们要修改显示的登陆错误行为,在我们这个例子中,loginSuccess.php模板为:

public function handleErrorLogin()
{
return sfView::SUCCESS;
}

模板错误帮助器

一旦再次调用loginSuccess.php模板,则会显示错误。我们将会使用验证帮助器组的form_error()帮助器。将模板的两个form-row div层改为下面的内容:

<?php use_helper('Validation') ?>

<div class="form-row">
<?php echo form_error('nickname') ?>
<label for="nickname">nickname:</label>
<?php echo input_tag('nickname', $sf_params->get('nickname')) ?>
</div>

<div class="form-row">
<?php echo form_error('password') ?>
<label for="password">password:</label>
<?php echo input_password_tag('password') ?>
</div>

如果发生错误,form_error()帮助器就会输出在login.yml文件中定义的错误。现在我们可以来测试一下表单验证了,我们可以输入一个小于5个字符的nickname,或是留空两个表单域来进行相应的测试。

现在密码是必须的,但是在数据中并没有密码。这并没有关系,只要我们输入密码,登陆就会成功。这并不是一个安全的过程,不是吗?

格式化错误

如果我们测试表单并且发生了错误,我们也许会注意到我们的错误信息格式并不是如上图所示的样子。这是因为我们定义了.form_error类格式,这是由form_error()帮助器所产生的表单错误的默认类:

.form_error
{
padding-left: 85px;
color: #d8732f;
}

授权用户

自定义验证器

我们是否还记得昨天在登陆动作中对所输入的用户是否存在的检测?是的,那看起来就是一个表单验证。这段代码应从这个动作中移出,并且包含在一个自定义验证器中。我们会认为这很复杂?事实上一点都不。编辑login.yml验证文件如下:

...
names:
nickname:
required: true
required_msg: your nickname is required
validators: [nicknameValidator, userValidator]
...
userValidator:
class: myLoginValidator
param:
password: password
login_error: this account does not exist or you entered a wrong password

我们只是为nickname域添加了一个新的验证器,myLoginValidator。这个验证器还不存在,但是我们知道对于完全授权的用户是需要密码的,所以他使用标签password作为参数传递。

密码存储

但是我们需要停留一下。在我们的数据模型以及测试数据中,并没有密码集合。现在是确定一个的时候了。但是我们知道从安全的角度来说,将密码以明文的形式存储在文本以及数据中是一个糟糕的主意。所以我们会使用随机值对必码进行哈希处理,并且存储密码的sha1哈希值。

所以我们要打开schema.xml文件,并且在User表中添加下面的列:

<column name="email" type="varchar" size="100" />
<column name="sha1_password" type="varchar" size="40" />
<column name="salt" type="varchar" size="32" />

使用symfony propel-build-model命令重新构建Propel模块。我们同时也应将这两列添加到数据库中,可以手动或者是使用symfony propel-build-sql命令后生成的lib.model.schema.sql。现在打开askeet/lib/model/User.php文件,并且添加下面的setPassword()方法:

public function setPassword($password)
{
$salt = md5(rand(100000, 999999).$this->getNickname().$this->getEmail());
$this->setSalt($salt);
$this->setSha1Password(sha1($salt.$password));
}

这个函数模拟一个直接的密码存储,但是所不同的是他存储的是随机键(一个32位的哈希化的随机字符串)与哈希化的密码(一个40位的字符串)。

在测试数据中添加密码

还记得第三天的测试数据文件吗?现在需要向测试用户中添加一个密码与一个email。打开并修改askeet/data/fixtures/test_data.yml文件如下:

User:
...
fabien:
nickname: fabpot
first_name: Fabien
last_name: Potencier
password: symfony
email: fp@example.com

francois:
nickname: francoisz
first_name: François
last_name: Zaninotto
password: adventcal
email: fz@example.com

因为我们已经为user类定义了setPassword()方法,所以当我们调用下面的命令时sfPropelData对象将会正确的处理sha1_password与salt列:

$ php batch/load_data.php

自定义验证器

现在需要编写我们自已的自定义myLoginValidator了。我们可以在模块可以访问的任何lib/目录下创建(也就是说在askeet/lib/,或是askeet/apps/frontend/lib/,或是askeet/apps/frontend/modules/user/lib/下)。就目前而言,我们认为这是一个程序相关的验证器,所以我们会在askeet/apps/frontend/lib/目录下创建myLoginValidator.class.php文件。

<?php

class myLoginValidator extends sfValidator
{
public function initialize($context, $parameters = null)
{
// initialize parent
parent::initialize($context);

// set defaults
$this->setParameter('login_error', 'Invalid input');

$this->getParameterHolder()->add($parameters);

return true;
}

public function execute(&$value, &$error)
{
$password_param = $this->getParameter('password');
$password = $this->getContext()->getRequest()->getParameter($password_param);

$login = $value;

// anonymous is not a real user
if ($login == 'anonymous')
{
$error = $this->getParameter('login_error');
return false;
}

$c = new Criteria();
$c->add(UserPeer::NICKNAME, $login);
$user = UserPeer::doSelectOne($c);

// nickname exists?
if ($user)
{
// password is OK?
if (sha1($user->getSalt().$password) == $user->getSha1Password())
{
$this->getContext()->getUser()->setAuthenticated(true);
$this->getContext()->getUser()->addCredential('subscriber');

$this->getContext()->getUser()->setAttribute('subscriber_id', $user->getId(), 'subscriber');
$this->getContext()->getUser()->setAttribute('nickname', $user->getNickname(), 'subscriber');

return true;
}
}

$error = $this->getParameter('login_error');
return false;
}
}

当验证器被调用时--在登陆表单提交之后--initialize()方法会被首先调用。他会被始化login_error信息的默认值('Invalid input'),并且将参数(login.yml文件中param:头部下的部分)组合到一个参数保持对象中。

然后execute()方法会被执行。$password_param是在login.yml文件中password头下面提供的。他用作一个由请求参数中获取值的域名字。所以$password中包含用户所输入的密码。$value为当前域的值--而myLoginValidator类是为被nickname域所调用的。所以$login包含用户所输入的用户名。最后,这个验证器包含验证一个用户所必须的所有数据了。

下面的代码会由登陆动作中移除。但是,密码的验证测试还没有实现:用户输入密码的哈希值与用户的哈希密码进行对比。

如果登陆名与密码是正确的,验证器就会返回真,而表单的目标动作(executeLogin())就会执行。否则,他会返回假,而handleErrorLogin()会被执行。

由动作中移除代码

现在所有的验证代码都位于验证器中了,我们需要将其从登陆动作中移除。确实,当使用POST方法调用动作时,这就意味着验证器验证这个请求,所以用户是正确的。这就意味着在这个例子中动作所需要做的唯一的事情就是重定向到referer页面:

public function executeLogin()
{
if ($this->getRequest()->getMethod() != sfRequest::POST)
{
// display the form
$this->getRequest()->getParameterHolder()->set('referer', $this->getRequest()->getReferer());

return sfView::SUCCESS;
}
else
{
// handle the form submission
// redirect to last page
return $this->redirect($this->getRequestParameter('referer', '@homepage'));
}
}

现在我们可以使用测试用户进行登陆来测试修改(在清除缓存之后,因为我们创建了一个新需要自动装入的验证器)。

限制访问

如果我们需要限制到一个动作的访问,我们只需要在模块config/目录下添加一个security.yml文件,如下所示(但是现在先不要做):

all:
is_secure: on
credentials: subscriber

这要只有当用户被授权时,这个模块的动作才会被执行。

在askeet中,只有当发表一个新问题,声明对一个问题的兴趣或者是评价时才需要登陆。而所有其他的动作都会对非登陆用户开放。

所以要限制对question/add动作的访问,在askeet/apps/frontend/modules/question/config/目录下添加下面的security.yml文件:

add:
is_secure: on
credentials: subscriber

all:
is_secure: off

重构

当验证密码为用户分配权限时执行了四行代码。我们可以将其看作myUser类的一个方法(会话类,而不是与User列相关的User类)。这很容易做到。将下面的代码添加到askeet/apps/frontend/lib/myUser.php类中:

public function signIn($user)
{
$this->setAttribute('subscriber_id', $user->getId(), 'subscriber');
$this->setAuthenticated(true);

$this->addCredential('subscriber');
$this->setAttribute('nickname', $user->getNickname(), 'subscriber');
}

public function signOut()
{
$this->getAttributeHolder()->removeNamespace('subscriber');

$this->setAuthenticated(false);
$this->clearCredentials();
}

现在将myLoginValidator类中由$this->getContext()->getUser()启动的四行代码改为:

$this->getContext()->getUser()->signIn($user);

同时将user/logout动作代码改为:

public function executeLogout()
{
$this->getUser()->signOut();

$this->redirect('@homepage');
}

subscriber_id与nickname会话属性同时也可以通过一个getter方法来进行抽象。仍然是在myUser类中,添加下面三个方法:

public function getSubscriberId()
{
return $this->getAttribute('subscriber_id', '', 'subscriber');
}

public function getSubscriber()
{
return UserPeer::retrieveByPk($this->getSubscriberId());
}

public function getNickname()
{
return $this->getAttribute('nickname', '', 'subscriber');
}

我们可以在layout.php文件中使用这些新方法,将下面的代码行:

<li><?php echo link_to($sf_user->getAttribute('nickname', '', 'subscriber').' profile', 'user/profile') ?></li>

替换为

<li><?php echo link_to($sf_user->getNickname().' profile', 'user/profile') ?></li>

不要忘记测试修改。

明天见
分享到:
评论

相关推荐

    js表单验证 表单验证类 整合

    js表单验证 表单的验证一直是网页设计者头痛的问题,表单验证类 Validator就是为解决这个问题而写的,旨在使设计者从纷繁复杂的表单验证中解放出来,把精力集中于网页的设计和功能上的改进上。 Validator是基于...

    一个Ajax表单检测验证实例.rar

    一个Ajax表单检测验证实例,在不刷新网页的情况下对表单中的各个输入项进行检查,并显示出错误提示,在众多ajax表单中,本示例并不是最复杂的,因此对于学习研究类似表单的...ajax无刷新表单验证插件就是基于此的实现。

    超强大的JS表单验证及使用方法教程

    超强大的JS表单验证及使用方法教程: 真实姓名只允许中文; 英文名只允许英文字母; 非法的Url; 密码不符合安全规则; 两次输入的密码不一致; 信箱格式不正确; QQ号码不存在; 身份证号码不正确; 电话号码不正确...

    一个非常强大完整的web表单验证程序

    表单的验证一直是网页设计者头痛的问题,表单验证类 Validator就是为解决这个问题而写的,旨在使设计者从纷繁复杂的表单验证中解放出来,把精力集中于网页的设计和功能上的改进上。 &lt;br&gt; Validator是基于...

    表单验证 Validator v4.0

    表单的验证一直是Web开发头痛的问题,表单验证类 Validator就是为解决这个问题而写的,旨在使设计者从纷繁复杂的表单验证中解放出来,把精力集中于UI设计与业务逻辑上。 Validator早期版本是基于JavaScript技术的伪...

    ASP.NET表单验证

    本文档详细介绍了ASP.NET安全机制,利用这个安全机制,可以最大程度的防止网站被非法侵入

    PHP表单验证类(不用一个一个自己去实现)

    PHP表单验证类(不用一个一个自己去实现)

    表单验证 Validator v4.0 案例 源码 帮助文档

    表单的验证一直是Web开发头痛的问题,表单验证类 Validator就是为解决这个问题而写的,旨在使设计者从纷繁复杂的表单验证中解放出来,把精力集中于UI设计与业务逻辑上。 Validator早期版本是基于JavaScript技术的伪...

    使用 HTML5 轻松验证表单

    从历史上看,构建表单验证一直很痛苦。在服务器端,全栈框架会为您处理它,这让这变得更容易,但在客户端,您通常最终会遇到需要花费大量精力才能集成的 JavaScript 库。 值得庆幸的是,HTML5 为我们提供了许多可以...

    注册验证实例会员注册表单验证代码.zip

    注册验证实例会员注册表单验证代码,用来验证用户注册会员的信息,是否满足要求,不满足则重新输入,也增加了安全性,php中文网推荐下载!

    实例讲解PHP表单验证功能

    PHP 表单验证 提示:在处理 PHP 表单时请重视安全性! 这些页面将展示如何安全地处理 PHP 表单。对 HTML 表单数据进行适当的验证对于防范黑客和垃圾邮件很重要! 我们稍后使用的 HTML 表单包含多种输入字段:必需...

    Javascript的表单验证-提交表单_.docx

    Javascript的表单验证-提交表单_.docx

    Struts2 Spring hibernate 国际化 表单验证 安全退出

    这是之前分页的升级版 带国际化 表单验证 用户登录 安全退出 功能都实现了就是不太美观 开发环境 myeclipse6.0 jdk1.6 Tomcat6.0 sql2000 数据库也放到里面了 在db文件夹下 库文件不变 这里就不附带了log.jsp或者log...

    Javascript表单验证控件(Validator v1.05).rar

    Javascript表单验证控件(Validator v1.05).rar --------------------------------- 内含以下两个文件: Validator.chm(详细的使用帮助文档) validator.js(源代码,当然没有prototype.js强大,但最...

    详解JavaScript表单验证(E-mail 验证)_.docx

    详解JavaScript表单验证(E-mail 验证)_.docx

    Javascript的表单验证长度_.docx

    Javascript的表单验证长度_.docx

    php基于表单密码验证与HTTP验证用法实例

    本文实例讲述了php基于表单密码验证与HTTP验证用法。分享给大家供大家参考。具体分析如下: PHP 的 HTTP 认证机制仅在 PHP 以 Apache 模块方式运行时才有效,因此该功能不适用于 CGI 版本。在 Apache 模块的 PHP ...

    Tomcat容器管理安全的验证方式汇总

    3、FORM(表单验证):在网页的表单上要求提供密码 4、CLIENT-CERT(客户端证书验证):以客户端证书来确认用户的身份 基本验证 当web.xml文件中的auth-method元素设置为BASIC时,表明应用使用的是基本验证,每次浏览器...

    头歌PHP语言之表单进阶

    头歌PHP语言之表单进阶 教程 四关教程

    php封装的表单验证类完整实例

    主要介绍了php封装的表单验证类,结合完整实例形式分析了php针对表单元素正则验证与类型判定的相关操作技巧,对于php程序设计的安全性有一定参考借鉴价值,需要的朋友可以参考下

Global site tag (gtag.js) - Google Analytics