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

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

时间: 2024-06-16 13:46:39

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

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

写在前面 最近在读一本来自淘宝技术团队大牛的书,名字叫<大型网站系统与Java中间件实践>.开篇的章节详细地介绍了一个网站架构由小变大不断演进的过程,其中从单机架构升级到集群架构的过程中着重介绍了关于session同步问题, 这也是很多人在聊到分布式时绕不过去的话题.下面就整理下书中的内容,也算是做个读书笔记,方便以后参考. 问题从哪来 做web开发的同学应该对session再熟悉不过,它是服务器分配给客户端的会话标识,浏览器每次请求会带上这个标识来告诉服务器我是谁,服务器会在内存中存储这些不

web集群环境中的session同步几种方法

在做了web集群后,你肯定会首先考虑session同步问题,因为通过负载均衡后,同一个IP访问同一个页面会被分配到不同的服务器上,如果 session不同步的话,一个登录用户,一会是登录状态,一会又不是登录状态.所以本文就根据这种情况给出三种不同的方法来解决这个问题: 一,利用数据库同步session 1,用一个低端电脑建个数据库专门存放web服务器的session,或者,把这个专门的数据库建在文件服务器上,用户访问web服务器时,会去这个专门的数据库check一下session的情况,以达到s

探讨Web开发中的Session存储与管理

我们都知道,由于HTTP协议是无状态的协议,一次浏览器和服务器的交互过程就是: 浏览器:你好吗? 服务器:很好! 这就是一次会话,对话完成后,这次会话就结束了,服务器端并不能记住这个人,下次再对话时,服务器端并不知道是上一次的这个人,所以服务端需要记录用户的状态时,就需要用某种机制来识别具体的用户,这个机制就是Session: 服务端如何识别特定的客户?这个时候需要使用Cookie: 每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端,用 Cookie 来实现Session跟踪

Session的活化和钝化,表单重复提交,验证码使用流程

钝化 指将HttpSession对象序列化到硬盘中,一般钝化发生在服务器停止,服务器停止时会自动将HttpSession对象序列化到硬盘,这个事我们称为钝化. 活化 将硬盘中HttpSession对象加载进内存中,一般在服务器启动时,会自动将硬盘中HttpSession对象重新加载进内存,这一过程我们成为活化. 钝化指将内存中的对象写到硬盘中, 一个类要想可以序列化到硬盘中必须要实现java.io.Serializable接口 这个类中的所有属性也需要实现java.io.Serializable

session,url重写,防止表单重复提交

Session入门_session原理 1.Session概述 1.在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象), 注意:一个浏览器独占一个session对象(默认情况下). 2.Session 是一个域 1.作用范围:一个浏览器和服务器会话范围 2.生命周期: 创建 当程序第一次调用到request.getSession()方法时说明客户端明确的需要用到session此时创建出对应客户端的Session对象. request.getSession()=req

在一般程序开发中可以使用synchronized同步多线程

假设:有两个boy,都要玩同一个toy,此时就会出现同步情况,必须一个玩好了,另一个才能玩.多线程也是这样子. 如果程序不是高并发的场景,则使用synchronized线程同步足够处理. synchronized关键字解释(synchronized(this)同步代码块也可以被替换为同步对象方法或者同步类方法,本质是一样的): 多个线程,访问同一个对象中的synchronized(this)同步代码块时,每一个线程需要串行得到执行.其他线程必须等待当前线程执行完以后才能执行. 当一个线程对象一个

JavaEE开发中字符编码出现乱码的处理

网上有很多处理字符编码的解决方案,在此,我站着前人的肩膀上作自己的总结. 在我看来,出现乱码问题的解决方法无非就是在3个地方进行编码设置: 1.HTML.JSP等前端页面: 2.后台servlet中request和response对象: 3.服务器配置文件. 1.HTML.JSP等前端页面,出现编码首先看前端某个页面的Meta标签是否正确设置了编码,这个Meta标签也就是页面的头部 <%@ page language="java" import="java.util.*

Django框架开发中避免表单重复提交

Form表单做为web2.0时代的重要角色,也是我们与web网站进行数据交互的重要渠道,但是大家在web网站开发过程中,都会遇到一个问题,那就是如何避免表单重复提交,我们可不确定用户可在提交了一个表单后,是否有足够的耐心等待我们的程序加载完成,如果此时用户不耐烦的在前台重复刷新页面,那么就会造成数据重复提交.信息不准确,因此我们在程序设计时一定要规避这样的问题,接下来介绍一下在Django框架开发中如何避免此问题. 首先说明一下Http协议中两种非常常见的方法GET和POST. 1.GET方法往

Struts2之Session Token机制理解及使用

一.学习案例:关于表单重复提交的问题,其实没什么可讲的,这个功能可以说很鸡肋,特别在现在的项目中很少会用,实际意义不大,之所以讲,是为了引出拦截器,给下一章学习拦截器做个铺垫,大家就当看杂文一样,随便瞟几眼就OK. 二.案例分析:token机制很类似于验证码的实现方式,当页面请求扫描到<s:token/>便签时就会调用标签处理类,而标签处理类会生成一个随机数返回给页面,另外在服务器端拷贝一份也就是防止session中,当请求到达服务器后,就会对比两个字符串,相等则通过,不相等证明是重复提交则跳