概述
有些时候,我们在想某个网页提交了信息之后,由于某些原因,我们会重复点击提交,或者刷新页面,或者是在提交页面呈现之后点击后退按钮,从而导致这些表单数据被重复提交。在大多数情况下我们是不希望这种情况发生的,我们不可能强迫使用者不这么做,那么我们就只能自己想办法来尽量避免这些情况了。
下面我们来看一个例子:
public class HelloServlet extends HttpServlet {
private int i = 0;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String userName = req.getParameter("name");
String password = req.getParameter("password");
int count = 0;
for (int i = 0; i < 1000; i++) {
count++;
}
resp.getWriter().println(
i + ": hello" + userName + " your password is " + password);
i++;
}
}
input.jsp
<body>
<form action="Hello"method="post">
<input type="text"name="name"><br>
<input type="text"name="password"><br>
<input type="submit"value="submit">
</form>
</body>
测试(提交后刷新页面):
我们看到,在提交之后,刷新页面,同样会触发服务端的操作!
防止重复提交
对于防止表单重复提交,网上的朋友想出了很多方案。比如有用javascript来实现检测的,有通过提交后进行页面跳转来防止重复提交的,也有利用cookie来检测是否重复提交的。也有人利用session来判断是否重复提交的。当然,这些方法都有他们的用处,我个人觉得利用session来防止重复提交是最佳的方案。javascript防止不了页面刷新,重定向防止不了页面后退。对于cookie和session,由于session保存在服务端,所以要安全一点。(当然大多时候session也是基于cookie的)。Struts2中提供了一个专门的token标签来实现防止表单重复提交。基本原理就是我们在表单输入页面呈现之前生成一个独一无二的标记,将它放到表单页面(一般放在hidden表单中),并且同时将这个标记保存到session中。那么当页面提交时,我们首先检查请求参数中的标记与session中的标记是否相同,如果相同,将session中的标记删除,这次提交就是第一次提交。那么当表单重复提交时,由于session中的标记已经在第一次提交的时候被我们删掉了,因此在检测的时候就通不过,由此来判断表单为重复提交,再做出相应的处理。
jsp/servlet实现session方式防止重复提交
InputServlet.java
public class InputServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String token = String.valueOf(System.currentTimeMillis());
req.setAttribute("token",token);
req.getSession().setAttribute("token",token);
RequestDispatcher rd = req.getRequestDispatcher("/input.jsp");
rd.forward(req,resp);
}
}
HelloServlet.java
public class HelloServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String reqToken = req.getParameter("token");
String sessionToken = (String) req.getSession().getAttribute("token");
if (reqToken == null || !reqToken.equals(sessionToken)) {
resp.getWriter().println("repeat submit");
return;
}
//删除session中的token
req.getSession().removeAttribute("token");
String userName = req.getParameter("name");
String password = req.getParameter("password");
int count = 0;
for (int i = 0; i < 1000; i++) {
count++;
}
resp.getWriter().println(
": hello" + userName + " your password is " + password);
}
}
input.jsp
<body>
<form action="Hello"method="post">
<input
type="text" name="name">
<br>
<input
type="text" name="password">
<br>
<input
type="hidden" name="token"
value=<%=request.getAttribute("token")%>/>
<br />
<input
type="submit" value="submit">
</form>
</body>
测试(访问InputServlet):
这时我们查看页面源代码:
可以看到增加了一个hidden类型的token表单。
提交页面:
我们刷新页面:
Struts2实现防止表单重复提交
Struts2中的token标签可以用来生成一个唯一的标记,这个标记必须嵌套在form标签内使用,它将在表单里插入一个隐藏字段并把标记保存到Session中。这个token标签必须和token拦截器或者tokenSession拦截器配合使用,这两个拦截器的差别就是,使用token拦截器,如果发现表单重复提交,那么会返回”invalid.token”结果并且加上一条Action error,这个默认消息是:The form has already bean processed or no token was
supplied,pleasetry agin.这个消息看起来可能会让用户觉得以为是让他们”try agin”,要想覆盖这个消息,那么可以新建一个TokenInterceptor.properties添加一个键为struts.messages.invalid.token的消息,这个文件必须放在/WEB_INF/classes/org/apache/struts2/interceptor下。这个操作起来很麻烦。一般情况下只要我们将用户重复提交的请求忽略掉就可以了,那么tokenSession拦截器正是我们想要的。
需要注意的是是这两个拦截器都没在defaultStack拦截器栈中,需要我们手动添加到Action的拦截器栈中。
tokenSession拦截器对应的实现类是org.apache.struts2.interceptor.TokenSessionStoreInterceptor,它是token拦截器的实现类org.apache.struts2.interceptor.TokenInterceptor的子类。我们查看他们的源代码:
protected String doIntercept(ActionInvocation invocation) throws Exception {
if (log.isDebugEnabled()) {
log.debug("Intercepting invocation to check for valid transaction token.");
}
//see WW-2902: we need to use the real HttpSession here, as opposed to the map
//that wraps the session, because a new wrap is created on every request
HttpSession session = ServletActionContext.getRequest().getSession(true);
synchronized (session) {
if (!TokenHelper.validToken()) {
return handleInvalidToken(invocation);
}
}
return handleValidToken(invocation);
}
TokenHelper类的validToken方法:
public static boolean validToken() {
String tokenName = getTokenName();
if (tokenName == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("no token name found -> Invalid token ");
}
return false;
}
String token = getToken(tokenName);
if (token == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("no token found for token name "+tokenName+" -> Invalid token ");
}
return false;
}
Map session = ActionContext.getContext().getSession();
String sessionToken = (String) session.get(tokenName);
if (!token.equals(sessionToken)) {
if (LOG.isWarnEnabled()) {
LOG.warn(LocalizedTextUtil.findText(TokenHelper.class, "struts.internal.invalid.token", ActionContext.getContext().getLocale(), "Form token {0} does not match the session token {1}.", new Object[]{
token, sessionToken
}));
}
return false;
}
// remove the token so it won't be used again
session.remove(tokenName);
return true;
}
从中我们看以看出,处理的基本思想和我们前面使用jsp/servlet实现的差不多。只是token的生成转交的token标签来做,验证交给了拦截器来做。这整个过程都不许我们做任何操作.
下面动手做一个实例:
public class SubmitAction extends ActionSupport implements SessionAware {
private String name;
private String password;
private Map<String, Object> session;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public void setSession(Map<String, Object> session) {
this.session = session;
}
@Override
public String execute() throws Exception {
return SUCCESS;
}
}
input.jsp
<formaction="submit.action"method="post">
name : <input
type="text" name="name"/><br/>
password : <input
type="text" name=password/><br>
<s:token></s:token>
<input type="submit"value="submit"/>
</form>
struts.xml
<packagename="default"namespace="/"extends="struts-default">
<action name="submit"class="action.SubmitAction">
<result
name="success">/success.jsp</result>
<result
name="input">/input.jsp</result>
<interceptor-ref
name="tokenSession"></interceptor-ref>
<interceptor-ref
name="defaultStack"></interceptor-ref>
</action>
</package>
success.jsp
<body>
hello ${name } your password is ${password}
</body>
测试:提交表单后多次刷新,页面没有看到的是同样的响应,就像提交了一次一样。实际上重复的提交未做处理。如果开启了dev模式,那么会从控制台从看到一些信息:
警告: Form token5XIHXGFEA4F15ZJKN07B88V9956ZAMPN does not match the session token null.
其实只要理解了这些原理,不管是用jsp/servlet亦或是php都能够实现出来!
分享到:
相关推荐
根据给定的文件信息,以下是对Struts2学习笔记中涉及的关键知识点的详细解析: ### Struts2框架概览 #### MVC模式的理解与演进 Struts2是基于MVC(Model-View-Controller)模式设计的一种Java Web开发框架。在MVC...
### Struts2学习笔记知识点概览 #### 一、环境搭建 **1.1 Struts2简介** - **Struts2概述**:Struts2是一个开源的MVC框架,它结合了Struts 1.x、WebWork和其他一些框架的优点。Struts2的主要目标是简化Web应用程序...
### Struts2学习笔记之文件上传与Ajax开发 #### Struts2文件上传 **文件上传简介** 文件上传是Web应用中常见的功能之一,Struts2框架内置了对文件上传的支持,使得开发者能够轻松地实现这一功能。为了确保文件...
Struts2是一个强大的MVC(Model-View-Controller)框架,它在Java Web开发中扮演着重要的角色。本文将深入探讨Struts2的核心概念,包括Namespace、标签、Action以及它们在实际开发中的应用。 一、Namespace ...
防止表单重复提交是Web开发中的常见问题,Struts2通过令牌机制来解决这个问题,避免了同一操作被多次执行。 项目练习部分涉及到使用Ajax实现异步请求并返回JSON数据,这通常通过Struts2的JSON插件实现。文件下载则...
在Struts2的学习笔记中,`note.jsp`可能同样用于显示和编辑笔记,但使用的是Struts2的标签库,如`<s:property>`来显示模型对象的属性,以及`s:form`标签创建表单。Struts2允许直接在JSP页面中使用Action的属性,无需...
### Struts2学习笔记2012 #### 一、建立一个Struts2工程 **步骤详解:** 1. **创建Web项目:** - 在MyEclipse中选择“File” > “New” > “Dynamic Web Project”,创建一个新的Web项目。 2. **解压并配置...
这个"struts2学习笔记"涵盖了Struts2的核心概念、配置、动作、拦截器、结果类型等关键知识点,旨在帮助初学者深入理解并掌握Struts2框架。 1. **Struts2核心概念** - **Action**:是Struts2的核心,负责处理用户的...
### Struts2框架学习笔记整理 #### 一、Struts2框架概述 ##### 1.1 Struts2框架定义 Struts2是一个基于MVC(Model-View-Controller)架构模式的开源Java web应用框架,它能够帮助开发者构建可扩展且易于维护的...
### Unmi的Struts2学习笔记关键知识点解析 #### 一、Struts2与Struts1的主要区别 1. **架构上的变化**:Struts2并非Struts1的直接升级版,而是基于WebWork框架发展而来,这导致两者在设计哲学和技术实现上有很大...
- **token防止表单重复提交**:使用`<s:token>`标签可以生成一个隐藏字段,防止用户多次提交同一个表单。 通过以上内容的学习,可以全面了解Struts2.1框架的核心特性和使用方法,这对于开发者来说是非常宝贵的资源...
### Struts2学习笔记 #### 一、Struts2配置文件详解 在深入理解Struts2框架之前,我们首先需要了解其核心配置文件——`struts.xml`。 **1. struts.xml配置** ```xml <!DOCTYPE struts PUBLIC "-//Apache ...