• 防止表单重复提交


      在Web开发中表单的重复提交是很严重的问题,重复提交成功会产生垃圾数据消耗不必要的资源,更严重的是如果遇到恶意刷库的情况垃圾数据更是数不胜数。在正常使用过程中产生重复提交的情况也有多重情况:鼠标连击、回退提交、刷新提交、网络延迟用户重复提交等。

      防止重复提交的方法分两大类就是客户端、服务端(这是废话了)。客户端主要是用js对按钮的限制,一次点击后屏蔽按钮或者是直接跳转等待页面,服务端思路为客户端加token进行验证。客户端就不做详细介绍,主要介绍服务端的控制。

    1、客户端存储

      就是在客户端不同的地方存储两个token,在服务端进行校验。在Form表单中存储一个token利用隐藏域,在Cookie中存储一个(也可以都放到form表单中两个不同的隐藏域)。档form表单提交的时候,对这两个token进行验证,相同则允许提交否则阻止提交。

    优点:

      不占用服务器资源

      实施起来简单,易上手

    缺点

      容易伪造(防君子不防小人)

      占用网络资源(或许不是那么明显)

    详细介绍一下客户端分布存储在Form表单中和Cookie中的情况。

    客户端的实现如下:

    package cn.simple.token;
    
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.lang.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    /**
     * 双客户端验证
     * @author ldm
     * @Date 2016年6月16日
     */
    @Service("clientTokenProcesser")
    public class ClientTokenProcesser extends TokenProcesser {
    
        @Autowired
        HttpServletResponse response;
    
        @Override
        public boolean validToken(HttpServletRequest request) {
            String formToken = request.getParameter(getTokenField()).toString();
            System.out.println("formToken:"+formToken);
            if(StringUtils.isEmpty(formToken))
            {
                printException("表单中没有token");
                return false;
            }
            Cookie[] cookies = request.getCookies();
            if(cookies==null)
            {
                printException("cookie 中没有token");
            }
            for (Cookie cookie : cookies) {
                if(cookie.getName().equals(getTokenKey(request)))
                {
                    String cookieValue = cookie.getValue();
                    System.out.println("cookieToken:"+cookieValue);
                    if(cookieValue.equals(formToken))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
    
        private void printException(String msg) {
            Exception e= new RuntimeException(msg);
            e.printStackTrace();
        }
    
        @Override
        public String getTokenKey(HttpServletRequest request) {
            String cookieKey = getTokenField() + "_cookie";
            return cookieKey;
        }
    
        @Override
        public void saveToken(HttpServletRequest request) {
            String token = MakeToken.getInstance().getToken();
            request.setAttribute(getTokenField(), token);
            if (response == null) {
                throw new RuntimeException("HttpServletResponse is null");
            }
            Cookie cookie = new Cookie(getTokenKey(request), token);
            response.addCookie(cookie);
        }
    
        @Override
        public String getClientToken(HttpServletRequest request) {
            Object token = request.getParameter(getTokenField());
            if (token == null) {
                return null;
            } else {
                return token.toString();
            }
    
        }
    
    }
    View Code

    2、双向存储

      客户端和服务端的token各自独立存储,客户端存储在Cookie或者Form的隐藏域(放在Form隐藏域中的时候,需要每个表单)中,服务端存储在Session(单机系统中可以使用)或者其他缓存系统(分布式系统可以使用)中。

    优点:

      安全性高(几乎是无法伪造的)

      网络资源相对于前者有所减少

    缺点:

      整个系统实施起来较第一种方法的时候复杂度增加

    详细介绍一下服务端存储在session中客户端存储在Cookie中

    SessionTokenProcesser实现如下:

    package cn.simple.token;
    
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import org.apache.commons.lang.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    /**
     * 服务端用Session存储
     * 
     * @author ldm
     * @Date 2016年6月16日
     */
    @Service("sessionTokenProcesser")
    public class SessionTokenProcesser extends TokenProcesser {
    
        @Autowired
        HttpServletResponse response;
    
        @Override
        public boolean validToken(HttpServletRequest request) {
    
            String clientToken = getClientToken(request);
            if (StringUtils.isEmpty(clientToken)) {
                return false;
            }
            HttpSession session = request.getSession(false);
            if (session == null) {
                return false;
            }
            String tokenKey = getTokenKey(request);
            Object tokenObj = session.getAttribute(tokenKey);
            if(tokenObj==null)
            {
                rethrow("服务端不存在当前token,请重新请求表单");
            }
            String serverToken = tokenObj.toString();
    
            session.removeAttribute(tokenKey);
            System.out.println("remove server token:" + serverToken);
            return clientToken.equals(serverToken);
    
        }
    
        @Override
        public String getTokenKey(HttpServletRequest request) {
            return getTokenField();
        }
    
        @Override
        public void saveToken(HttpServletRequest request) {
    
            HttpSession session = request.getSession();
            String tokenKey = getTokenKey(request);
            Object tokenObj = session.getAttribute(tokenKey);
            String token;
            if (tokenObj == null) {
                token = MakeToken.getInstance().getToken();
                // 服务端保存token
                session.setAttribute(tokenKey, token);
            } else {
                token = tokenObj.toString();
            }
            System.out.println("current token:" + token);
            // 写入cookie
            Cookie cookie = new Cookie(getTokenField(), token);
            response.addCookie(cookie);
        }
    
        private void rethrow(String message) {
            RuntimeException e = new RuntimeException(message);
            throw e;
        }
    
        @Override
        public String getClientToken(HttpServletRequest request) {
            Cookie[] cookies = request.getCookies();
            if (cookies == null) {
                rethrow("没有读取到客户端的cookie");
                return null;
            }
            
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(getTokenKey(request))) {
                    String cookieValue = cookie.getValue();
                    return cookieValue;
                }
            }
            rethrow("客户端cookie中没有存储token");
            return null;
        }
    
    }
    View Code

    整个示例源码:

    https://github.com/monkeyming/AvoidDuplicateSubmission

    参考:

    http://blog.csdn.net/h183288132/article/details/50184199

    http://www.cnblogs.com/monkeyming/p/5599061.html

  • 相关阅读:
    JZOJ 3845. 简单题(simple)
    JZOJ 3844. 统计损失(count)
    JZOJ 3843. 寻找羔羊(agnus)
    JZOJ 3833. 平坦的折线
    JZOJ 1956. 矩形
    JZOJ 3832. 在哪里建酿酒厂
    mysql 语法一 :case when详解
    阿里云推荐码
    redis配置文件详解(转)
    压力测试工具 webbench总结
  • 原文地址:https://www.cnblogs.com/hpuCode/p/5531939.html
Copyright © 2020-2023  润新知