@Time:2019年1月4日 16:19:19
@Author:QGuo
 
背景:最开始打算写个防止表单重复提交的拦截器;网上见到一种不错的方式,比较合适前后端分离,校验在后台实现;

我在此基础上,将key,value。Objects.hashCode()了下
因为request的body 可能太大,过长;
但不保证存在不同的object生成的哈希值却相同,但是我们目的只是为了防止重复提交而已,不同对象生成哈希值相同的机率很小。
 
==========================代码==============================

1、HttpServletRequestReplacedFilter 过滤器.

目的:post请求时,复制request;注意代码中的注释部分;
package com.kdgz.service;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException; /**
* @author QGuo
* @date 2019/1/3 15:04
*/
public class HttpServletRequestReplacedFilter implements Filter {
@Override
public void destroy() {} @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String contentType = request.getContentType(); if (contentType != null && contentType.contains("application/x-www-form-urlencoded")) {
//如果是application/x-www-form-urlencoded, 参数值在request body中以 a=1&b=2&c=3...形式存在,
//若直接构造BodyReaderHttpServletRequestWrapper,在将流读取并存到copy字节数组里之后,
//httpRequest.getParameterMap()将返回空值!
//若运行一下 httpRequest.getParameterMap(), body中的流将为空! 所以两者是互斥的!
request.getParameterMap();
}
if ("POST".equals(httpServletRequest.getMethod().toUpperCase())) {
requestWrapper = new BodyHttpServletRequestWrapper((HttpServletRequest) request);
}
} if (requestWrapper == null) {
chain.doFilter(request, response);
} else {
chain.doFilter((HttpServletRequest)requestWrapper, response);
}
} @Override
public void init(FilterConfig arg0) throws ServletException {}
}

HttpServletRequestReplacedFilter

 
2、HttpServletRequestWrapper --复制ServletRequest
目的在于:使servletRequest可以重复获取inputStream、reader;
 package com.kdgz.service;

 import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.Map; /**
* @author QGuo
* @date 2019/1/3 15:05
*/
public class BodyHttpServletRequestWrapper extends HttpServletRequestWrapper { private byte[] body; public byte[] getBody() { return body; } public void setBody(byte[] body) { this.body = body; } public BodyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = this.getBodyString(request).getBytes(Charset.forName("UTF-8"));
} @Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream(),"UTF-8"));
} @Override
public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(this.body); return new ServletInputStream() {
@Override
public boolean isFinished() { return false; } @Override
public boolean isReady() { return false; } @Override
public void setReadListener(ReadListener readListener) {} @Override
public int read() throws IOException { return bais.read(); }
};
} @Override
public String getHeader(String name) { return super.getHeader(name); } @Override
public Enumeration<String> getHeaderNames() { return super.getHeaderNames(); } @Override
public Enumeration<String> getHeaders(String name) { return super.getHeaders(name); } @Override
public Map<String, String[]> getParameterMap() { return super.getParameterMap(); } public String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}

HttpServletRequestWrapper

3、web.xml 中添加过滤器

   <filter>
<filter-name>httpServletRequestFilter</filter-name>
<filter-class>com.kdgz.service.HttpServletRequestReplacedFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>httpServletRequestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

4、添加自定义注解

 package com.kdgz.annotation;

 import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @author QGuo
* @date 2018/12/24 13:58
* 一个用户 相同url 同时提交 相同数据 验证
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SameUrlData {
}

@SameUrlData

5、添加拦截器

 package com.kdgz.service;

 import com.alibaba.fastjson.JSON;
import com.kdgz.annotation.SameUrlData;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.annotation.Resource;
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;
import java.util.Objects;
import java.util.concurrent.TimeUnit; /**
* 一个用户 相同url 同时提交 相同数据 验证
* 主要通过 session中保存到的url 和 请求参数。如果和上次相同,则是重复提交表单
*
* @author QGuo
* @date 2018/12/24 14:02
*/
public class SameUrlDataInterceptor extends HandlerInterceptorAdapter {
@Resource
StringRedisTemplate stringRedisTemplate; @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
SameUrlData annotation = method.getAnnotation(SameUrlData.class);
if (annotation != null) {
if (repeatDataValidator(request)) {//如果重复相同数据
//在此可添加response响应内容,提醒用户重复提交了
return false;
} else
return true;
}
return true;
} else {
return super.preHandle(request, response, handler);
}
} /**
* 验证同一个url数据是否相同提交 ,相同返回true
*
* @param httpServletRequest
* @return
*/
public boolean repeatDataValidator(HttpServletRequest httpServletRequest) throws IOException {
Map<String, String[]> parameterMap = new HashMap(httpServletRequest.getParameterMap());
//删除参数中的v;(v参数为随机生成的字符串,目的是为了每次访问都是最新值,既然要防止重复提交,需要剔除此参数)
if (parameterMap.containsKey("v"))
parameterMap.remove("v");
//每一位登录者都有唯一一个token认证
String tokens = "";
if (parameterMap.get("token").length > 0)
tokens = parameterMap.get("token")[0];
String method = httpServletRequest.getMethod().toUpperCase();//请求类型,GET、POST
String params;
if (StringUtils.equals(method, "POST")) {//post请求时
BodyHttpServletRequestWrapper requestWrapper = new BodyHttpServletRequestWrapper((HttpServletRequest) httpServletRequest);
byte[] bytes = requestWrapper.getBody();
if (bytes.length != 0) {
params = JSON.toJSONString(new String(bytes, "UTF-8").trim());
} else {//若body被清空,则说明参数全部被填充到Parameter集合中了
/**
* 当满足一下条件时,就会被填充到parameter集合中
* 1:是一个http/https请求
* 2:请求方法是post
* 3:请求类型(content-Type)是application/x-www-form-urlencoded
* 4: Servlet调用了getParameter系列方法
*/
Map<String, String[]> map = new HashMap(requestWrapper.getParameterMap());
// 去除 v 参数
if (map.containsKey("v"))
map.remove("v");
params = JSON.toJSONString(map);
}
} else {
params = JSON.toJSONString(parameterMap);
} String url = String.valueOf(Objects.hashCode(httpServletRequest.getRequestURI() + tokens));
Map<String, String> map = new HashMap<String, String>();
map.put(url, params);
//防止参数过多,string过大;现将储存为 hash编码;
String nowUrlParams = String.valueOf(Objects.hashCode(map));
String preUrlParams = stringRedisTemplate.opsForValue().get(url);
if (preUrlParams == null) {//如果上一个数据为null,表示还没有访问页面
//设置过期时间为3分钟
stringRedisTemplate.opsForValue().set(url, nowUrlParams, 3, TimeUnit.MINUTES);
return false;
} else if (preUrlParams.equals(nowUrlParams)) {//否则,已经访问过页面
//如果上次url+数据和本次url+数据相同,则表示重复添加数据
return true;
} else {//如果上次 url+数据 和本次url加数据不同,则不是重复提交,更新
stringRedisTemplate.opsForValue().set(url, nowUrlParams, 3, TimeUnit.MINUTES);
return false;
}
}
}

SameUrlDataInterceptor

使用的时候,只要在接口上,添加注解即可
例如:
@RequestMapping(value = "v1.0/monGraphSave")
@SameUrlData
public AjaxMessage monGraphSave(@RequestBody MonGraphFB monGraphFB){}
====================代码结束===================
 
整理至此,主要有以下注意点;
①、得考虑post请求参数获取的特殊性
②、request.getInputStream() 只能获取一次,要想可以多次读取,得继承HttpServletRequestWrapper,读出来--放回去
③、过滤器的目的是可以直接读取request里面的body
④、request参数body可能很大,可以取hash值。
⑤、key、value的存储,需要设置过期时间;
 
心得:
其实我觉得防止表单重复提交这个功能,作用不是特别大;因为只要随便加一个参数,就可以把需要的参数重复添加进系统中;
只能做到,防止用户误操作,点击了多次这种情况;(一般前端也会做处理的,但万一前端抽风自动发起了多次请求呢);
只能说一定程度上 更加完善吧
 
改进:
可以在SameUrlDataInterceptor拦截器中,添加response响应内容,让用户知道自己重复提交了。
很简单不举例了;

由防止表单重复提交引发的一系列问题--servletRequest的复制、body值的获取的更多相关文章

  1. Spring Boot (一) 校验表单重复提交

    一.前言 在某些情况下,由于网速慢,用户操作有误(连续点击两下提交按钮),页面卡顿等原因,可能会出现表单数据重复提交造成数据库保存多条重复数据. 存在如上问题可以交给前端解决,判断多长时间内不能再次点 ...

  2. 12、Struts2表单重复提交

    什么是表单重复提交 表单的重复提交: 若刷新表单页面, 再提交表单不算重复提交. 在不刷新表单页面的前提下: 多次点击提交按钮 已经提交成功, 按 "回退" 之后, 再点击 &qu ...

  3. java防止表单重复提交

    用session防止表单重复提交 思路:在服务器端生成一个唯一的随机标识串Token,同时在当前用户的Session域中保存这个Token.然后将Token发送到客户端的Form表单中,在Form表单 ...

  4. java web学习总结(十三) -------------------使用Session防止表单重复提交

    在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交. 一.表单重复提 ...

  5. 使用Struts 2防止表单重复提交

    用户重复提交表单在某些场合将会造成非常严重的后果.例如,在使用信用卡进行在线支付的时候,如果服务器的响应速度太慢,用户有可能会多次点击提交按钮,而这可能导致那张信用卡上的金额被消费了多次.因此,重复提 ...

  6. js阻止form表单重复提交

    防止表单重复提交的方法总体来说有两种,一种是在js中阻止重复提交:另一种是在后台利用token令牌实现,大致思路是生成一个随机码放到session和form表单的隐藏输入框中,提交表单时两者对比,表单 ...

  7. PHP简单利用token防止表单重复提交

    <?php /* * PHP简单利用token防止表单重复提交 * 此处理方法纯粹是为了给初学者参考 */ session_start(); function set_token() { $_S ...

  8. token防止表单重复提交

    出现表单重复提交的三种情况: 一.服务器响应缓慢,用户多次点击提交按钮. 二.提交成功后刷新页面. 三.提交成功后返回表单页面再次点击提交. package com.jalja.token; impo ...

  9. JavaWeb防止表单重复提交(转载)

    转载自:http://blog.csdn.net/ye1992/article/details/42873219 在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用 ...

随机推荐

  1. 转js resplace方法使用

    作者: hezhiwu5#163.com    时间:2007-3-22 大家好!!今晚在华软G43*宿舍没什么事做,把javascript中replace方法讲解一下,如果讲得不对或不合理是情理之中 ...

  2. 【原创】关于not in的一些事情

    早上到公司,收到一条cocall消息,是某哥们遇到的疑惑,可能很多新手并不知情: 请教个问题 我执行 . select * from t_htgl_htpswj t where t.c_wjmc = ...

  3. 手工备份恢复oracle数据库

     手工备份恢复oracle数据库: 虽然已经有了rman工具 但是手工恢复oracle能够让你对oracle数据库有更加深入的了解 数据库一致性开机条件: 数据文件 scn,控制文件 scn,redo ...

  4. CorelDRAW X6低价再次冲破底线

    平时我们看到的标志设计.杂志排版.产品商标.插图描画......这些都是设计师们使用CorelDRAW设计而来.如今CorelDRAW已经成为每个设计师必装的软件,从12年发布CorelDRAW X6 ...

  5. nodejs 封装mysql连接池

    写在前面的 在nodejs后台代码中,我们总是会和数据库打交道 然而,每次都要写数据库的配置以及连接和断开,不胜其烦 我就封装了一个连接池模块,不足之处还请多多批评 上代码 一下是写在mysqls.j ...

  6. Converting Legacy Chrome IPC To Mojo

    Converting Legacy Chrome IPC To Mojo Looking for Mojo Documentation? Contents Overview Deciding What ...

  7. 洛谷P4894 GodFly求解法向量

    如果没有学过向量相关知识请出门右转高中数学必修四~~~ 当然如果你和我一样也是小学生我也不反对 首先说结论:\(\vec{z}=(y1z2-y2z1,z1x2-z2x1,x1y2-x2y1)\) 其实 ...

  8. WPF 获取应用的所有窗口

    原文:WPF 获取应用的所有窗口 本文告诉大家如何获取应用内的所有窗口,无论这些窗口有没显示 在 WPF 可以通过 Application.Current.Windows 列举应用的所有窗口 fore ...

  9. 题解 洛谷 P3376 【【模板】网络最大流】

    本人很弱,只会Dinic.EK与Ford-Fulkerson...(正在学习ISAP...) 这里讲Dinic... Dinic:与Ford-Fulkerson和的思路相似(话说好像最大流求解都差不多 ...

  10. [terry笔记]11gR2_DataGuard搭建_primary零停机

    11gR2搭建dataguard环境,此篇文章是利用rman搭建dataguard,这样的好处是primary不用停机,当然,前提条件是primary已经开启归档. 相对于可以停机,零停机传送数据文件 ...