表单重复提交的常见应用场景
一、在网络延迟的状况下让用户又是加你点击屡次submit按钮致使
二、表单提交后用户点击刷新按钮致使表单重复提交
三、用户表单提交后,点击浏览器后退按钮退回表单页面后进行再次提交前端
不少状况下,重复提交的数据,都不是咱们想要的,如订单的提交,申请退款的提交等,那么如何作到防重提交呢?java
下面介绍有4种方法,重点最后一种:web
1.给数据库所需的字段添加上惟一性约束spring
此方法最有效的防止了数据重复提交,可是前台仍是会出现重复提交的状况,后台回报错.数据库
2.前端使用js在点击按钮提交后设置disable,后或者js设置一个属性,提交前为true,提交后为false
客户端禁用js,这种方法将无效apache
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;
/**
*
*/
@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<String, Object> 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
<input type="hidden" name="token" value="${sessionScope.token }"<input type="hidden" name="token.invoke" value="/admin/toAdminAdd">