zoukankan      html  css  js  c++  java
  • 防止表单重复提交

      在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

  • 相关阅读:
    在TreeView控件节点中显示图片
    PAT 甲级 1146 Topological Order (25 分)
    PAT 甲级 1146 Topological Order (25 分)
    PAT 甲级 1145 Hashing
    PAT 甲级 1145 Hashing
    PAT 甲级 1144 The Missing Number (20 分)
    PAT 甲级 1144 The Missing Number (20 分)
    PAT 甲级 1151 LCA in a Binary Tree (30 分)
    PAT 甲级 1151 LCA in a Binary Tree (30 分)
    PAT 甲级 1149 Dangerous Goods Packaging
  • 原文地址:https://www.cnblogs.com/hpuCode/p/5531939.html
Copyright © 2011-2022 走看看