(1)问题描述
一般网页按refresh或reload更新网页时,browser会重复执行上一次get或post动作,如果没有检查这样的行为,则Double submit在重要应用会有问题(可能会下多次订单)。
(2)解决方法
利用token比对方式,当正常的submit动作时,client送到controller的token与session中的token比对相同,则视为正常的submit。
若按下refresh送到controller的token会是上次的token,所以与session中的token不同,则视为refresh动作,此时可以显示警告信息。
(3)token同步令牌:
服务器端在处理到达的request之前,会将request中的token值与保存在当前用户session中的令牌值比较,看是否匹配。匹配则为正常的submit。在处理完request之后,且在response发送给客户端之前,会产生一个新的token,该token除了传给客户端外,也会将用户session中旧的token替换掉。这样,如果用户回退到刚才的提交页面并再次提交,则客户端传过来的token与服务器端不一致,所以避免重复提交。
(4)示例
按下“新增”会增加一条,并返回原页面;如果按下refresh会出现选择框,选择“重试”,则浏览器会重新执行post动作,若没有检查,则会再新增一条记录
<1>
CannotDoubleSubmitCommand cmd=(CannotDoubleSubmitCommand)command;
String action=cmd.getAction();
Long requestToken=cmd.getToken();//取出页面回传回来的Token
HttpSession session=request.getSession();
Long sessionToken=(Long)session.getAttribute(“TokenKey”);//取出session中的token
if(“Add”.equals(action)){
if(isTokenValid(requestToken,request)){//比较2个token是否相同
List<TaskDO> taskes=cmd.getTaskes();
TaskDO task=createTask(taskes.size()+1);
taskes.add(task);
Long nextToken=nextToken();//取到下一个token
session.setAttribute(“TokenKey”,nextToken);
cmd.setToken(nextToken);
}else{
errors.reject(null,“Invalid token, not allow to refresh page”);
Long next Token=nextToken();
//必须重新设定token,否则client无法再submit.
session.setAttribute(“TokenKey”,nextToken);
cmd.setToken(nextToken);
}
}
<2>
protected boolean isTokenValid(Long requestToken,HttpServletRequest request){
Long sessionToken=doGetSessionToken(request);
if(requestToken==null){
throw new RuntimeException(“Missing token in request”);
}
if(sessionToken==null){
throw new RuntimeException(“Missing token in session”);
}
if(sessionToken.equals(requestToken)){
return true;
}
return false;
}
<3>
protected Long nextToken(){
long seed=System.currentTimeMillis();
Random r=new Random();
r.setSeed(seed);
return r.nextLong();
}