Struts的Token(令牌)机制能够很好的解决表单重复提交的问题基本原理是服务器端在处理到达的请求之前会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较看是否匹配在处理完该请求后且在答复发送给客户端之前将会产生一个新的令牌该令牌除传给客户端以外也会将用户会话中保存的旧的令牌进行替换这样如果用户回退到刚才的提交页面并再次提交的话客户端传过来的令牌就和服务器端的令牌不一致从而有效地防止了重复提交的发生
这时其实也就是两点第一你需要在请求中有这个令牌值请求中的令牌值如何保存其实就和我们平时在页面中保存一些信息是一样的通过隐藏字段来保存保存的形式如 〈input type=hidden name=orgapaclTOKEN value=aaffdcccae〉这个value是TokenProcessor类中的generateToken()获得的是根据当前用户的session id和当前时间的long值来计算的第二在客户端提交后我们要根据判断在请求中包含的值是否和服务器的令牌一致因为服务器每次提交都会生成新的Token所以如果是重复提交客户端的Token值和服务器端的Token值就会不一致下面就以在数据库中插入一条数据来说明如何防止重复提交
在Action中的add方法中我们需要将Token值明确的要求保存在页面中只需增加一条语句saveToken(request);如下所示
public ActionForward add(ActionMapping mapping ActionForm form
HttpServletRequest request HttpServletResponse response)
//前面的处理省略
saveToken(request);
return mappingfindForward(add);
}在Action的insert方法中我们根据表单中的Token值与服务器端的Token值比较如下所示
public ActionForward insert(ActionMapping mapping ActionForm form
HttpServletRequest request HttpServletResponse response)
if (isTokenValid(request true)) {
// 表单不是重复提交
//这里是保存数据的代码
} else {
//表单重复提交
saveToken(request);
//其它的处理代码
}
}
其实使用起来很简单举个最简单最需要使用这个的例子
一般控制重复提交主要是用在对数据库操作的控制上比如插入更新删除等由于更新删除一般都是通过id来操作(例如updateXXXById removeXXXById)所以这类操作控制的意义不是很大(不排除个别现象)重复提交的控制也就主要是在插入时的控制了
先说一下我们目前所做项目的情况
目前的项目是用Struts+Spring+Ibatis页面用jstlStruts复杂View层Spring在Service层提供事务控制Ibatis是用来代替JDBC所有页面的访问都不是直接访问jsp而是访问Structs的Action再由Action来Forward到一个Jsp所有针对数据库的操作比如取数据或修改数据都是在Action里面完成所有的Action一般都继承BaseDispatchAction这个是自己建立的类目的是为所有的Action做一些统一的控制在Struts层对于一个功能我们一般分为两个Action一个Action里的功能是不需要调用Struts的验证功能的(常见的方法名称有addeditremoveviewlist)另一个是需要调用Struts的验证功能的(常见的方法名称有insertupdate)
就拿论坛发贴来说吧论坛发贴首先需要跳转到一个页面你可以填写帖子的主题和内容填写完后单击提交贴子就发表了所以这里经过两个步骤
转到一个新增的页面在Action里我们一般称为add例如
public ActionForward add(ActionMapping mapping ActionForm form
HttpServletRequest request HttpServletResponse response)
throws Exception {
//这一句是输出调试信息表示代码执行到这一段了
logdebug(:: action subject add);
//your code here
//这里保存Token值
saveToken(request);
//跳转到add页面在Structsconfigxml里面定义例如跳转到subjectAddjsp
return mappingfindForward(add);
}
在填写标题和内容后选择 提交 会提交到insert方法在insert方法里判断是否重复提交了
public ActionForward insert(ActionMapping mapping ActionForm form
HttpServletRequest request HttpServletResponse response){
if (isTokenValid(request true)) {
// 表单不是重复提交
//这里是保存数据的代码
} else {
//表单重复提交
saveToken(request);
//其它的处理代码
}
}
下面更详细一点(注意下面所有的代码使用全角括号)
你想发贴时点击我要发贴链接的代码可以里这样的
〈html:link action=subjectdo?method=add〉我要发贴〈/html:link〉
subjectdo 和 method 这些在structconfigxml如何定义我就不说了点击链接后会执行subjectdo的add方法代码如上面说的跳转到subjectAddjsp页面页面的代码大概如下
〈html:form action=subjectFormdo?method=insert〉
〈html:text property=title /〉
〈html:textarea property=content /〉
〈html:submit property=发表 /〉
〈html:reset property=重填 /〉
〈html:form〉
如果你在add方法里加了saveToken(request);这一句那在subjectAddjsp生成的页面上会多一个隐藏字段类似于这样〈input type=hidden name=orgapaclTOKEN value=aaffdcccae〉
点击发表后表单提交到subjectFormdo里的insert方法后你在insert方法里要将表单的数据插入到数据库中如果没有进行重复提交的控制那么每点击一次浏览器的刷新按钮都会在数据库中插入一条相同的记录增加下面的代码你就可以控制用户的重复提交了
if (isTokenValid(request true)) {
// 表单不是重复提交
//这里是保存数据的代码
} else {
//表单重复提交
saveToken(request);
//其它的处理代码
}
注意你必须在add方法里使用了saveToken(request)你才能在insert里判断否则你每次保存操作都是重复提交
记住一点Struts在你每次访问Action的时候都会产生一个令牌保存在你的Session里面如果你在Action里的函数里面使用了saveToken(request);那么这个令牌也会保存在这个Action所Forward到的jsp所生成的静态页面里
如果你在你Action的方法里使用了isTokenValid那么Struts会将你从你的request里面去获取这个令牌值然后和Session里的令牌值做比较如果两者相等就不是重复提交如果不相等就是重复提交了
由于我们项目的所有Action都是继承自BaseDispatchAction这个类所以我们基本上都是在这个类里面做了表单重复提交的控制默认是控制add方法和insert方法如果需要控制其它的方法就自己手动写上面这些代码否则是不需要手写的控制的代码如下
public abstract class BaseDispatchAction extends BaseAction {
protected ActionForward perform(ActionMapping mapping ActionForm form
HttpServletRequest request HttpServletResponse response)
throws Exception {
String parameter = mappinggetParameter();
String name = requestgetParameter(parameter);
if (null == name) { //如果没有指定 method 则默认为 list
name = list;
}
if (addequals(name)) {
if (addequals(name)) {
saveToken(request);
}
} else if (insertequals(name)) {
if (!isTokenValid(request true)) {
resetToken(request);
saveError(request new ActionMessage(errorrepeatSubmit));
logerror(重复提交!);
return mappingfindForward(error);
}
}
return dispatchMethod(mapping form request response name);
}
}