1. 程式人生 > >session防止重複提交

session防止重複提交

http://blog.chinaunix.net/uid-26284395-id-3044216.html
針對這個博文進行了優化,將servlet改寫為攔截器,其它action需要防止重複提交時只需在TokenInterceptor中的if下新增一個actionString;在頁面中新增EL表示式就ok了
struts2 token不僅可以防止重複提交還可以防禦CSRF,不過是在不存在XSS漏洞的情況下;使用struts2 token防止CSRF攻擊(http://www.ibm.com/developerworks/cn/web/1102_niugang_csrf/),要保證token不會洩露,一般的做法是在記憶體中儲存一個唯一的token,有提交操作的表單頁面是使用CGI,PHP,JSP等生成的,保證只有你的頁面才能通過記憶體取到這個token,這樣跨站的網頁是無法偽造的。
最安全的防止CSRF就是圖片驗證碼了


1)TokenInterceptor.java
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import com.cmcc.db.session.TokenProcessor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy
; public class TokenInterceptor extends org.apache.struts2.interceptor.TokenInterceptor { private static final long serialVersionUID = 1L; @Override protected String doIntercept(ActionInvocation invocation) throws Exception { int count = 0; HttpServletRequest req = (HttpServletRequest) invocation .getInvocationContext
().get(ServletActionContext.HTTP_REQUEST); HttpServletResponse resp = (HttpServletResponse) invocation .getInvocationContext().get(ServletActionContext.HTTP_RESPONSE); resp.setContentType("text/html;charset=GBK"); ActionProxy proxy = invocation.getProxy(); String actionString = proxy.getNamespace() + "/" + proxy.getActionName() + "!" + proxy.getMethod() + ".action"; if (actionString.startsWith("/user/user!add.action")) { TokenProcessor processor = TokenProcessor.getInstance(); if (processor.isTokenValid(req)) { try { Thread.sleep(5000); } catch (InterruptedException e) { System.out.println(e); } System.out.println("submit : " + count); if (count % 2 == 1) count = 0; else count++; System.out.println("success"); return invocation.invoke(); } else { processor.saveToken(req); System.out.println("你已經提交了表單,同一表單不能提交兩次。"); return null; } } else { return invocation.invoke(); } } }

2)TokenProcessor.java
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class TokenProcessor {

static final String TOKEN_KEY = "org.sunxin.token";

private static TokenProcessor instance = new TokenProcessor();

public static TokenProcessor getInstance()

{

    return instance;

}

/**
 * 
 * 最近一次生成令牌值的時間戳。
 */

private long previous;

/**
 * 
 * 判斷請求引數中的令牌值是否有效。
 */
public synchronized boolean isTokenValid(HttpServletRequest request) {
    // 得到請求的當前Session物件。
    HttpSession session = request.getSession(false);
    if (session == null) {
        return false;
    }
    // 從Session中取出儲存的令牌值。
    String saved = (String) session.getAttribute(TOKEN_KEY);
    if (saved == null) {
        return false;
    }
    // 清除Session中的令牌值。
    resetToken(request);

    // 得到請求引數中的令牌值。

    String token = request.getParameter(TOKEN_KEY);
    if (token == null) {
        return false;
    }
    return saved.equals(token);
}
/**
 * 清除Session中的令牌值。
 */
public synchronized void resetToken(HttpServletRequest request)
{
    HttpSession session = request.getSession(false);
    if (session == null) {
        return;
    }
    session.removeAttribute(TOKEN_KEY);
}

/**
 * 產生一個新的令牌值,儲存到Session中, 如果當前Session不存在,則建立一個新的Session。
 */
public synchronized void saveToken(HttpServletRequest request) {

    HttpSession session = request.getSession();
    String token = generateToken(request);
    if (token != null) {
        session.setAttribute(TOKEN_KEY, token);
    }
}

/**
 * 根據使用者會話ID和當前的系統時間生成一個唯一的令牌。
 */
public synchronized String generateToken(HttpServletRequest request) {

    HttpSession session = request.getSession();
    try {
        byte id[] = session.getId().getBytes();
        long current = System.currentTimeMillis();
        if (current == previous) {
            current++;
        }
        previous = current;
        byte now[] = new Long(current).toString().getBytes();
        MessageDigest md = MessageDigest.getInstance("MD5");
        md.update(id);
        md.update(now);
        return toHex(md.digest());
    } catch (NoSuchAlgorithmException e) {
        return null;
    }
}

/**
 * 將一個位元組陣列轉換為一個十六進位制數字的字串。
 */
private String toHex(byte buffer[]) {
    StringBuffer sb = new StringBuffer(buffer.length * 2);
    for (int i = 0; i < buffer.length; i++) {
        sb.append(Character.forDigit((buffer[i] & 0xf0) >> 4, 16));
        sb.append(Character.forDigit(buffer[i] & 0x0f, 16));
    }
    return sb.toString();
}

/**
 * 從Session中得到令牌值,如果Session中沒有儲存令牌值,則生成一個新的令牌值。
 */
public synchronized String getToken(HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    if (null == session)
        return null;
    String token = (String) session.getAttribute(TOKEN_KEY);
    if (null == token) {
        token = generateToken(request);
        if (token != null) {
            session.setAttribute(TOKEN_KEY, token);
            return token;
        } else
            return null;
    } else
        return token;
}

}
3)其它程式碼
①需要防止重複提交的頁面

<%@ page import="com.cmcc.db.session.TokenProcessor" language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
**<%
   //獲取令牌類例項
    TokenProcessor processor = TokenProcessor.getInstance();    //獲取令牌值
    String token = processor.getToken(request);
%>**

<head>
<title>新增使用者資訊介面</title>
</head>
<body>
<form method="post" action="user!add.action" name="form">

</form>
<form action="user!add.action" method="post">
         <%--使用隱藏域儲存生成的token--%>
         <%--
             <input type="hidden" name="token" value="<%=session.getAttribute("token") %>">
         --%>
         <%--使用EL表示式取出儲存在session中的token--%>

        <table>
            <tr>
                <td>使用者名稱:<input type="text" name="userName" id="userName"></td>
            </tr>
            <tr>
                <td>年齡:<input type="text" name="userAge" id="userAge"></td>
            </tr>
            <tr>
                <td>地址:<input type="text" name="userAddress" id="userAddress"></td>
            </tr>

        <tr>
        <td>**<input type="hidden" name="org.sunxin.token" value="<%=token%>"/>**
        <!-- 作為hidden提交,request的token -->
        <input type="submit" value="提交" style="background-color: pink">
        <input type="reset" value="重置" style="background-color: red"></td>
            </tr>
        </table>
    </form>
</body>
</html>

②struts.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
    <constant name="struts.convention.default.parent.package"
        value="cmcc-default" />
    <constant name="struts.convention.package.locators" value="web" />
    <constant name="struts.convention.package.locators.basePackage"
        value="com.cmcc.db.web" />
    <constant name="struts.convention.result.path" value="/WEB-INF/jsp/"/>
    <constant name="struts.i18n.encoding" value="utf-8" />
    <constant name="struts.enable.DynamicMethodInvocation" value="true" />
    <package name="cmcc-default" extends="convention-default">
        <interceptors>
            <interceptor name="loginInter" class="com.cmcc.db.base.LoginInterceptor" />
            <interceptor name="token" class="com.cmcc.db.base.TokenInterceptor" /> 
            <interceptor-stack name="webStack">
                <interceptor-ref name="store">
                    <param name="operationMode">AUTOMATIC</param>
                </interceptor-ref>
                <interceptor-ref name="paramsPrepareParamsStack" />
                <!-- <interceptor-ref name="loginInter" /> -->
                 <interceptor-ref name="token"/> 
                <interceptor-ref name="defaultStack"></interceptor-ref>
            </interceptor-stack>
        </interceptors>

        <default-interceptor-ref name="webStack" />

        <global-exception-mappings>
            <exception-mapping exception="java.lang.Exception"
                result="error" />
        </global-exception-mappings>
    </package>

    <!-- 使用Convention外掛,實現約定大於配置的零配置檔案風格. 特殊的Result路徑在Action類中使用@Result設定. -->
</struts>