天天看點

利用token 防止表單重複送出

package com.yoro.core.web;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

/**
 * 防止表單重複送出 FORM TOKEN 過濾器 作用:1.防止使用者後退操作,2.防止使用者重複重新整理操作
 * <li>
 * 	利用TOKEN 防止重複送出需要結合特定場景,不要随意亂用
 * 	1.有寫業務是允許使用者插入重複資料的,沒必要使用
 *  2.有些業務是有狀态控制的 重複送出有引發業務及驗證 也沒必要使用
 *  3.如類是于淘寶下單業務是不允許通過一個form 重複送出下單的 則可以利用token實作
 * <li>
 * 使用注意:
 * <li>
 * 1.如果結合springmvc 等其他mvc架構 使用 注意 addUrl 路徑 不能 和  validUrl 路徑 相同 
 * 隻通過  get | post 送出方式區分調用的 action method  否則 點選後退安全不會讀取浏覽器緩存
 * </li>
 * @author zoro
 */
public class XsrfTokenRequestFilter implements Filter {

	/**
	 * addUrls ,validUrls , processUrls 三個數組長度必須相等一一對應
	 */
	private String[] addUrls; // 需要增加 TOKEN 的頁面
	private String[] validUrls;// 需要驗證TOEKN 的頁面
	private String[] processUrls; // TOKEN 驗證失敗處理的頁面
	FilterConfig filterConfig = null;

	public void init(FilterConfig filterConfig) throws ServletException {
		// 傳入的參數分割符号
		String splitChar = filterConfig.getInitParameter("splitChar");
		if (StringUtils.isNotBlank(filterConfig.getInitParameter("addUrls"))) {
			addUrls = filterConfig.getInitParameter("addUrls").split(splitChar);
		}
		if (StringUtils.isNotBlank(filterConfig.getInitParameter("validUrls"))) {
			validUrls = filterConfig.getInitParameter("validUrls").split(
					splitChar);
		}
		if (StringUtils
				.isNotBlank(filterConfig.getInitParameter("processUrls"))) {
			processUrls = filterConfig.getInitParameter("processUrls").split(
					splitChar);
		}
		this.filterConfig = filterConfig;
	}

	public void destroy() {
		this.filterConfig = null;
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		// 擷取請求路徑
		String path = getRelativePath((HttpServletRequest) request);
		// 如果路徑存在增加的urls清單裡面,則向用戶端增加一個token 參數
		if (ArrayUtils.contains(addUrls, path)) {
			xsrfTokenGenerator.save((HttpServletRequest) request,
					(HttpServletResponse) response);
		}
		// 如果路徑存在驗證urls清單裡面 這擷取用戶端 token 參數做驗證
		if (ArrayUtils.contains(validUrls, path)) {
			if (!xsrfTokenGenerator.validate((HttpServletRequest) request,
					(HttpServletResponse) response)) {
				// 驗證沒有通過則跳轉到錯誤處理頁面
				((HttpServletResponse) response)
						.sendRedirect(processUrls[ArrayUtils.indexOf(validUrls,
								path)]);
				return;
			}
		}
		// 到了這裡說明驗證已經通過,則将用戶端 token 删除 下次送出用戶端cookie token 為空 重複送出則失敗
		if (ArrayUtils.contains(validUrls, path)) {
			xsrfTokenGenerator.remove((HttpServletRequest) request,
					(HttpServletResponse) response);
		}
		chain.doFilter(request, response);
	}

	protected String getRelativePath(HttpServletRequest request) {
		if (request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null) {
			String result = (String) request
					.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
			if (result == null) {
				result = (String) request
						.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
			} else {
				result = (String) request
						.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH)
						+ result;
			}
			if ((result == null) || (result.equals(""))) {
				result = "/";
			}
			return (result);
		}
		// No, extract the desired path directly from the request
		String result = request.getPathInfo();
		if (result == null) {
			result = request.getServletPath();
		} else {
			result = request.getServletPath() + result;
		}
		if ((result == null) || (result.equals(""))) {
			result = "/";
		}
		return (result);

	}
	// 執行個體化 token 生成器
	private XsrfTokenGenerator xsrfTokenGenerator = new SimpleXsrfTokenGenerator();
}
           
package com.yoro.core.web;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface XsrfTokenGenerator {

	
	/**
	 * 增加 TOKEN 
	 */
	public String save(HttpServletRequest request, HttpServletResponse response);
	
	/**
	 * 删除TOKEN
	 */
	public void remove(HttpServletRequest request, HttpServletResponse response);
	
	/**
	 * 驗證TOKEN 是否有效 驗證成功應當手動調用 remove 方法 手動從COOKIE中清除TOKEN
	 */
	public boolean validate(HttpServletRequest request, HttpServletResponse response);
}
           
package com.yoro.core.web;

import java.util.UUID;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import org.apache.commons.lang.StringUtils;

import com.common.web.CookieUtils;

/**
 * 
 * 基于cookie 的 利用TOOKEN 防止表單重複送出的實作
 * 
 * @author zoro
 * 
 */
public class SimpleXsrfTokenGenerator implements XsrfTokenGenerator {

	/**
	 * 預設TOOKEN 名稱
	 */
	private final static String FORM_TOEKN_NAME = "_form_token";

	@Override
	public boolean validate(HttpServletRequest request,
			HttpServletResponse response) {
		String paramString = request.getParameter(FORM_TOEKN_NAME);
		if (StringUtils.isBlank(paramString)) {
			return false;
		}
		String value = get(request);
		if (StringUtils.isBlank(value)) {
			return false;
		}
		return value.equals(paramString);
	}

	@Override
	public String save(HttpServletRequest request, HttpServletResponse response) {
		String value = StringUtils.remove(UUID.randomUUID().toString(), "-");
		if (value != null) {
			set(request, response, value);
		}
		return value;
	}

	@Override
	public void remove(HttpServletRequest request, HttpServletResponse response) {
		String value = get(request);
		if (value != null) {
			cancle(request, response);
		}
	}

	/**
	 * 從cookie中擷取TOKEN
	 * 
	 * @param request
	 * @return
	 */
	private String get(HttpServletRequest request) {
		Cookie cookie = CookieUtils.getCookie(request, FORM_TOEKN_NAME);
		return cookie == null ? null : cookie.getValue();
	}

	/**
	 * 設定 TOKEN 到 COOKIE 當中
	 * 
	 * @param request
	 * @param response
	 * @param value
	 */
	private void set(HttpServletRequest request, HttpServletResponse response,
			String value) {
		if (value != null) {
			CookieUtils.addCookie(request, response, FORM_TOEKN_NAME, value,
					-1, null);
		}
		if (value != null) {
			request.setAttribute(FORM_TOEKN_NAME, value);
		}
	}

	/**
	 * 從COOKIE中清楚TOKEN
	 * 
	 * @param request
	 * @param response
	 */
	private void cancle(HttpServletRequest request, HttpServletResponse response) {
		CookieUtils.cancleCookie(request, response, FORM_TOEKN_NAME, null);
	}
}
           
package com.common.web;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.util.Assert;

/**
 * Cookie 輔助類
 * 
 * @author hp
 * 
 */
public class CookieUtils {
	/**
	 * 每頁條數cookie名稱
	 */
	public static final String COOKIE_PAGE_SIZE = "_cookie_page_size";
	/**
	 * 預設每頁條數
	 */
	public static final int DEFAULT_SIZE = 20;
	/**
	 * 最大每頁條數
	 */
	public static final int MAX_SIZE = 200;

	/**
	 * 獲得cookie的每頁條數
	 * 
	 * 使用_cookie_page_size作為cookie name
	 * 
	 * @param request
	 *            HttpServletRequest
	 * @return default:20 max:200
	 */
	public static int getPageSize(HttpServletRequest request) {
		Assert.notNull(request);
		Cookie cookie = getCookie(request, COOKIE_PAGE_SIZE);
		int count = 0;
		if (cookie != null) {
			if (NumberUtils.isDigits(cookie.getValue())) {
				count = Integer.parseInt(cookie.getValue());
			}
		}
		if (count <= 0) {
			count = DEFAULT_SIZE;
		} else if (count > MAX_SIZE) {
			count = MAX_SIZE;
		}
		return count;
	}

	/**
	 * 獲得cookie
	 * 
	 * @param request
	 *            HttpServletRequest
	 * @param name
	 *            cookie name
	 * @return if exist return cookie, else return null.
	 */
	public static Cookie getCookie(HttpServletRequest request, String name) {
		Assert.notNull(request);
		Cookie[] cookies = request.getCookies();
		if (cookies != null && cookies.length > 0) {
			for (Cookie c : cookies) {
				if (c.getName().equals(name)) {
					return c;
				}
			}
		}
		return null;
	}

	/**
	 * 根據部署路徑,将cookie儲存在根目錄。
	 * 
	 * @param request
	 * @param response
	 * @param name
	 * @param value
	 * @param expiry
	 * @param domain
	 * @return
	 */
	public static Cookie addCookie(HttpServletRequest request,
			HttpServletResponse response, String name, String value,
			Integer expiry, String domain) {
		Cookie cookie = new Cookie(name, value);
		if (expiry != null) {
			cookie.setMaxAge(expiry);
		}
		if (StringUtils.isNotBlank(domain)) {
			cookie.setDomain(domain);
		}
		String ctx = request.getContextPath();
		cookie.setPath(StringUtils.isBlank(ctx) ? "/" : ctx);
		response.addCookie(cookie);
		return cookie;
	}

	/**
	 * 取消cookie
	 * 
	 * @param request
	 * @param response
	 * @param name
	 * @param domain
	 */
	public static void cancleCookie(HttpServletRequest request,
			HttpServletResponse response, String name, String domain) {
		Cookie cookie = new Cookie(name, "");
		cookie.setMaxAge(0);
		String ctx = request.getContextPath();
		cookie.setPath(StringUtils.isBlank(ctx) ? "/" : ctx);
		if (StringUtils.isNotBlank(domain)) {
			cookie.setDomain(domain);
		}
		response.addCookie(cookie);
	}
}
           
<[email protected]分隔 -->
	<filter>
		<filter-name>xsrfTokenRequestFilter</filter-name>
		<filter-class>com.yoro.core.web.XsrfTokenRequestFilter</filter-class>
		<init-param>
			<param-name>splitChar</param-name>
			<param-value>@</param-value>
		</init-param>
		<init-param>
			<param-name>addUrls</param-name>
			<param-value>/member/forgot_password.html;/login.html</param-value>
		</init-param>
		<init-param>
			<param-name>validUrls</param-name>
			<param-value>/member/forgot_password.jhtml;/login.jhtml</param-value>
		</init-param>
		<init-param>
			<param-name>processUrls</param-name>
			<param-value>/member/forgot_password.html;/login.html</param-value>
		</init-param>
	</filter>

	<filter-mapping>
		<filter-name>xsrfTokenRequestFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>      

不介意的可以進入我的店鋪看下增加點人氣

http://qc5z.taobao.com/