原文:https://www.cnblogs.com/manliu/articles/5983888.html

1.这里采用的方法是:使用get请求进入表单页面时,后台会生成一个tokrn_flag分别放到session和request中,表单页面用一个隐藏域储存该token_flag,在提交表单时,将该token_flag一并提交到后台,后台将该token_flag和session中对比,只要比对通过就立即删除session中的token_flag,这样就能保证表单最多只有一次成功提交的机会。

2.表单防重复提交一般前后端都会做,前端比较简单,点击过一次就将提交按钮置灰或disabled。

3.因为生成和验证token_flag具有通用性,一般不建议嵌入到具体方法中,最好的方法就是使用aop+注解的方式

4.注解

/**
* 表单注解,放在需要验证表单的方法上,一般是controller上
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FormToken {

}

5.AOP

@Aspect
@Component
public class ResubmitAspect {
    private static final String PARAM_TOKEN = "token";
    private static final String PARAM_TOKEN_FLAG = "TokenFlag_";

    /**
     * 执行切面拦截逻辑
     */
    @Around("@annotation(formToken)")
    public void execute(ProceedingJoinPoint joinPoint, FormToken formToken) throws Throwable {
       if (formToken != null) {
            //方法入参列表
            Object[] args = joinPoint.getArgs();
            //类名
            String className = joinPoint.getTarget().getClass().getName();
            for (Object arg : args) {
                //方法入参是否包含request
                if (arg != null && arg instanceof HttpServletRequest) {
                    HttpServletRequest request = (HttpServletRequest) arg;
                    HttpSession session = request.getSession(true);
                    if ("GET".equalsIgnoreCase(request.getMethod())) {
                        /* GET 生成 token */
                        this.generate(joinPoint, request, session, PARAM_TOKEN_FLAG + className);
                    } else {
                        /* POST 验证 token */
                        this.validation(joinPoint, request, session, PARAM_TOKEN_FLAG + className);
                    }
                }
            }
        }
    }
    /**
     * <p>
     * 生成表单 token
     * </p>
     */
    public void generate(ProceedingJoinPoint joinPoint, HttpServletRequest request, HttpSession session,
            String tokenFlag) throws Throwable {
        String uuid = UUID.randomUUID().toString();
        session.setAttribute(tokenFlag, uuid);
        request.setAttribute(PARAM_TOKEN, uuid);
        joinPoint.proceed();
    } 
    /**
     * <p>
     * 验证表单 token
     * </p>
     * <p>
     * 验证结果一致,既为第一次提交,删除会话中存储的token,并继续执行方法。<br>
     * 否则不做任何处理。
     * </p>
     */
    public void validation(ProceedingJoinPoint joinPoint, HttpServletRequest request, HttpSession session,
            String tokenFlag) throws Throwable {
        Object sessionFlag = session.getAttribute(tokenFlag);
        Object requestFlag = request.getParameter(PARAM_TOKEN);
        if (sessionFlag != null && sessionFlag.equals(requestFlag)) {
            //删除已验证的token
            session.removeAttribute(tokenFlag);
            joinPoint.proceed();
        }
    }
}

6.配置

在spring配置文件中

<aop:aspectj-autoproxy />
<context:component-scan base-package="com.baomidou.framework.aop">
</context:component-scan>

7.html中使用

<input type="hidden" name="token" value="${token}" />