防止重复提交

2016/01/01 Java

有时候前端用户可能在上一次提交结果还未返回时又点击造成重复提交,这里后端增加了过滤来防止重复提交。基本思想是每次提交都应该附带一个唯一的 token,这个 token 只能使用一次,使用第二次时直接异常。

拦截器

package com.albeert.token;

import com.alibaba.fastjson.JSONObject;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * The type Token interceptor.
 */
public class TokenInterceptor extends HandlerInterceptorAdapter {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		if (handler instanceof HandlerMethod) {
			HandlerMethod handlerMethod = (HandlerMethod) handler;
			Method method = handlerMethod.getMethod();
			Token annotation = method.getAnnotation(Token.class);
			if (annotation != null) {
				if (isRepeatSubmit(request, response, annotation)) {
					return false;
				}
			}
			return true;
		} else {
			return super.preHandle(request, response, handler);
		}
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
		if (handler instanceof HandlerMethod) {
			HandlerMethod handlerMethod = (HandlerMethod) handler;
			Method method = handlerMethod.getMethod();
			Token annotation = method.getAnnotation(Token.class);
			if (annotation != null) {
				String tokenName = request.getParameter("REQ_TOKEN_NAME");
				String clinetToken = request.getParameter(tokenName);
				request.getSession().setAttribute(tokenName, clinetToken);
			}
		} else {
			super.preHandle(request, response, handler);
		}
	}

	/**
	 * 判断是否重复提交。
	 *
	 * @param request
	 *            请求对象
	 * @param response
	 *            相应对象
	 * @return
	 * @throws Exception
	 *             异常
	 */
	private boolean isRepeatSubmit(HttpServletRequest request, HttpServletResponse response, Token annotation)
			throws Exception {
		String tokenName = request.getParameter("REQ_TOKEN_NAME");
		String serverToken = (String) request.getSession(false).getAttribute(tokenName);
		if (serverToken == null) {
			dealTokenError(request, response, annotation);
			return true;
		}

		String clinetToken = request.getParameter(tokenName);
		if (clinetToken == null) {
			dealTokenError(request, response, annotation);
			return true;
		}

		if (!serverToken.equals(clinetToken)) {
			dealTokenError(request, response, annotation);
			return true;
		}
		request.getSession().removeAttribute(tokenName);
		return false;
	}

	private void dealTokenError(HttpServletRequest request, HttpServletResponse response, Token annotation)
			throws IOException, ServletException {
		if (!annotation.isTip()) {
			return;
		}
		if (!annotation.isAjax()) {
//			request.setAttribute("code", "token_error");
//			request.setAttribute("msg", "不能重复提交。");
//			request.getRequestDispatcher("/basedemo/baseDemo/list").forward(request, response);
		} else {
			response.setHeader("Cache-Control", "no-cache");
			response.setCharacterEncoding("UTF-8");
			response.setContentType("text/json;charset=utf-8");
			Map<String, String> result = new HashMap<>();
			result.put("code", "token_error");
			result.put("msg", "不能重复提交。");
			response.getWriter().write(JSONObject.toJSONString(result));
		}
	}
}

拦截器配置

    <!-- 拦截器配置 -->
    <mvc:interceptors>
        <!-- 配置Token拦截器,防止用户重复提交数据 -->
        <bean class="com.albert.TokenInterceptor"/>
    </mvc:interceptors>

freemarker 标签

package com.albert.token;

import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;
import java.util.UUID;

public class InitToken implements TemplateDirectiveModel {

	@Autowired
	private HttpSession session;

	private static final String REQ_TOKEN_SUFFIX = "REQ_TOKEN_";

	@Override
	public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
			throws TemplateException, IOException {
		Writer out = env.getOut();
		String name = REQ_TOKEN_SUFFIX + genRandomCode("abcdefghijklmnopqrstuvwxyz0123456789", 6);
		String value = UUID.randomUUID().toString();
		session.setAttribute(name, value);
		String tokenHtml = "<input type='hidden' name='REQ_TOKEN_NAME' value='" + name + "' />";
		tokenHtml += "<input type='hidden' name='" + name + "' value='" + value + "' />";
		out.write(tokenHtml);
		body.render(out);
	}

	public static String genRandomCode(String src, Integer length) {
		char[] chars = src.toCharArray();
		StringBuilder builder = new StringBuilder();

		for (int i = 0; i < length.intValue(); ++i) {
			double randomValue = Math.random();
			int randomIndex = (int) Math.round(randomValue * (double) (chars.length - 1));
			char characterToShow = chars[randomIndex];
			builder.append(characterToShow);
		}

		return builder.toString();
	}
}

注入 freemarker

    <!-- freemarker的配置 -->
    <bean id="freemarkerConfig"
          class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
        <property name="templateLoaderPath" value="/WEB-INF/view/" />
        <property name="defaultEncoding" value="utf8" />
        <property name="freemarkerSettings">
            <props>
                <prop key="template_update_delay">10</prop>
                <prop key="locale">zh_CN</prop>
                <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
                <prop key="date_format">yyyy-MM-dd</prop>
                <prop key="number_format">#.##</prop>
                <prop key="object_wrapper">freemarker.ext.beans.BeansWrapper</prop>
                <prop key = "template_exception_handler">ignore</prop>
            </props>
        </property>
        <property name="freemarkerVariables">
            <map>
                <entry key="initToken" value-ref="initToken"/>
            </map>
        </property>
    </bean>

Search

    Table of Contents