PHP session token防止重复提交表单
我们提交表单的时候,不能忽视的一个限制是防止用户重复提交表单,因为有可能用户连续点击了提交按钮或者是攻击者恶意提交数据,那么我们在提交数据后的处理如修改或添加数据到数据库时就会惹上麻烦。
那么如何规避这中重复提交表单的现象出现呢?我们可以从很多方面入手,首先从前端做限制。前端JavaScript在按钮被点击一次后禁用,即disabled,这个方法简单的防止了多次点击提交按钮,但是缺点是如果用户禁用了javascript脚本则失效。第二,我们可以在提交后做redirect页面重定向,即提交后跳转到新的页面,主要避免F5重复提交,但是也有不足之处。第三,就是数据库做唯一索引约束。第四,就是做session令牌验证。
我们现在来了解下简单的利用session token来防止表单重复提交的方法。
我们在表单中加一个input隐藏域,即type="hidden",其value值用来保存token值,当页面刷新的时候这个token值会变化,提交后判断token值是否正确,如果前台提交的token与后台不匹配,则认为是重复提交。
<?php
/*
* PHP简单利用token防止表单重复提交
*/
session_start();
header("Content-Type: text/html;charset=utf-8");
function set_token() {
$_SESSION['token'] = md5(microtime(true));
}
function valid_token() {
$return = $_REQUEST['token'] === $_SESSION['token'] ? true : false;
set_token();
return $return;
}
//如果token为空则生成一个token
if(!isset($_SESSION['token']) || $_SESSION['token']=='') {
set_token();
}
if(isset($_POST['web'])){
if(!valid_token()){
echo "token error,请不要重复提交!";
}else{
echo '成功提交,Value:'.$_POST['web'];
}
}else{
?>
<form method="post" action="">
<input type="hidden" name="token" value="<?php echo $_SESSION['token']?>">
<input type="text" class="input" name="web" value="www.helloweba.com">
<input type="submit" class="btn" value="提交" />
</form>
<?php
}
?>
以上是一个简单的防止重复提交表单的例子,仅供参考。那么实际项目开发中,会对表单token做更复杂的处理,即我们说的令牌验证。可能要做的处理有:验证来源域,即来路,是否为外部提交;匹配要执行的动作,是添加、修改or删除;其次最重要的是构建token,token可以采用可逆的加密算法,尽可能复杂,因为明文还是不安全的。令牌验证的具体算法可以参考各大PHP框架,如ThinkPHP提供了很好的令牌验证功能。
上面的比较简单一点的方法,下面的代码更加安全一点。
Token.php
<?php
/*
* Created on 2013-3-25
*
* To change the template for this generated file go to
* Window - Preferences - PHPeclipse - PHP - Code Templates
*/
function getToken($len = 32, $md5 = true) {
# Seed random number generator
# Only needed for PHP versions prior to 4.2
mt_srand((double) microtime() * 1000000);
# Array of characters, adjust as desired
$chars = array (
'Q',
'@',
'8',
'y',
'%',
'^',
'5',
'Z',
'(',
'G',
'_',
'O',
'`',
'S',
'-',
'N',
'<',
'D',
'{',
'}',
'[',
']',
'h',
';',
'W',
'.',
'/',
'|',
':',
'1',
'E',
'L',
'4',
'&',
'6',
'7',
'#',
'9',
'a',
'A',
'b',
'B',
'~',
'C',
'd',
'>',
'e',
'2',
'f',
'P',
'g',
')',
'?',
'H',
'i',
'X',
'U',
'J',
'k',
'r',
'l',
'3',
't',
'M',
'n',
'=',
'o',
'+',
'p',
'F',
'q',
'!',
'K',
'R',
's',
'c',
'm',
'T',
'v',
'j',
'u',
'V',
'w',
',',
'x',
'I',
'$',
'Y',
'z',
'*'
);
# Array indice friendly number of chars;
$numChars = count($chars) - 1;
$token = '';
# Create random token at the specified length
for ($i = 0; $i < $len; $i++)
$token .= $chars[mt_rand(0, $numChars)];
# Should token be run through md5?
if ($md5) {
# Number of 32 char chunks
$chunks = ceil(strlen($token) / 32);
$md5token = '';
# Run each chunk through md5
for ($i = 1; $i <= $chunks; $i++)
$md5token .= md5(substr($token, $i * 32 - 32, 32));
# Trim the token
$token = substr($md5token, 0, $len);
}
return $token;
}
?>
form.php
<?php
include_once("token.php");
$token = getToken();
session_start();
$_SESSION['token'] = $token;
?>
<form action="action.php" method="post"
<input type="hidden" name="token" value="<?=$token?>" />
<!-- 其他input submit之类的 -->
</form>
action.php
<?php
session_start();
if($_POST['token'] == $_SESSION['token']){
unset($_SESSION['token']);
echo "这是一个正常的提交请求";
}else{
echo "这是一个非法的提交请求";
}
?>
ThinkPHP内置了表单令牌验证功能,可以有效防止表单的重复提交等安全防护。
表单令牌验证相关的配置参数有:
'TOKEN_ON'=>true, // 是否开启令牌验证
'TOKEN_NAME'=>'__hash__', // 令牌验证的表单隐藏字段名称
'TOKEN_TYPE'=>'md5', //令牌哈希验证规则 默认为MD5
'TOKEN_RESET'=>true, //令牌验证出错后是否重置令牌 默认为true
如果开启表单令牌验证功能,系统会自动在带有表单的模板文件里面自动生成以TOKEN_NAME为名称的隐藏域,其值则是TOKEN_TYPE方式生成的哈希字符串,用于实现表单的自动令牌验证。
自动生成的隐藏域位于表单Form结束标志之前,如果希望自己控制隐藏域的位置,可以手动在表单页面添加 标识,系统会在输出模板的时候自动替换。
如果页面中存在多个表单,建议添加标识,并确保只有一个表单需要令牌验证。
如果个别页面输出不希望进行表单令牌验证,可以在控制器中的输出方法之前动态关闭表单令牌验证,例如:
C('TOKEN_ON',false);
$this->display();
模型类在创建数据对象的同时会自动进行表单令牌验证操作,如果你没有使用create方法创建数据对象的话,则需要手动调用模型的autoCheckToken方法进行表单令牌验证。如果返回false,则表示表单令牌验证错误。例如:
$User = M("User"); // 实例化User对象
// 手动进行令牌验证
if (!$User->autoCheckToken($_POST)){
// 令牌验证错误
}