saveToken介绍二
Struts的Token(令牌)机制能够很好的解决表单重复提交的问题,基本原理是:服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。
这时其实也就是两点,第一:你需要在请求中有这个令牌值,请求中的令牌值如何保存,其实就和我们平时在页面中保存一些信息是一样的,通过隐藏字段来保存,保存的形式如: 〈input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae"〉,这个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 mapping.findForward("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,页面用jstl,Struts复杂View层,Spring在Service层提供事务控制,Ibatis是用来代替JDBC,所有页面的访问都不是直接访问jsp,而是访问Structs的Action,再由Action来Forward到一个Jsp,所有针对数据库的操作,比如取数据或修改数据,都是在Action里面完成,所有的Action一般都继承BaseDispatchAction,这个是自己建立的类,目的是为所有的Action做一些统一的控制,在Struts层,对于一个功能,我们一般分为两个Action,一个Action里的功能是不需要调用Struts的验证功能的(常见的方法名称有add,edit,remove,view,list),另一个是需要调用Struts的验证功能的(常见的方法名称有insert,update)。
就拿论坛发贴来说吧,论坛发贴首先需要跳转到一个页面,你可以填写帖子的主题和内容,填写完后,单击“提交”,贴子就发表了,所以这里经过两个步骤:
1、转到一个新增的页面,在Action里我们一般称为add,例如:
public ActionForward add(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
//这一句是输出调试信息,表示代码执行到这一段了
log.debug(":: action - subject add");
//your code here
//这里保存Token值
saveToken(request);
//跳转到add页面,在Structs-config.xml里面定义,例如,跳转到subjectAdd.jsp
return mapping.findForward("add");
}
2、在填写标题和内容后,选择 提交 ,会提交到insert方法,在insert方法里判断,是否重复提交了。
public ActionForward insert(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response){
if (isTokenValid(request, true)) {
// 表单不是重复提交
//这里是保存数据的代码
} else {
//表单重复提交
saveToken(request);
//其它的处理代码
}
}
下面更详细一点(注意,下面所有的代码使用全角括号):
1、你想发贴时,点击“我要发贴”链接的代码可以里这样的:
〈html:link action="subject.do?method=add"〉我要发贴〈/html:link〉
subject.do 和 method 这些在struct-config.xml如何定义我就不说了,点击链接后,会执行subject.do的add方法,代码如上面说的,跳转到subjectAdd.jsp页面。页面的代码大概如下:
〈html:form action="subjectForm.do?method=insert"〉
〈html:text property="title" /〉
〈html:textarea property="content" /〉
〈html:submit property="发表" /〉
〈html:reset property="重填" /〉
〈html:form〉
如果你在add方法里加了“saveToken(request);”这一句,那在subjectAdd.jsp生成的页面上,会多一个隐藏字段,类似于这样〈input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae"〉,
2、点击发表后,表单提交到subjectForm.do里的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 = mapping.getParameter();
String name = request.getParameter(parameter);
if (null == name) { //如果没有指定 method ,则默认为 list
name = "list";
}
if ("add".equals(name)) {
if ("add".equals(name)) {
saveToken(request);
}
} else if ("insert".equals(name)) {
if (!isTokenValid(request, true)) {
resetToken(request);
saveError(request, new ActionMessage("error.repeatSubmit"));
log.error("重复提交!");
return mapping.findForward("error");
}
}
return dispatchMethod2(mapping, form, request, response, name);
}
}
saveToken介绍二的更多相关文章
- Lucene.Net 2.3.1开发介绍 —— 二、分词(六)
原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(六) Lucene.Net的上一个版本是2.1,而在2.3.1版本中才引入了Next(Token)方法重载,而ReusableStrin ...
- Lucene.Net 2.3.1开发介绍 —— 二、分词(五)
原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(五) 2.1.3 二元分词 上一节通过变换查询表达式满足了需求,但是在实际应用中,如果那样查询,会出现另外一个问题,因为,那样搜索,是只 ...
- Lucene.Net 2.3.1开发介绍 —— 二、分词(三)
原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(三) 1.3 分词器结构 1.3.1 分词器整体结构 从1.2节的分析,终于做到了管中窥豹,现在在Lucene.Net项目中添加一个类关 ...
- Lucene.Net 2.3.1开发介绍 —— 二、分词(四)
原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(四) 2.1.2 可以使用的内置分词 简单的分词方式并不能满足需求.前文说过Lucene.Net内置分词中StandardAnalyze ...
- Lucene.Net 2.3.1开发介绍 —— 二、分词(二)
原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(二) 1.2.分词的过程 1.2.1.分词器工作的过程 内置的分词器效果都不好,那怎么办?只能自己写了!在写之前当然是要先看看内置的分词 ...
- Lucene.Net 2.3.1开发介绍 —— 二、分词(一)
原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(一) Lucene.Net中,分词是核心库之一,当然,也可以将它独立出来.目前Lucene.Net的分词库很不完善,实际应用价值不高.唯 ...
- {Django基础十之Form和ModelForm组件}一 Form介绍 二 Form常用字段和插件 三 From所有内置字段 四 字段校验 五 Hook钩子方法 六 进阶补充 七 ModelForm
Django基础十之Form和ModelForm组件 本节目录 一 Form介绍 二 Form常用字段和插件 三 From所有内置字段 四 字段校验 五 Hook钩子方法 六 进阶补充 七 Model ...
- MySQL之多表查询一 介绍 二 多表连接查询 三 符合条件连接查询 四 子查询 五 综合练习
MySQL之多表查询 阅读目录 一 介绍 二 多表连接查询 三 符合条件连接查询 四 子查询 五 综合练习 一 介绍 本节主题 多表连接查询 复合条件连接查询 子查询 首先说一下,我们写项目一般都会建 ...
- MySQL行(记录)的详细操作一 介绍 二 插入数据INSERT 三 更新数据UPDATE 四 删除数据DELETE 五 查询数据SELECT 六 权限管理
MySQL行(记录)的详细操作 阅读目录 一 介绍 二 插入数据INSERT 三 更新数据UPDATE 四 删除数据DELETE 五 查询数据SELECT 六 权限管理 一 介绍 MySQL数据操作: ...
随机推荐
- Uncaught SyntaxError: Unexpected identifier 报错 import Vue from 'vue';
一般情况是因为Webpack与webpack-dev-server版本不兼容导致. package.json { "name": "vue-loader-demo&quo ...
- ISE主备注册和同步
Synchronize Primary and Secondary Cisco ISE Nodes You can make configuration changes to Cisco ISE on ...
- Nginx笔试题!
1.Nginx实现HTTP及TCP负载均衡的模块?HTTP就是工作在七层协议TCP工作在四层协议 Nginx七层负载:七层通过虚拟的URL或主机名接收请求在server里面配置location反向代理 ...
- python学习 —— post请求方法的应用
声明:本篇仅基于兴趣以及技术研究而对B站曾经发生过的抢楼事件背后相关技术原理进行研究而写.请不要将其作为私利而对B站以及B站用户体验造成影响!谢谢合作!若本文对B站及其用户带来困扰,请联系本人删除本文 ...
- 理解WPF中的视觉树和逻辑树
轉載地址:http://blog.csdn.net/changtianshuiyue/article/details/26981797 理解WPF中的视觉树和逻辑树 Understanding th ...
- 如何在PHP中防止SQL注入
使用PDO对象(对于任何数据库驱动都好用) addslashes用于单字节字符串的处理, 多字节字符用mysql_real_escape_string吧. 另外对于php手册中get_magic_qu ...
- 时间选择器UIDatePicker的使用
UIDatePicker的介绍 UIDatePicker这个类的对象让用户可以在多个车轮上选择日期和时间.iPhone手机上的‘时钟’应用程序中的时间与闹铃中便使用了该控件.使用这个控件时,如果你能配 ...
- ubuntu13.10安装tomcat
步骤: ubuntu :13.10(32bit) -->i586 jdk 1.7 安装JDK 步骤: 1.官网下载如下图: 2.点击java SE7,下载jdk1.7 3.点击接受,并下载对应的 ...
- HDU 5570:balls 期望。。。。。。。。。。。。。。。
balls Accepts: 19 Submissions: 55 Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 65536/655 ...
- MySQL数据库索引:索引介绍和使用原则
本篇目录: 一.数据页与索引页 二.聚簇索引与非聚簇索引 三.唯一索引 四.索引的创建 五.索引的使用规则 六.数据库索引失效情况 本篇正文: 一.数据页与索引页 数据库的表存储分为数据页存储和索引页 ...