首先明确一个概念,防止重复提交和防止输入重复数据是两码事,防止重复数据的输入是DataBase应该完成的任务。在JavaEE中由JDBC或Hibernate完成;而防止重复提交是control和view层应该完成的任务。
-------------------------------------------我是光荣的分割线--------------------------------------------
请求有效性处理,使用令牌可以有效的防止重复提交。
protected String generateToken(HttpServletRequest request) 创建一个令牌.
protected boolean isTokenValid(HttpServletRequest request) 检查令牌是否有效
protected boolean isTokenValid(HttpServletRequest request,Boolean reset) 检查令牌是否有效,并且重置令牌(如果reset 是true)
protected void resetToken(HttpServletRequest request) 重置令牌
protected void saveToken(HttpServletRequest request) 添加令牌
Struts的Token(令牌)机制能够很好的解决表单重复提交的问题,基本原理是:
服务器端在处理请求(Action中的execute()方法用于处理请求)之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。
这时其实也就是两点:
第一:你需要在请求中有这个令牌值,请求中的令牌值如何保存,其实就和我们平时在页面中保存一些信息是一样的,通过隐藏字段来保存,保存的形式如:
<input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae">
这个value是TokenProcessor类中的generateToken()获得的,是根据当前用户的session id和当前时间的long值来计算的。
第二:在客户端提交后,我们要根据判断在请求中包含的值是否和服务器的令牌一致,因为服务器每次提交都会生成新的Token,所以,如果是重复提交,客户端的Token值和服务器端的Token值就会不一致。下面就以在数据库中插入一条数据来说明如何防止重复提交。
以我的TestCase系统为例
在Action中的queryAll()方法中,我们需要将Token值明确的要求保存在页面中,只需增加一条语句:saveToken(request) --> 将token添加到queryAll()方法的原因只有一个,我的insert()方法的前置方法就是queryAll()方法,也就是说,在处理insert请求之前必须先执行queryAll()方法。
public ActionForward queryAll(...) {
saveToken(req); //添加令牌, 用于在insert中避免重复提交
...
}
在Action的insert方法中,我们根据表单中的Token值与服务器端的Token值比较,如下所示:
public ActionForward insert(...) {
//判断令牌是否有效并重置令牌
if (isTokenValid(req, true)) {
...
}
...
}
-------------------------------------------我是光荣的分割线--------------------------------------------
这个例子是纯jsp例子,没有用到Action,帮助理解token机理。(非struts项目适用Token)
struts Token原理是: 生成令牌 --> 在表单隐含框里显示令牌值 --> 提交表单,对隐含框令牌值与系统存的令牌值进行比较.如果正确说明没重复提交.错误说明有重复提交 --> 清除令牌值
上面是原理,其实令牌值是以session值保存。所以只需要做2件事就可以实现struts token功能: 建2个页面:1个页面insertData.jsp(用于提交表单用),另一个页面saveData.jsp(接收表单值)
- <!-- insertData.jsp-->
- <%
- org.apache.struts.util.TokenProcessor.getInstance().saveToken(request);
- %>
- <form action="saveData.jsp" method="post">
- <input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="<%=session.getAttribute("org.apache.struts.action.TOKEN")%>" />
- <input type="text" name="username" />
- <input type="text" name="password" />
- <input type="submit" value="Submit" />
- </form>
- <!-- saveData.jsp-->
- <%
- Thread.sleep(3000);
- String username = null;
- String password = null;
- if(org.apache.struts.util.TokenProcessor.getInstance().isTokenValid(request,true)) {
- username = request.getParameter("username");
- password = request.getParameter("password");
- System.out.println(val+"*********username:"+username);
- System.out.println(val+"*********password:"+password);
- org.apache.struts.util.TokenProcessor.getInstance().resetToken(request);
- } else {
- org.apache.struts.util.TokenProcessor.getInstance().saveToken(request);
- System.out.println("error");
- }
- %>
saveToken方法就是生成一个令牌值,放在session里.session名为:org.apache.struts.action.TOKEN。
isTokenValid(request,true):判断提交令牌值是否和session值一样.true表示调用该方法后重新生成一个令牌值,保证只接受一次提交。
-------------------------------------------我是光荣的分割线--------------------------------------------
Struts本身有一套完善的防止重复提交表单的Token(令牌)机制。但如果不用struts framework就需要自写防止用户因为后退或者刷新来重复提交表单内容的Token机制。
实现原理:一致性。jsp生成表单时,在表单中插入一个隐藏<input>字段,该字段就是保存在页面端的token字符串,同时把该字符串存入session中。等到用户提交表单时,会一并提交该隐藏的token字符串。在服务器端,查看下是否在session中含有与该token字符串相等的字符串。如果有,那么表明是第一次提交该表单,然后删除存放于session端的token字符串,再做正常业务逻辑流程;如果没有,那么表示该表单被重复提交,做非正常流程处理,可以警告提示也可以什么也不做。
- public class Token {
- public static final String TOKEN = "token";
- public static String generateToken() {
- return (new Long(System.currentTimeMillis())).toString();
- }
- public static String getToken(HttpSession session) {
- String token = generateToken();
- saveToken(session, token);
- return token;
- }
- public static void saveToken(HttpSession session, String token) {
- ArrayList<String> tokenList = getTokenList(session);
- tokenList.add(token);
- session.setAttribute("tokenList", tokenList);
- }
- @SuppressWarnings("unchecked")
- public static ArrayList<String> getTokenList(HttpSession session) {
- Object obj = session.getAttribute("tokenList");
- if (obj != null) {
- return (ArrayList) obj;
- } else {
- ArrayList<String> tokenList = new ArrayList<String>();
- return tokenList;
- }
- }
- public static boolean isTokenValid(HttpSession session, String token) {
- boolean isValid = false;
- if (session != null) {
- ArrayList<String> tokenList = getTokenList(session);
- //如果tokenList中包含token(表单页生成的token(long类型)), 说明token未进行过tokenList.remove(token)处理, 即是第一次处理。
- //如果tokenList中不包含token, 说明此token已经进行过tokenList.remove(token)处理, 即意味着这是重复操作。
- if (tokenList.contains(token)) {
- isValid = true;
- tokenList.remove(token);
- }
- }
- return isValid;
- }
- }
下面是两个测试页面
- <!-- insert.jsp -->
- <%@ page import="edu.hust.common.Token"%>
- <form action="action.jsp" method="get">
- <input name="name">
- <input type="hidden" name="<%=test.TOKEN%>" value="<%=test.getToken(session)%>">
- <input type="submit" value="submit">
- </form>
- <!-- action.jsp -->
- <%@ page contentType="text/html;charset=GBK"%>
- <%@ page import="edu.hust.common.Token"%>
- <%
- String name = request.getParameter("name");
- String token = request.getParameter(test.TOKEN);
- System.out.println(token);
- if (test.isTokenValid(session, token)) {
- %>
- //进行insert()等操作
- <%
- } else {
- %>
- //为重复提交
- <%
- }
- %>