`
huangpengpeng
  • 浏览: 32435 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

利用token 防止表单重复提交

    博客分类:
  • Xsrf
阅读更多
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);
	}
}

 

<!--@分隔 -->
	<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/
5
0
分享到:
评论
3 楼 iwindyforest 2014-07-14  
huangpengpeng 写道
iwindyforest 写道
XsrfTokenRequestFilter 类里面使用了四个成员变量, 会不会引起同步问题呢?


4个成员变量都是读取引用的操作,不存在任何修改的操作,他们写入的操作都是正对客户端的cookie进行的,每个客户端的cookie对象都是不同的,所以不会有同步问题

了解了, 不错 
2 楼 huangpengpeng 2014-07-14  
iwindyforest 写道
XsrfTokenRequestFilter 类里面使用了四个成员变量, 会不会引起同步问题呢?


4个成员变量都是读取引用的操作,不存在任何修改的操作,他们写入的操作都是正对客户端的cookie进行的,每个客户端的cookie对象都是不同的,所以不会有同步问题
1 楼 iwindyforest 2014-07-14  
XsrfTokenRequestFilter 类里面使用了四个成员变量, 会不会引起同步问题呢?

相关推荐

    struts2利用token防止表单重复提交(源代码)

    struts2防止表单重复提交,利用struts的拦截器tokenSession,轻轻松松解决表单重复提交的问题。 附件为源代码,后台延迟了3秒,可直接在web服务器下部署运行,输入用户名和密码后,多点几次提交按钮,然后看控制台...

    利用Token机制解决重复重复提交

    Token机制是一种常用的防止表单重复提交的技术方案。其基本思想是在用户提交表单时,服务器会生成一个唯一的Token并将其存储在客户端(如通过Cookie或隐藏字段),同时也会在服务器端存储一份。当用户提交表单时,...

    javascript方式防止表单重复提交

    在Web开发中,表单重复提交是一个常见的问题,可能导致数据的冗余或错误。JavaScript作为客户端脚本语言,可以通过多种策略来防止用户意外或恶意地多次提交表单。以下是一些关键的知识点: 1. **禁用提交按钮**:最...

    JavaEE Struts2利用tokenSession防止重复提交

    当用户提交表单时,服务器会检查session中的token与表单提交的token是否匹配,如果匹配并且是首次提交,那么执行业务逻辑,否则视为重复提交并拒绝处理。 具体实现步骤如下: 1. **配置拦截器**:在struts.xml配置...

    防止表单重复提交

    本篇文章将深入探讨如何防止表单重复提交,主要关注于基于Struts2框架的解决方案。 首先,理解表单重复提交的原因。用户在点击提交按钮后,如果网络延迟或用户误操作导致页面刷新或再次点击提交,就可能发生重复...

    Struts之Token解决表单那重复提交

    表单重复提交可能发生在用户点击提交按钮后,由于网络延迟或刷新页面等原因,导致请求被多次发送到服务器。如果不加以控制,可能会导致同一份数据被处理多次,从而对数据库造成不必要的影响。 二、Token机制的原理 ...

    struts+token机制解决表单重复提交

    Struts是Java Web开发中的一款流行MVC框架,它简化了构建基于JSP的Web应用程序的过程。然而,Web应用在处理表单提交...通过对这些代码的学习和研究,开发者能够熟练地在自己的项目中应用Struts+Token防止表单重复提交。

    thinkphp3.2 防止表单重复提交

    在Web开发中,防止表单重复提交是一个重要的问题,它能确保用户操作的唯一性和数据的一致性。ThinkPHP 3.2 是一个基于MVC模式的PHP开发框架,提供了丰富的功能来帮助开发者构建安全、高效的应用。在这个场景中,我们...

    ASP.NET中防止刷新页面造成表单重复提交

    ### ASP.NET中防止刷新页面...通过上述方法,可以在不影响用户体验的前提下有效地防止表单重复提交,从而提高系统的稳定性和安全性。这种方法适用于大多数Web应用场合,特别是那些对数据准确性和一致性要求较高的场景。

    token-springMVC 防止重复提交

    5. **扩展技术**:除了Token机制,还可以结合其他方法,如`HttpSessionOncePerRequestFilter`过滤器,或者利用数据库的唯一约束来防止重复提交。在分布式环境中,可以考虑使用分布式锁或者分布式令牌桶算法。 6. **...

    用STRUTS的TOKEN机制解决表单重复提交,转载自:百度文库

    Struts的Token机制是Web应用中防止表单重复提交的一种常用方法。在处理表单提交时,如果用户意外地多次点击了提交按钮,可能会导致数据的重复录入,从而引起不必要的问题,例如订单重复、数据库数据异常等。为了解决...

    struts2防止表单重复提交--重定向

    总结起来,Struts2通过重定向策略可以有效地防止表单重复提交,结合其他如Token Session Strategy等方法,可以进一步提高应用的安全性和稳定性。开发者可以根据项目需求选择合适的防重提交方案,确保系统正常运行。

    asp.net 页面防止重复提交

    一种防止重复提交的方法是使用“请求令牌”(Request Token)。在页面加载时,服务器生成一个唯一的令牌,并将其存储在隐藏表单字段或Cookie中。在用户提交表单时,服务器会检查令牌是否有效且未使用过。如果令牌已...

    利用struts的token控制重复提交

    Struts的Token机制主要用来防止用户意外或恶意的多次提交表单,例如在刷新页面时重复提交数据。它的工作原理如下: 1. **生成Token**: 当用户访问一个需要防重复提交的表单时,服务器会生成一个唯一的Token并将其...

    防止页面重复提交demo

    4. **清理token**:处理完请求后,服务器应该清理或标记token为已使用,防止恶意用户利用已知的token进行重复提交。 结合以上方法,"防止页面重复提交demo"旨在提供一个完整的解决方案,既考虑了前端用户体验,又...

    struts2中token限制表单多次提交

    Token机制是通过在客户端(浏览器)和服务器之间传递一个唯一的标识符(Token)来防止表单的重复提交。当用户首次提交表单时,服务器生成一个Token并将其存储在服务器端,同时将其作为隐藏字段返回给客户端。如果...

    jsp项目放置表单重复提交

    防止表单重复提交的方法通常有两种:客户端解决方案和服务器端解决方案。 1. 客户端解决方案: - JavaScript禁用提交按钮:在提交表单时,可以通过JavaScript立即禁用提交按钮,避免用户连续点击。 - 使用Ajax...

    解决在struts 中可以通过token 来重复提交的问题

    通过以上三个步骤,我们成功地实现了在Struts中通过Token来防止表单重复提交的功能。这种方法不仅提高了应用的安全性,还提升了用户体验。在实际项目开发过程中,还需要根据具体需求进一步完善和优化这一机制。

Global site tag (gtag.js) - Google Analytics