重复提交,这是一直以来都会存在的问题,当在网站某个接口调用缓慢的时候就会有可能引起表单重复提交的问题,不论form提交,还是ajax提交都会有这样的问题,最近在某社交app上看到这么一幕,这个团队没有做重复提交的验证,从而导致了数据有很多的重复提交,在这里我们不讨论谁对谁错,问题解决即可。

首先的一种方式,在前端加入loading,或者是blockUI,在ios以及安卓上也是类似,效果如下:

这个时候整个页面不能再用鼠标点击,只能等待请求响应以后才能操作

具体可以参考blockUI这个插件

此外就是后端了,其实后端在一定程度上也要进行防止重复提交的验证,某些无所谓的情况下可以在前端加,某些重要的场景下比如订单等业务就必须再前后端都要做,为了测试方便,blockUI就直接注释

在后台我们线程sleep5秒

@RequestMapping("/CSRFDemo/save")
@ResponseBody
public LeeJSONResult saveCSRF(Feedback feedback) { log.info("保存用户反馈成功, 入参为 Feedback.title: {}", feedback.getTitle()); try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} return LeeJSONResult.ok();
}

多次点击,效果如下

步骤1:页面生成token,每次进入都需要重新生成

设置自定义标签

package com.javasxy.web.tag;

import java.io.IOException;
import java.io.StringWriter;
import java.util.UUID; import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport; import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils; import com.javasxy.components.JedisClient;
import com.javasxy.pojo.ActiveUser;
import com.javasxy.web.utils.SpringUtils; /**
*
* @Title: TokenTag.java
* @Package com.javasxy.web.tag
* @Description: 生成页面token
* Copyright: Copyright (c) 2016
* Company:DINGLI.SCIENCE.AND.TECHNOLOGY
*
* @author leechenxiang
* @date 2017年4月11日 下午3:29:13
* @version V1.0
*/
public class TokenTag extends SimpleTagSupport { private String id; private String name; private static final String CACHE_MAKE_CSRF_TOKEN_ = "cache_make_csrf_token_"; StringWriter sw = new StringWriter(); private JedisClient jedis = SpringUtils.getContext().getBean(JedisClient.class); public void doTag() throws JspException, IOException { // 生成token
String token = UUID.randomUUID().toString(); // 构建token隐藏框
String tokenHtml = "<input type='hidden' ";
if (StringUtils.isNotEmpty(id)) {
tokenHtml += " id='" + id + "' ";
}
if (StringUtils.isNotEmpty(name)) {
tokenHtml += " name='" + name + "' ";
}
tokenHtml += " value='" + token + "' ";
tokenHtml += " />"; // 获取当前登录用户信息
ActiveUser activeUser = (ActiveUser)SecurityUtils.getSubject().getPrincipal();
// 设置token到redis(如果是单应用项目设置到session中即可)
jedis.set(CACHE_MAKE_CSRF_TOKEN_ + ":" + activeUser.getUsername(), token);
jedis.expire(CACHE_MAKE_CSRF_TOKEN_ + ":" + activeUser.getUsername(), 1800); JspWriter out = getJspContext().getOut();
out.println(tokenHtml);
} public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} }

页面生成

查看redis

拦截器代码:

package com.javasxy.web.controller.interceptor;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import com.javasxy.common.utils.JsonUtils;
import com.javasxy.common.utils.LeeJSONResult;
import com.javasxy.common.utils.NetworkUtil;
import com.javasxy.components.JedisClient;
import com.javasxy.pojo.ActiveUser;
import com.javasxy.web.controller.filter.ShiroFilterUtils;
import com.javasxy.web.utils.SpringUtils; public class CSRFTokenInterceptor extends HandlerInterceptorAdapter { final static Logger log = LoggerFactory.getLogger(CSRFTokenInterceptor.class); private static final String CACHE_MAKE_CSRF_TOKEN_ = "cache_make_csrf_token_"; private JedisClient jedis = SpringUtils.getContext().getBean(JedisClient.class); @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取从页面过来的token
String pageCSRFToken = request.getHeader("pageCSRFToken"); // 获取IP
String ip = NetworkUtil.getIpAddress(request);
if (StringUtils.isEmpty(pageCSRFToken)) {
String msg = "禁止访问";
log.error("ip: {}, errorMessage: {}", ip, msg);
returnErrorResponse(response, LeeJSONResult.errorTokenMsg(msg));
return false;
} // 当前登录用户
ActiveUser activeUser = (ActiveUser)SecurityUtils.getSubject().getPrincipal(); String CSRFToken = jedis.get(CACHE_MAKE_CSRF_TOKEN_ + ":" + activeUser.getUsername());
if (StringUtils.isEmpty(CSRFToken)) {
String msg = "操作频繁,请稍后再试";
log.error("ip: {}, errorMessage: {}", ip, msg);
returnErrorResponse(response, LeeJSONResult.errorTokenMsg(msg));
return false;
} if (!pageCSRFToken.equals(CSRFToken)) {
String msg = "禁止访问";
log.error("ip: {}, errorMessage: {}", ip, msg);
returnErrorResponse(response, LeeJSONResult.errorTokenMsg(msg));
return false;
} // 清除token
jedis.del(CACHE_MAKE_CSRF_TOKEN_ + ":" + activeUser.getUsername());
return true;
} public void returnErrorResponse(HttpServletResponse response, LeeJSONResult result) throws IOException, UnsupportedEncodingException {
OutputStream out=null;
try{
response.setCharacterEncoding("utf-8");
response.setContentType("text/json");
out = response.getOutputStream();
out.write(JsonUtils.objectToJson(result).getBytes("utf-8"));
out.flush();
} finally{
if(out!=null){
out.close();
}
}
} /*public boolean returnError(HttpServletRequest request, HttpServletResponse response, String errorMsg) throws IOException, UnsupportedEncodingException {
if (ShiroFilterUtils.isAjax(request)) {
returnErrorResponse(response, LeeJSONResult.errorTokenMsg(errorMsg));
return false;
} else {
// TODO 跳转页面
return false;
}
}*/ }

测试:

这样重复提交的问题就解决了,同时也解决了CSRF攻击的问题,关于什么是CSRF可以自行百度

注意:

1、token生成也可以在异步调用的时候生成,也就是一次请求一个token,而不是一个页面一个token,但是这样做可能会被第三方获取

2、这里使用了springmvc的拦截器,当然在shiro中也可以自定义过滤器来实现,代码略

防CSRF攻击:一场由重复提交的问题引发的前端后端测试口水战的更多相关文章

  1. .NET MVC中的防CSRF攻击

    一.CSRF是什么? CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSR ...

  2. asp.netcore mvc 防CSRF攻击,原理介绍+代码演示+详细讲解

    一.CSRF介绍 1.CSRF是什么? CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session ridin ...

  3. 记录一下自己在MVC项目中如何防CSRF攻击,直接上代码

    1.前端的处理: 2.后台 1.)添加过滤器,哪里用放哪里 2.)需要验证的方法上直接添加过滤器即可 大功告成 以下为过滤器代码块 /// <summary>/// ajax中加上Anti ...

  4. egg 提交数据 防csrf 攻击 配置

    await ctx.render('from',{csrf:this.ctx.csrf}); 或者 使用中间件 ctx.state.csrf = ctx.csrf;

  5. 19、Flask实战第19天:CSRF攻击与防御

    CSRF攻击原理 网站是通过cookie来实现登录功能的.而cookie只要存在浏览器中,那么浏览器在访问这个cookie的服务器的时候,就会自动的携带cookie信息到服务器上去.那么这时候就存在一 ...

  6. 转-CSRF——攻击与防御

    0x01 什么是CSRF攻击 CSRF是Cross Site Request Forgery的缩写(也缩写为XSRF),直译过来就是跨站请求伪造的意思,也就是在用户会话下对某个CGI做一些GET/PO ...

  7. JS前端无侵入实现防止重复提交请求技术

    JS前端无侵入实现防止重复提交请求技术 最近在代码发布测试的过程中,我发现有些请求非常的消耗服务器资源,而系统测试人员因为响应太慢而不停的点击请求.我是很看不惯系统存在不顺眼的问题,做事喜欢精益求精, ...

  8. Spring Boot 如何防止重复提交?

    Java技术栈 www.javastack.cn 优秀的Java技术公众号 在传统的web项目中,防止重复提交,通常做法是:后端生成一个唯一的提交令牌(uuid),并存储在服务端.页面提交请求携带这个 ...

  9. 防止跨站请求伪造(CSRF)攻击 和 防重复提交 的方法的实现

    CSRF的概念可以参考:http://netsecurity.51cto.com/art/200812/102951.htm 本文介绍的是基于spring拦截器的Spring MVC实现 首先配置拦截 ...

随机推荐

  1. 有向图的强连通算法 -- tarjan算法

    (绘图什么真辛苦) 强连通分量: 在有向图 G 中.若两个顶点相互可达,则称两个顶点强连通(strongly connected). 假设有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有 ...

  2. Mingw编译DLib

    Mingw编译DLib 因为机器上安装了qt-opensource-windows-x86-mingw530-5.8.0,所以准备使用其自带的mingw530来编译DLib使用. 因为DLib使用CM ...

  3. Oracle Tuxedo的配置文件配置详解

    # (c) 2003 BEA Systems, Inc. All Rights Reserved. #ident "@(#) samples/atmi/simpapp/ubbsimple $ ...

  4. CocoaAsyncSocket UDP发送数据超过包大小限制(Message too long)

    最近在做iOS上,基于UDP传输音视频时遇到的一个问题,这边纪录一下: 由于考虑实时性比较高,所以采用了 CocoaAsyncSocket 的UDP框架来实现,将视频切割成一帧帧的图片发给服务端,不过 ...

  5. Python函数的静态变量

    C语言中,在函数内部可以定义static类型的变量,这个变量是属于这个函数的全局对象.在Python中也可以实现这样的机制. def f(): if not hasattr(f, 'x'): f.x ...

  6. spring加载jar包中多个配置文件(转)

    转自:http://evan0625.iteye.com/blog/1598366 在使用spring加载jar包中的配置文件时,不支持通配符,需要一个一个引入,如下所示: Java代码 <co ...

  7. 基于 CoreText 实现高性能 UITableView

    引起UITableView卡顿比较常见的原因有cell的层级过多.cell中有触发离屏渲染的代码(譬如:cornerRadius.maskToBounds 同时使用).像素是否对齐.是否使用UITab ...

  8. linux top命令查看内存及多核CPU的使用讲述【转】

    转载一下top使用后详细的参数,之前做的笔记找不见了,转载一下,作为以后的使用参考: 原文地址:http://blog.csdn.net/linghao00/article/details/80592 ...

  9. mysql5.7忘记密码时,修改root密码

    (1).由于MySQL5.7在安装完后,第一次启动时,会在root目录下生产一个随机的密码,文件名为 .mysql_secret 所以,登录时需要用随机密码登录,然后通过以下命令修改密码 “SET P ...

  10. magento登陆

    magento判断用户登录 Magento 登陆之后返回登录之前的页面 magento 在登陆后一般会自动跳转到 My Account 页面 但是经常会有需求 就是登陆自动跳转到 之前的页面里面 工具 ...