Struts2学习笔记(十八) 防止表单重复提交

概述

有些时候,我们在想某个网页提交了信息之后,由于某些原因,我们会重复点击提交,或者刷新页面,或者是在提交页面呈现之后点击后退按钮,从而导致这些表单数据被重复提交。在大多数情况下我们是不希望这种情况发生的,我们不可能强迫使用者不这么做,那么我们就只能自己想办法来尽量避免这些情况了。

下面我们来看一个例子:

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都能够实现出来!

Java教学资源管理系统:Spring Boot+MyBatis-Plus权限设计与文件上传下载实践 在Java后端开发中,权限控制与资源管理是两类高频需求。RBAC模型通过用户-角色-权限三层拆分,避免了散落的身份判断;结合Spring Boot与MyBatis-Plus,可以快速搭建稳定的文件上传、下载与检索接口。这类技术方案不仅适用于教学资源管理系统,也能迁移到文档库、内容平台等场景。文章从系统设计、数据表结构到拦截器鉴权与部署避坑,提供一套完整的工程落地路径,适合课程设计及内部系统开发者参考。 阅读详情

相关推荐

struts2利用token防止表单重复提交(源代码)

struts2防止表单重复提交,利用struts的拦截器tokenSession,轻轻松松解决表单重复提交的问题。 附件为源代码,后台延迟了3秒,可直接在web服务器下部署运行,输入用户名和密码后,多点几次提交按钮,然后看控制台输出效果

struts2 使用了token 再次提交 报空指针(初次提交没有问题)[ 寻求帮助 ]

错误信息 ?? 13, 2020 11:27:22 ?? com.opensymphony.xwork2.util.logging.jdk.JdkLogger warn ??: Form token 7D52ZAHT13615ZNRIJ1SYXHZARFXC0NG does not match the session token null. ?? 13, 2020 11:27:22 ?? com.opensymphony.xwork2.util.logging.jdk.JdkLogger error ??:

Java的一只小实习生的博客 417

struts2 防止表单重复提交的例子

这里是Struts2 中放置表单重复提交的例子。

使用token拦截器(自定义拦截器的使用(不重要))

<br />    在页面的提交表单中加入<s:token/>标签,当请求该页面时,由服务器产生一个字符串,如:LO3ZSJ4MGAQH3VMFS425V62PK535THUI。可以通过查看ie源码查看生成的字符串。服务器产生这个字符串后将其放入到服务器的session里。同时客户端的字符串将随着提交,token拦截器在action执行之前会比较那个字符串与服务器session的字符串是否一致,比较一次后session中的字符串将会被删除。如果用户重复提交将不会通过。<br />Form token LO3

centre10的专栏 3717

Struts2防止重复提交解决方案

防止重复提交的总要解决方案 相信对struts的学者有帮助 大家看看

WebVTT中的文本轨道(Text Tracks,TT)到底是什么鬼?

WebVTT,全称是The Web Video Text Tracks Format,意思是网络视频文本轨道格式。那么到底什么是文本轨道(Text Tracks)呢?这篇文章希望一探究竟。

码农1479的博客 1985

处理struts2自带的错误提示

struts2-core.jar包中的 struts-messages.properties文件中有定义: struts.messages.invalid.token=The form has already been processed or no token was supplied, please try again. 你可以在自己的项目根目录里建立一个message_zh_CN

chaoloveyou的专栏 648

adams案例分析_案例1(麦弗逊悬架子系统创建)

本文字数3292字83图,建议阅读时间26分钟首先看下案例1的麦弗逊悬架数模结构就不细讲了,从数模上主要是获取硬点坐标(铰接点、安装点、参考点)及大部件结构(副车架、摆臂、转向节等实际运动过程中会产生变形的部件,在ADAMS Car中我们就可以用柔性体来搭建)再来回顾下上一篇讲到的模型元素1、Hardpoint——硬点(只有位置没有方向)2、Parameter Variable——参数...

weixin_39609407的博客 6474

Struts2中防止表单重复提交的两种方式

前言防止表单重复提交,这是个很重要的知识点,而且经常会用到。当用户提交了一个表单,此时,地址栏显示的是处理这个表单的Action的地址,若此时刷新,则会重新发送一次表单数据,即又进行了一次提交,若这个Action是用来处理用户注册的,那么重复提交会再一次向数据库中插入之前已经插入的数据,这显然不是我们想要的。有两种方法,可以防止表单重复提交,一种是用Action的重定向,一种是用Session To

nwpu_geeker的博客 1252

实战指南:CentOS 7下VOS3000 8.0从零部署到授权激活全流程

本文提供了一份详尽的CentOS 7系统下部署VOS3000 8.0的实战指南。从环境准备、使用FileZilla上传安装包、执行自动化安装脚本,到获取软件码、利用注册机生成授权文件并激活,手把手带领用户完成从零部署到投入商用的全流程,确保系统稳定运行。

zero1的博客 895

IC版图设计之OASIS文件格式详解

OASIS(Open Artwork System Interchange Standard)是继GDSII之后新一代的集成电路版图数据交换标准,由SEMI在2004年正式发布(SEMI标准号:SEMI P39)。高压缩率:相比GDSII可减少3-10倍文件体积扩展性强:支持纳米级工艺的复杂图形描述跨平台兼容:独立于EDA工具和操作系统安全增强:内置校验和与加密支持。

ZZcppc的博客 2435

Java进阶学习第十八天(Struts框架:数据校验、UI标签、数据回显、模型驱动、防止表单重复提交)

一、Struts数据效验 表单数据的验证: 前台验证:主要是通过JS验证, 表达数据是否合法! 后台验证:通过后台java代码进行验证! Struts也提供了数据效验的方式! Struts数据效验, 通过拦截器完成: 代码方式验证Action中所有的方法: 步骤: 1. 搭建struts环境 2. login.jsp 3.UserAction.java Struts数据效验错误信息处理: pub...

_Gerald的博客 377

Yolov5 多边形标签转换,所有json文件自动转成txt格式[详细过程]

注:labelme是麻省理工(MIT)的计算机科学和人工智能实验室(CSAIL)研发的图像标注工具,人们可以使用该工具创建定制化标注任务或执行图像标注,项目源代码已经开源。通过labelme对图进行标注后,得到的是json文件,而Yolov5对数据进行模型构建的时候,读取需要的是txt格式的文件。txt_outer_path——保存txt文本的文件夹的绝对路径。json_name——json_name是json文本的名字。json_floder——读取json的文件夹的绝对路径。

Pandas_007的博客 1万+

java filter 是否能拦截到form表单的所有数据_java 知识点总结(框架篇)

目录基础篇​zhuanlan.zhihu.com框架篇 一、 Struts1的运行原理 二、 Struts2的运行原理 三、 struts2的体系结构 四、 Spring MVC运行原理 五、 Struts1.x与Struts2.x的区别 六、 Spring MVC、struts1和struts2区别 七、 Struts2中result中的type类型 八、 Struts2标签 九、 SSI整合 ...

weixin_39755873的博客 623

从零构建腾讯滑块验证码破解引擎:逆向工程与实战对抗

本文深入解析腾讯滑块验证码(原天御验证码)的防护机制,包括环境感知层、行为分析层和验证决策层的核心技术。通过JSVMP虚拟机保护、轨迹验证算法和动态加密体系,腾讯验证码实现了高效的人机识别。文章还提供了安全研究实践指南,涵盖环境模拟和验证码识别方案,并探讨了防御对抗的演进趋势与合规研究建议。

rl6adventurer的博客 827

表格数据学习的图神经网络综述

今天学习一篇发表在中科院一区期刊《ACM Computing Surveys》的综述文章《Graph Neural Networks for Tabular Data Learning: A Survey with Taxonomy & Directions》,该刊主要刊载计算机理论方法领域的系统性综述与教程论文,注重跨学科技术融合研究,最新的影响因子为28.0。本文的目的在于通过学习这篇文章,了解关于表格数据学习点图神经网络一些基础知识。

qq_41009600的博客 798

java面试问题总结

目录 基础篇 4 一、JDK常用的包 4 二、 Get和Post的区别 4 三、 Java多态的具体体现 4 四、 StringBuffer StringBuilder String 区别 5 五、 Hashtable与HashMap的区别 5 六、 九大隐式对象 5 七、 Forword(请求转发)与Redirect(重定向) 6 八、JQurey总结 6 九、 XML和Js...

qq_36863199的博客 2440

uniapp(安卓苹果app端) - 微信小程序分享提示报错“由于不支持的分享类型无法分享到微信“,“由于应用和小程序未绑定在同意微信开放平台账号,无法分享到微信“解决办法,uniapp小程序分享失败

uni-app,安卓app,苹果app,手机app端,苹果ios,安卓Android,移动端app,分享,微信分享,微信朋友圈,微信好友,转发,vue2,vue3,App端调用手机的系统分享,微信小程序,分享功能,原生app分享小程序到微信,可分享到微信、朋友圈等,报错,uniapp分享报错由于不支持的分享类型无法分享到微信,未审核应用,uniApp安卓苹果app报错由于应用和小程序未绑定在同意微信开放平台账号,无法分享到微信,微信appid,已经配置了,还是报错,无法解决,一直提示,APP_ID错误,打包

✅ 精选推荐文章 5509

Java基础知识点总结(面试版)

  目录 基础篇 4 一、JDK常用的包 4 二、 Get和Post的区别 4 三、 Java多态的具体体现 4 四、 StringBuffer StringBuilder String 区别 5 五、 Hashtable与HashMap的区别 5 六、 九大隐式对象 5 七、 Forword(请求转发)与Redirect(重定向) 6 八、JQurey总结 6 九、 XML...

小小木子渐成长 1967

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

Java面试整理 -码之狼

_面试概念整理以及项目逻辑分享 注意:( 所有的概念都是结合所学知识去记忆,死记硬背并不会有太好的记忆并且难以结合到自己的项目中去) 一、JDK常用的包 二、 Get和Post的区别 三、 Java多态的具体体现 四、 StringBuffer StringBuilder String 区别 五、 Hashtable与HashMap的区别 六、 九大隐式对象 七、 Forword(请求转发)与Re...

JingTao_Wu的博客 1069

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

java统一复习

5目录 目录 1 基础篇 4 一、JDK常用的包 4 二、 Get和Post的区别 4 十一、 Page和PageContext的区别 7 十二、 Ajax总结三、 Java多态的具体体现 4 四、 StringBuffer StringBuilder String 区别 5 五、 Hashtable与HashMap的区别 5 六、 九大隐式对象 6 七、 Forword(请求转发)与Redirect(重定向) 6 八、JQurey总结 6 九、 XML和Json的特点 6 .

weixin_48364455的博客 3679

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

参考

目录 目录 1 基础篇 4 一、JDK常用的包 4 二、 Get和Post的区别 4 三、 Java多态的具体体现 4 四、 StringBuffer StringBuilder String 区别 5 五、 Hashtable与HashMap的区别 5 六、 九大隐式对象 5 七、 Forword(请求转发)与Redirect(重定向) 6 八、JQurey总结 6 九、 XML和Json的特点...

aiqutianli的博客 690

Spring MVC深入分析-优快云博客

为开发团队选择一款优秀的MVC框架是件难事儿,在众多可行的方案中决择需要很高的经验和水平。你的一个决定会影响团队未来的几年。要考虑方面太多:1、简单易用,以提高开发效率。使小部分的精力在框架上,大部分的精力放在业务上。2、性能优秀,这是一个最能吸引眼球的话题。3、尽量使用大众的框架(避免使用小众的、私有的框架),新招聘来的开发人员有一些这方面技术积累,减低人员流动再适应的影响。如果你还在为这件事件发愁,本文最适合你了。选择Spring MVC吧。

求知若渴、虚心若愚 341
上一篇: Struts2学习笔记(十七) 文件下载(File Download)
下一篇: Struts2学习笔记(十八) 防止表单重复提交
jdluojing
博客等级 码龄17年 79粉丝 47原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值