• 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

  • 相关阅读:
    sql sever读取写入Excel总结
    sql sever跨数据库复制数据的方法
    SQLServer2005重建索引前后对比【转】
    SQL自定义函数
    【C】——回调函数实现泛型算法
    【C】——压缩字符串
    【C】——指针与const限定符
    loop within fields
    分类与分步在行测数量关系中的应用
    隔年增长率
  • 原文地址:https://www.cnblogs.com/Vae1990Silence/p/4630383.html
Copyright © 2020-2023  润新知