`
isiqi
  • 浏览: 16352979 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

安全与表单验证

阅读更多
回顾

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

不要忘记测试修改。

明天见
分享到:
评论

相关推荐

    DW笨阿猪高级表单验证

    5. **客户端验证与服务器端验证**:虽然客户端验证可以提供即时反馈,但为了安全,服务器端验证同样必不可少。"笨阿猪高级表单验证"可能会教你如何在服务器端进行二次验证,防止恶意篡改。 6. **错误提示**:良好的...

    js表单验证 全网最全的 表单验证

    在Web开发中,表单验证是一项至关重要的任务,它确保用户输入的数据符合预期,从而防止错误数据的提交,提升用户体验并保障数据安全。本资源集合了全网最全面的JavaScript表单验证方法,覆盖了从基础到高级的各种...

    js表单验证大全js表单验证大全js表单验证大全

    根据给定的文件信息,我们可以总结出一系列与JavaScript表单验证相关的知识点,这些知识点涵盖了从基本的输入检查到复杂的格式验证。以下是对这些知识点的详细解释: ### 1. 表单验证概述 表单验证是Web开发中的一...

    C# 表单验证,验证表单

    在Web开发或者桌面应用开发中,表单验证是非常重要的环节之一。它确保用户输入的数据符合预期格式,从而提高用户体验并防止潜在的安全风险。C# 提供了多种方式来实现表单验证。 ### 2. 常用的验证方法 #### 2.1 `...

    简单的表单验证

    在IT领域,表单验证是网页开发中必不可少的一部分,它确保用户输入的数据符合预设的规则,从而保证数据的有效性和安全性。"简单的表单验证"这个主题,通常涉及到JavaScript(JS)这一前端脚本语言,用于实现客户端的...

    bootstarp表单验证

    Bootstrap表单验证是一种在网页开发中确保用户输入数据有效性的技术。Bootstrap,作为最流行的前端框架之一,提供了优雅的样式和组件,使开发者能够轻松创建响应式和美观的界面。结合jQuery,我们可以实现动态和交互...

    表单验证自定义验证规则和错误信息

    在Web开发中,表单验证是必不可少的一部分,它确保用户输入的数据符合预设的规则,从而维护数据的完整性和安全性。本主题将深入探讨“表单验证自定义验证规则和错误信息”,帮助开发者构建更加灵活、用户体验良好的...

    漂亮的表单验证效果

    在React、Vue、Angular等现代前端框架中,表单验证往往与状态管理紧密结合,利用框架提供的API和指令实现便捷的验证逻辑。 8. **无障碍性**: 验证提示应遵循无障碍性标准,如使用ARIA属性,确保屏幕阅读器用户也...

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

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

    dw表单验证高级插件-asp表单验证高级插件-测试通过

    "dw表单验证高级插件-asp表单验证高级插件-测试通过"这个标题暗示了我们正在讨论的是一个针对Dreamweaver的高级插件,用于增强ASP(Active Server Pages)平台上的表单验证功能,并且已经通过了测试,证明其功能稳定...

    jquery表单验证实例网站会员注册表单验证提交form表单代码

    在网页开发中,表单验证是一项至关重要的任务,它确保用户输入的数据符合预期的格式和规则,从而提高数据质量和用户体验。jQuery作为一个强大的JavaScript库,提供了简单易用的API来进行表单验证。本实例将深入探讨...

    struts的表单验证

    表单验证是在用户提交数据前检查输入的有效性,以确保数据的完整性和安全性。在Struts框架中,表单验证通常通过两个主要方式实现:客户端验证和服务器端验证。 1. 客户端验证: 客户端验证发生在用户的浏览器上,...

    jsp表单验证实例

    **正文** 在网页开发中,表单验证是不可或缺的一部分,它确保用户输入的数据符合预设的规则,从而...学习这个实例可以帮助开发者理解如何在实际项目中构建安全、有效的表单验证机制,提升应用程序的质量和用户体验。

    非常强大的JQ表单验证

    在IT领域,尤其是在Web开发中,表单验证是不可或缺的一部分,它确保用户输入的数据符合预设的规则,从而保证数据的准确性和系统的安全性。这里我们关注的是“JQ表单验证”,这是一种基于jQuery库的轻量级解决方案,...

    简单实用表单验证实例

    在网页设计和开发中,表单验证是一项必不可少的技术,它确保用户输入的数据符合特定的规则,从而保护数据的完整性和安全性。"简单实用表单验证实例"是一个专注于这个主题的资源,提供了一种直观且易于使用的解决方案...

    表单验证验证联系.zip

    总结来说,表单验证是Web开发中不可或缺的一部分,HTML5提供了强大的客户端验证功能,结合服务器端验证和JavaScript可以构建出既安全又用户体验良好的表单。通过深入研究"表单验证验证联系.zip"中的内容,开发者可以...

    JavaScript程序设计——页面设置与表单验证实验报告.docx

    实验报告的标题“JavaScript程序设计——页面设置与表单验证实验报告.docx”涉及的核心是JavaScript编程中的两个关键领域:页面设置和表单验证。在Web开发中,JavaScript是一种常用的客户端脚本语言,用于增强用户的...

    Jquery表单验证特效示例

    10. **跨域提交与安全**:在实际应用中,还需要考虑跨域提交的安全问题,比如使用JSONP或CORS策略。 综上所述,"Jquery表单验证特效示例"涵盖了使用jQuery进行表单验证的基本原理、常见规则、自定义规则和视觉反馈...

    JQ,JS表单验证大全

    在网页开发中,表单验证是一项至关重要的任务,它确保用户输入的数据符合预期的格式和规则,从而保证数据的准确性和安全性。`JQ` 和 `JS` 是两种常见的JavaScript库,它们在处理表单验证时各有优势。`JQ`(jQuery)...

    强大的表单验证框架

    在IT领域,表单验证是Web开发中不可或缺的一部分,它确保用户输入的数据符合预设的规则和格式,从而提高数据的准确性和系统的安全性。"强大的表单验证框架"标题所指的,很可能是用于简化这一过程的高效工具,旨在...

Global site tag (gtag.js) - Google Analytics