防止表單重複提交

  在Web開發中表單的重複提交是很嚴重的問題,重複提交成功會產生垃圾數據消耗沒必要要的資源,更嚴重的是若是遇到惡意刷庫的狀況垃圾數據更是數不勝數。在正常使用過程當中產生重複提交的狀況也有多重狀況:鼠標連擊、回退提交、刷新提交、網絡延遲用戶重複提交等。html

  防止重複提交的方法分兩大類就是客戶端、服務端(這是廢話了)。客戶端主要是用js對按鈕的限制,一次點擊後屏蔽按鈕或者是直接跳轉等待頁面,服務端思路爲客戶端加token進行驗證。客戶端就不作詳細介紹,主要介紹服務端的控制。java

一、客戶端存儲

  就是在客戶端不一樣的地方存儲兩個token,在服務端進行校驗。在Form表單中存儲一個token利用隱藏域,在Cookie中存儲一個(也能夠都放到form表單中兩個不一樣的隱藏域)。檔form表單提交的時候,對這兩個token進行驗證,相同則容許提交不然阻止提交。git

優勢:github

  不佔用服務器資源spring

  實施起來簡單,易上手apache

缺點緩存

  容易僞造(防君子不防小人)安全

  佔用網絡資源(或許不是那麼明顯)服務器

詳細介紹一下客戶端分佈存儲在Form表單中和Cookie中的狀況。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

 

二、雙向存儲

  客戶端和服務端的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

相關文章
相關標籤/搜索