问题
在看Java Web 深入分析时, 看到表单重复提交问题一节, 如下描述如何解决问题:
要防止表单重复提交, 就要标识用户的每一次访问请求, 使得每一次访问对服务端来说都是唯一的. 为了标识用户的每次访问请求, 可以在用户请求一个表单域时,增加一个隐藏的表单项, 这个表单项的值每一次都是唯一的token. 如:
<form id = "form" method="post">
<input type=hidden name="crsf_token" value="XXX">
</form>
当客户在请求时生成这个唯一的token时, 同时将这个token保存在用户的Session中, 等用户提交请求时检查这个token和当前的Session中保存的是否一致. 如果一致, 则说明没有重复提交, 否则用户提交上来的token已经不是当前这个请求的合法token.
流程图如下:
但是让我迷惑的是: 访问服务器时获得唯一的token标识, 然后提交时带上这个标志, 服务器检测是否和自己Session中的内容一致. 为什么这样就可以防止重复提交? 我提交的第二次, 第三次还是带有相同的token啊, 服务器检测Session中的内容应该还是一致的. 为什么可以防止重复提交?
解决
然后上网查了下资料, 发现应该是作者少提了一点: 处理前移除Session中指定token.
就是说当服务器第一次收到客户端提交时, Session中是能检测到token的, 然后在准备处理时, 应该先移除Session中的指定token. 这样下一次客户端再次提交, 服务器检测token就会发现Session中没有对应token. 就可以拒绝服务.
remove if exist //如果存在, 那么移除
流程图如下:
优化
然后又想到了有没有什么优化措施, 可以减缓服务器的压力.
服务端的事情没有办法减少, 那么就从客户端入手, 当客户端重复提交时, 通过JavaScript脚本阻止用户提交. 当客户提交表格时, 可以通过JavaScript脚本里的变量来表示用户是否提交. 当然这种方法不可以绝对相信, 黑客可以伪造数据来通过这个检测. 所以服务器端的检测还是必不可少的, 但是可以大幅度减少用户无意识的多次提交表单, 从而减轻服务器压力.
<script>
var isCommitted=false;
function checkPost()
{
if(!isCommitted)
{
isCommitted=true;
return true;
}
else
{
alert("不能重复提交表单");
return false;
}
}
</script>
<form method="post" onsubmit="return checkPost()">
......
<input type="submit" value="提交">
</form>
}