@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. Linux安装(虚拟机)

    ** 虚拟机安装CentOS系统 以下步骤会连续给出截图,大家自行校对即可. 首先打开虚拟机,出现的界面如上一篇文章截图所示. ** 配置虚拟机 步骤: 1.点击“创建新的虚拟机”     2.选择“ ...

  2. Python的Flask框架入门-Ubuntu

    全文请见tuts code:An Introduction to Python's Flask Framework Flask是Python一个小而强大的web框架.学起来简单,用起来也容易,能够帮你 ...

  3. angular 报错笔记

    1.错误信息: Failed to instantiate module app due to: Error: [$injector:unpr] http://errors.angularjs.org ...

  4. hdu 2037 - 典型贪心*

    题目链接 给一堆电视节目的起止时间,问最多能完整收看几个节目 --------------------------------------------------------------------- ...

  5. HOJ 1867 经理的烦恼 【 树状数组 】

    题意:给出一个区间,求这个区间里面素数的个数 这道题wa了好多次---是因为add操作没有写对 每次更新的时候,应该先判断没有加上y是不是质数,加上了y是不是质数 如果从质数变成不是质数,那么add( ...

  6. tigergao

    互联网从业 6 年.前码农&DBA,现运维&电商创业者,也在做自媒体.终生学习者. 运营微信公众号:高哥咋么看 感兴趣的朋友们可以订阅.

  7. 有趣的console

    博文第一篇,就以前端调试的“座上客”---console开始

  8. CSS常用样式--font

    CSS font 属性 参考:W3school- CSS font 所有浏览器都支持 font 属性,可在一个声明中设置所有字体属性,各属性需按顺序,语法如下: selector{ font:styl ...

  9. keepalive安装配置

    安装 Centos7.4 yum install keepalived 配置 Master服务器配置 [root@wsjy-proxy01 keepalived]# cat keepalived.co ...

  10. Qt 学习之路问题

    Qt5 cannot include the file QMainWindow add "greaterThan(QT_MAJOR_VERSION, 4): QT += widgets&qu ...