javaEE开发中使用session同步和token机制来防止并发重复提交

通常在普通的操作当中,我们不需要处理重复提交的,而且有很多方法来防止重复提交。比如在登陆过程中,通过使用redirect,可以让用户登陆之上重定向到后台首页界面,当用户刷新界面时就不会触发重复提交了。或者使用token,隐藏在表单中,当提交时进行token验证,验证失败也不让提交。这都是一般的做法。

我们这次碰到的问题是重复提交本身就是一个错误,重复提交会导致一些相关数据的逻辑不再正确。而这些重复提交并不是通过普通的刷新界面,或者两次点击按钮来进行的。在普通的操作当中,我们可以通过一系列的手段,使得相应参数被清零,从而防止数据上的不正确。但是,在一种情况下,这些手段都不再有效,那就是并发的重复提交。
    并发重复提交,那就是在同一时间内(时间间隔可以缩短到0.X秒之内),在这种情况下,所有的常规逻辑都不再有效,因为多个请求,同时进入系统,系统已不能判断出这些请求是否是无效的,它们同时通过常规的重复逻辑判断,并最终在同一时间内将数据写入到数据库中,引起数据错误。

举一个简单的例子,在系统中销售一个商品,首先通过该商品id进入到系统逻辑判断,判断此商品是否已售出,如果未售出,就进行数据存取操作。商品是否售出,是一个逻辑判断,是验证数据存储到数据库的一道门。在常规的判断当中,前一请求通过这道门之后,后一请求就不能通过了,因为验证为false。但在并发请求中,两个或多个请求同时通过了这道门,因为都是同时进入到判断,在判断之前都验证商品没有被售出,所以就同时进入到数据的存储当中。

在常规的java开发中,对于这种情况,临界资源,通常是使用加锁来保证这种情况的先后顺序。但是加锁有一个问题即是,它是对于全局信息的加锁,即对整个将要销售的商品进行加锁了。对于BS应用来说,我们必须保证另一个操作人员的同一种商品的销售请求通过,即只限制同一个操作人员销售的并发请求,不限制多个操作人员不同请求的处理。
    在这种情况下,我们的加锁就不能简单的锁定在商品上,而是要锁定在与操作人员有关的信息上,这就是session。

session是一个在单个操作人员整个操作过程中,与服务器端保持通信的惟一识别信息。在同一操作人员的多次请求当中,session始终保证是同一个对象,而不是多个对象,因为可以对其加锁。当同一操作人员多个请求进入时,可以通过session限制只能单向通行。
    本文正是通过使用session以及在session中加入token,来验证同一个操作人员是否进行了并发重复的请求,在后一个请求到来时,使用session中的token验证请求中的token是否一致,当不一致时,被认为是重复提交,将不准许通过。
    整个流程可以由如下流程来表述:

  1. 客户端申请token
  2. 服务器端生成token,并存放在session中,同时将token发送到客户端
  3. 客户端存储token,在请求提交时,同时发送token信息
  4. 服务器端统一拦截同一个用户的所有请求,验证当前请求是否需要被验证(不是所有请求都验证重复提交)
  5. 验证session中token是否和用户请求中的token一致,如果一致则放行
  6. session清除会话中的token,为下一次的token生成作准备
  7. 并发重复请求到来,验证token和请求token不一致,请求被拒绝

由以上的流程,我们整个实现需要以下几个东西

  1. token生成器,负责生成token
  2. 客户token请求处理action,负责处理客户请求,并返回token信息
  3. token拦截器,用于拦截指定的请求是否需要验证token
  4. token请求拦截标识,用于标识哪些请求是需要被拦截的
  5. 客户端token请求处理方法,用于请求token,并存放于特定操作中,并在提交时发送到请求中

    token生成器
    token生成器在这里使用了一个随机数来实现,即随机生成一个数字,即实现token生成,如下所示:

1
2
3
4
5
6
7
8
9
private static final Random random = new Random(System.currentTimeMillis());
public static final String TOKENPARAM = "session-token";
 
/** 生成一个token */
public static synchronized String generateToken(HttpSession session) {
    String s = String.valueOf(random.nextLong());
    session.setAttribute(TOKENPARAM, s);
    return s;
}

    token请求处理action
    请求处理action,即接收相应的请求,然后直接返回相对应的token即可,如下即为一个为ajax请求生成token的处理action:

1
2
3
4
5
public String generateTokenAjax() {
    String token = SessionTokenGenerator.generateToken(ServletActionContext.getRequest().getSession());
    AjaxSupport.sendSuccessText(token);
    return NONE;
}

    token请求拦截标识
    拦截标识,即表示哪些方法需要被拦截,这里可以使用注解来实现,即在要拦截的方法上追加类似@TokenNeed的注解,或者使用配置文件,将需要拦截的方法列表记录在配置文件中,在本文中,使用了一个配置文件来记录

    token拦截器
    token拦截器实现了我们所需要的拦截处理,在当碰到需要拦截的方法请求中,将同步进行token的判断和处理,并根据处理结果判断是否该继续放行或拦截之:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public String intercept(ActionInvocation invocation) throws  Exception {
    String action = invocation.getProxy().getAction().getClass().getName();
    String method = invocation.getProxy().getMethod();
    final HttpSession session = ServletActionContext.getRequest().getSession();
    if(includeMethodSet.contains(action + "." + method)) {
        synchronized(session) {
            String paramSessionToken = ServletActionContext.getRequest().getParameter(SessionTokenGenerator.TOKENPARAM);
            String sessionSessionToken = (String) session.getAttribute(SessionTokenGenerator.TOKENPARAM);
            if(sessionSessionToken == null || paramSessionToken == null || !paramSessionToken.equals(sessionSessionToken))
                return fail();
            session.removeAttribute(SessionTokenGenerator.TOKENPARAM);
        }
    }
    return invocation.invoke();
}

如上即是判断处理的方法是否在拦截列表中,如果是,则取得参数中的token,再将其与session中的token相比,如果不一致,则直接返回fail,随后将其从session中移除。

    客户端token实现
    作为客户端,只需要在进行请求提交之前申请一个token,在请求时,将此token加到请求中即可。在本文中,有一个jquery的ajax方法来处理token请求,随后在进行ajax请求时将此token一起加入到param。如下即为token的jquery请求

1
2
3
4
5
6
m_ylf.token = function() {
    m_ylf.invoke("/token/generateToken",{}, function(re) {
        re = re["result"];
        window["session-token"] = re;
    });
}

即在处理时将接收到的token放到window中,要提交请求时再将其从window中取出,一并提交即可,如下的统一ajax处理方法:

1
2
3
//追加session-token
if(window["session-token"])
    param["session-token"] = window["session-token"];

至此,整个防session Token请求即完成。如果在客户端模拟多个请求中,首先会有一个请求被成功处理,其它的请求即直接返回类似“不能重复提交”的错误警告(对于ajax请求)。

转载请标明出处:i flym
本文地址:http://www.iflym.com/index.php/code/avoid-conrrent-duplicate-submit-by-use-session-synchronized-and-token.html

javaEE开发中使用session同步和token机制来防止并发重复提交的更多相关文章

  1. 分布式web架构中对session同步的常用处理方法以及优缺点

    写在前面 最近在读一本来自淘宝技术团队大牛的书,名字叫<大型网站系统与Java中间件实践>.开篇的章节详细地介绍了一个网站架构由小变大不断演进的过程,其中从单机架构升级到集群架构的过程中着 ...

  2. apache tomcat搭建负载均衡(实现集群中的session同步)

    原理:tomcat 做个WEB服务器有它的局限性,处理能力低,效率低.承受并发小(1000左右).但目前有不少网站或者页面是JSP的.并采用了tomcat做为WEB,因此只能在此基础上调优. 目前采取 ...

  3. 软件架构设计学习总结(19):详解分布式系统中的session同步问题

    几周前,有个盆友问老王,说现在有多台服务器,怎么样来解决这些服务器间的session同步问题?老王一下就来精神了,因为在n年以前,老王还在学校和几个同学一起所谓创业的时候,也遇到了类似的问题.当时查了 ...

  4. JavaWeb -- Session实例 -- 自动登录 和 防止表单重复提交(令牌产生器) MD5码

    1. 自动登录 http://blog.csdn.net/xj626852095/article/details/16825659 2. 防止表单重复提交 表单Servlet //负责产生表单 pub ...

  5. 关于JavaEE 开发中web.xml的主要配置及其使用

    web.xml 中的listener. filter.servlet 加载顺序及其详解 在项目中总会遇到一些关于加载的优先级问题,近期也同样遇到过类似的,所以自己查找资料总结了下,下面有些是转载其他人 ...

  6. iOS开发中如何实现同步、异步、GET、POST等请求实操演示!

    1.同步请求可以从因特网请求数据,一旦发送同步请求,程序将停止用户交互,直至服务器返回数据完成,才可以进行下一步操作, 2.异步请求不会阻塞主线程,而会建立一个新的线程来操作,用户发出异步请求后,依然 ...

  7. Android 开发中的View事件监听机制

    在开发过程中,我们常常根据实际的需要绘制自己的应用组件,那么定制自己的监听事件,及相应的处理方法是必要的.我们都知道Android中,事件的监听是基于回调机制的,比如常用的OnClick事件,你了解它 ...

  8. 开发中,如何配合后端,保存你的静态html页

    添加备注2015.4.8 最终决定采用相对路径方法, /img/img.jpg这种“绝对”路径写法必须在网站环境中才能识别,不利于静态页面的查看,故不予采用! 所以采用img/img.jpg或../i ...

  9. JavaEE开发使用Maven管理的SpringMVC工程

    前几篇博客已经陆陆续续的聊了一些Spring的东西,今天博客我们就来聊一下SpringMVC.SpringMVC目前在JavaEE开发中可谓占据一席之地,用起来也是比较顺手的.低耦合,高内聚,利用一些 ...

随机推荐

  1. Jquery获取input=text 的值

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  2. Redis hash 类型及操作

    原文:http://blog.sina.com.cn/s/blog_5f044a4d0102v01k.html Redis hash是一个string类型的field和value的映射表.它的添加.删 ...

  3. getHibernateTemplate()

    getHibernateTemplate 前提条件:你的类必须继承HibernateDaoSupport 一: 回调函数: public List getList(){   return (List ...

  4. push控制器 卡顿

    代码: RecommendController *rec = [[RecommendController alloc]init]; [self.navigationController pushVie ...

  5. 硬盘安装Win7、CentOS7双系统

    待补充 0.软件 Acronis Disk Director:用来对硬盘分区,将磁盘的一部分格式化成Linux可以识别的ext3格式 Ext2Fsd:因为Windows不能识别ext3格式的文件系统, ...

  6. Source

    转载自:http://blog.csdn.net/u014084081/article/details/44617707 Guide iOS Developer Library 教程 Ray Wend ...

  7. 集合-字典(Lookup/SortedDictionary)

    Lookup<TKey, TElement>非常类似于Dictionary<TKey, TValue>,但是把键映射在一个值集上. 必须调用ToLookup方法创建Lookup ...

  8. android数据库sqlite增加删改查

    http://hi-beijing.iteye.com/blog/1322040 http://www.cnblogs.com/wenjiang/archive/2013/05/28/3100860. ...

  9. Left/Right/Inner Join用法和区别

    left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录 right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录inner join(等值连接) 只 ...

  10. css3选择器总结--强大如jquery

    最近发现,阿里的笔试考了许多css3的知识,像query media.box-flex等等.主要是移动浏览器的开发,让html5和css3如虎添翼,再也不用担心兼容了.so总结一下css3的选择器: ...