Struts 的 Token(令牌)机制能够很好地解决表单重复提交的问题,基本原理是:服务器端在处理客户端的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将产生一个新的令牌,该令牌除传给客户端以外,也会替换用户会话中保存的旧令牌。这样操作后,如果用户回退到刚才的提交页面并再次提交,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止重复提交。
用来保存请求中的令牌值,例如:
value 是由 TokenProcessor 类中的 generateToken() 获得的,是根据当前用户的 session 对象和当前时间的 long 值来计算的。
在客户端提交后,判断请求中包含的值是否和服务器的令牌一致,因为服务器每次提交都会生成新的Token,因此如果是重复提交,客户端的Token值和服务器端的Token值就会不一致。
Struts 框架的 Token(同步令牌)机制在 org.apache.struts.action.Action 类中提供了一些 Token 相关的方法,下面就以在数据库中插入一条数据来说明防止页面重复提交的主要方法。
1) isTokenValid() 方法:
isTokenValid() 方法判断存储在当前用户会话中的令牌值和请求参数中的令牌值是否相同。如果相同,就返回 true,如果符合以下情况之一,便会返回 false。
2) resetToken() 方法:
resetToken() 方法从当前 session 会话中删除令牌属性。
3) saveToken() 方法:
saveToken() 方法用来创建一个新的令牌,并把它保存在当前 session 范围内。如果 HttpSession 对象不存在,就首先创建一个 HttpSession 对象。
4) add() 方法:在 Action 类的 add() 方法中,将令牌值保存在页面中只需增加一条语句:saveToken(request),关键代码如下:
public ActionForward add(ActionMapping mapping,ActionForm form,HttpServletRequest
request,HttpServletResponse response)
saveToken(request); //前面的处理省略
return mapping.findForward("add");
}
5) insert() 方法:在 Action 类的 insert() 方法中,将表单中的令牌值与服务器端的令牌值进行比较,关键代码如下:
public ActionForward insert(ActionMapping mapping,ActionForm form,
HttpServletRequest request,HttpServletResponse response)
if(isTokenValid(request,true)){ //表单不是重复提交//这里是保存数据的代码
}else{ //表单重复提交
s aveToken(request); //其他的处理代码
}
}
本示例应用 token 标签实现防止用户重复提交。
在添加用户之前,首先把请求转发给 PrepareInsertAction 类,它将调用 saveToken(request)方法创建一个新令牌,并把令牌保存在当前 session 会话中,然后 PrepareInsertAction 类把请求转发给添加用户的 insert.jsp 页面。PrepareInsertAction 类的关键代码如下:
public class PrepareInsertAction extends Action{
public ActionForward execute(ActionMapping mapping,ActionForm form,
HttpServletRequest request,
HttpServletResponse response){
saveToken(request); //创建一个新令牌
return mapping.findForward("prepareInsertAction");
}
}
在 insert.jsp 中的 <html:form> 标签的处理类中判断 session 会话中是否存在令牌,如果存在,就在表单中生成一个包含令牌信息的隐藏字段。insert.jsp 页面的关键代码如下:
<%@taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix="bean"%>
<%@taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html"%>
<html:form action="userInfoAction.do">
<table width="281" height="102" border="1">
<tr>
<td width="73" height="26" bgcolor="#000000"><div align="center" class=
"word_white">姓名</div></td>
<td width="192"><div align="center">
<html:text property="name"/><html:errors property="name"/>//定义文本框表单项
</div></td>
</tr>
<tr>
<td height="32"bgcolor="#000000"><div align="center" class="word_white">年龄
</div></td>
<td><div align="center">
<html:text property="age"/><html:errors property="age"/>
</div></td>
</tr>
<tr>
<td height="34"bgcolor="#000000"><div align="center" class="word_white">职业
</div></td>
<td><div align="center">
<html:text property="profession"/><html:errors property="profession"/>
</div></td>
</tr>
</table>
<input type="submit" name="Submit2" value="提交">&nbsp;&nbsp;&nbsp;//提交按钮
<input type="reset" name="Submit" value="重置">&nbsp;&nbsp;&nbsp;//重置按钮
<a href="index.jsp">返回</a>//返回超级链接
</html:form>
当用户收到 insert.jsp 页面后,在源文件中会看到表单中定义了一个包含令牌信息的隐藏字段,显示的代码如下:
<input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value=
"a9bf32c5fade032e405947bfe15ea18f">
在用户提交表单后,由 UserInfoAction 类处理请求,在 UserInfoAction 类中,首先调用 isTokenValid(request)方法,判断当前 session 会话中的令牌值和请求参数中的令牌值是否相同。如果相同,就调用 resetToken(request)方法,从当前会话中删除 Token,然后执行添加数据的操作。UserInfoAction 类的关键代码如下:
public ActionForward execute(ActionMapping mapping,ActionForm form,
HttpServletRequest request,
HttpServletResponse response){
UserInfoForm userInfoForm = (UserInfoForm)form; //获取与表单对应的ActionForm对象
userInfoForm.setAge(Integer.valueOf(request.getParameter("age"))); //设置ActionForm对象的age属性
userInfoForm.setName(Chinese.chinese(request.getParameter("name"))); //设置ActionForm对象的name属性
userInfoForm.setProfession(Chinese.chinese(request.getParameter("profession")));
ActionMessages errors = new ActionMessages() ; //创建ActionMessages对象
if(!isTokenValid(request)){ //判断session会话中的令牌值和请求参数中的值是否相等
errors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("error.invalid.token")); //向ActionMessages对象中添加对象
saveErrors(request,errors); //保存ActionMessages对象
saveToken(request); //创建新的令牌
request.setAttribute("success","错误!"); //将提示信息保存在request对象中
}else{
dao.addUserInfo(userInfoForm); //添加用户信息的方法
resetToken(request);
request.setAttribute("success","添加用户信息成功!");
}
return mapping.findForward("success");
}
在 Struts-config.xml 文件中配置 PrepareInsertAction 类和 UserInfoAction 类的关键代码如下:
<action-mappings>
<action name="userInfoForm" path="/userInfoAction" scope="request" type=
"com.action.UserInfoAction" validate="true">//配置Action
<forward name="success" path="/success.jsp"/>//请求转发地址
</action>
<action path="/prepareInsertAction" type="com.action.PrepareInsertAction">
<forward name="prepareInsertAction" path="/insert.jsp"/>
</action>
</action-mappings>
用户提交表单后,如果使用浏览器的后退功能退回到刚才的 insert.jsp 页面,并再次提交表单,其请求将由 UserInfoAction 类来处理,并弹出错误信息。