资讯专栏INFORMATION COLUMN

表单防重提交详解

Kross / 749人阅读

摘要:注意设计模式并不适用所有的重复提交情况,比如由于服务器响应缓慢,用户刷新提交请求造成的重复提交。用户恶意避开客户端预防多次提交手段,进行重复数据提交。

表单重复提交的常见应用场景 
1、在网络延迟的情况下让用户又是加你点击多次submit按钮导致 
2、表单提交后用户点击刷新按钮导致表单重复提交 
3、用户表单提交后,点击浏览器后退按钮退回表单页面后进行再次提交

很多情况下,重复提交的数据,都不是我们想要的,如订单的提交,申请退款的提交等,那么如何做到防重提交呢?

下面介绍有4种方法,重点最后一种:

1.给数据库所需的字段添加上唯一性约束

    此方法最有效的防止了数据重复提交,但是前台还是会出现重复提交的情况,后台回报错.

2.前端使用js在点击按钮提交后设置disable,后或者js设置一个属性,提交前为true,提交后为false
    客户端禁用js,这种方法将无效

3.使用Post/Redirect/Get  

Post/Redirect/Get简称PRG,是一种可以防止表单数据重复提交的一种Web设计模式,像用户刷新提交响应页面等比较典型的重复提交表单数据的问题可以使用PRG模式来避免。例如:当用户提交成功之后,执行客户端重定向,跳转到提交成功页面。

   注意:PRG设计模式并不适用所有的重复提交情况,比如:

       1)由于服务器响应缓慢,用户刷新提交POST请求造成的重复提交。

       2)用户点击后退按钮,返回到数据提交界面,导致的数据重复提交。

       3)用户多次点击提交按钮,导致的数据重复提交。

       4)用户恶意避开客户端预防多次提交手段,进行重复数据提交。

4.使用session和注解设置令牌

所谓令牌其实就是一种标识,标识当前提交状态,比如以前古代打战时,皇帝发布命令时会给个虎符给手下的人携带到将军那边传达,而将军手上也有皇帝给的另一半虎符,将军一对比,果然能凑成一对,就根据传达的命令去打战,如果来人没有携带虎符,将军是肯定把来人砍头的.

那么这个令牌怎么设置,下图就是写这个令牌token的一个思路:

1.先写一个简单的注解类


import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**

自定义一个Token注解,用于标识需要防重提交的方法

@author ranger

*
*/
@Target(value=ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TokenForm {

//用于标记需要防重提方法的 ,创建Token的属性
boolean create() default false;
//用于标记需要防重提方法的,删除Token的属性
boolean remove() default false;

}


2.在跳转到增加页面前,创建token

/**
 * 跳转到增加页面
 * 请求路径:${pageContext.request.contextPath}/admin/toAdminAdd
 * @return
 */

@RequestMapping(value="/toAdminAdd")
@TokenForm(create=true)
public String toAdminAdd(HttpServletRequest request) {

    return "manager/adminAdd";
}


3.添加数据后删除token
/**

 * 增加管理员
 * 请求路径:${pageContext.request.contextPath }/admin/addAdmin
 * @param admin
 * @param request
 * @return
 */
@RequestMapping(value="/addAdmin")
@TokenForm(remove=true)
public String addAdmin(@RequestParam Map admin,HttpServletRequest request) {
    try {
        //将密码Md5编码后在插入
        admin.put("admin_pwd",Md5Utils.md5((String)admin.get("admin_pwd")) );
        
        LOGGER.debug("-增加管理员-"+admin);
        adminService.addAdmin(admin);
        request.setAttribute("admin_add_msg", "增加管理员成功");
        
    } catch (Exception e) {
        e.printStackTrace();
        request.setAttribute("admin_add_msg", "增加管理员失败");
    }
    return "manager/adminAdd";
}

4.创建一个拦截器,拦截发送路径前的token信息
import java.util.UUID;

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

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import cn.gzsxt.annotation.TokenForm;

public class TokenInterceptor implements HandlerInterceptor {

private static final Logger LOGGER = LogManager.getLogger(TokenInterceptor.class);

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
    
    //第一步:获得调用处理方法的注解
    HandlerMethod hm=(HandlerMethod) handler;
    TokenForm tokenForm = hm.getMethodAnnotation(TokenForm.class);
    
    
    //第二步:判断是否有Token注解
    if (tokenForm!=null) {
        HttpSession session = request.getSession();
        if (tokenForm.create()==true) {
            session.setAttribute("token", UUID.randomUUID().toString());
            LOGGER.debug("打印出来的token:"+session.getAttribute("token"));
        }
        if (tokenForm.remove()==true) {
            //判断表单的Token与服务端的Token是否相同
            String formToken = request.getParameter("token");
            Object sessionToken = session.getAttribute("token");
            //传递过来的Token与服务端的Token相同,允许操作,并且删除session的Token
            if (formToken.equals(sessionToken)){
                session.removeAttribute("token");
            }else{
                //跳转到指定的路径
                String invoke = request.getParameter("token.invoke");
                response.sendRedirect(request.getContextPath()+invoke);
                return false;
            }
        }
        
    }
    
    return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
        ModelAndView modelAndView) throws Exception {
    // TODO Auto-generated method stub
    
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
        throws Exception {
    // TODO Auto-generated method stub
    
}


5.前端指定提交的token和重复提交后可跳转的地址token.invoke

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/54875.html

相关文章

  • 表单防重提交详解

    摘要:注意设计模式并不适用所有的重复提交情况,比如由于服务器响应缓慢,用户刷新提交请求造成的重复提交。用户恶意避开客户端预防多次提交手段,进行重复数据提交。 表单重复提交的常见应用场景 1、在网络延迟的情况下让用户又是加你点击多次submit按钮导致 2、表单提交后用户点击刷新按钮导致表单重复提交 3、用户表单提交后,点击浏览器后退按钮退回表单页面后进行再次提交 很多情况下,重复提交的数据,...

    Backache 评论0 收藏0
  • 连连支付注意事项

    摘要:异步通知和同步通知异步通知和同步通知都是支付完成的时候即时发送的。请务必要做好订单成功状态防重控制。同步返回是表单的形式请求,用来页面显示成功信息。 风控参数风控参数是我司风险控制系统用来控制用户盗卡风险,保障用户资金安全的参数,也是商户上线之前一个重要的审核事项,一定要传的哟~ 如果是: 1、小额虚拟类商户,只要传风控参数列表中的基本风控参数; 2、实物类的商户,要传文档中基本风控参...

    Aceyclee 评论0 收藏0
  • API设计中防重放攻击

    摘要:数据加密是否可以防止重放攻击否,加密可以有效防止明文数据被监听,但是却防止不了重放攻击。防重放机制我们在设计接口的时候,最怕一个接口被用户截取用于重放攻击。这样,这个请求即使被截取了,你也只能在内进行重放攻击。 HTTPS数据加密是否可以防止重放攻击? 否,加密可以有效防止明文数据被监听,但是却防止不了重放攻击。 防重放机制 我们在设计接口的时候,最怕一个接口被用户截取用于重放攻击。重...

    vvpvvp 评论0 收藏0
  • 表单提交时编码类型enctype详解

    摘要:以下引用,摘自规范的章节这不就是我们在回调函数里判断返回数据的类型,并且是在请求头中的那个玩意儿吗没错就是它根据规范的基础数据类型的说明,这个指定了连接资源的属性,同时也是的那些媒体类型。今天掰扯完了表单提交时的编码类型,以及它和的关系。 很早以前,当还没有前端这个概念的时候,我在写表单提交完全不去理会表单数据的编码,在action属性里写好目标URL,剩下的啊交给浏览器吧~但是现在,...

    jackzou 评论0 收藏0
  • JS基础篇--JS之表单提交时编码类型enctype详解

    摘要:格式支持比键值对复杂得多的结构化数据,这一点也很有用。例如下面这段代码最终发送的请求是这种方案,可以方便的提交复杂的结构化数据,特别适合的接口。 简介 form的enctype属性为编码方式,常用有两种:application/x-www-form-urlencoded和multipart/form-data,默认为application/x-www-form-urlencoded。 ...

    ad6623 评论0 收藏0

发表评论

0条评论

Kross

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<