@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. spring boot多数据源配置示例

    1. application.properties #\u4E3B\u5E93\u914D\u7F6E spring.datasource.primary.url=jdbc:mysql://mysql ...

  2. Java8新特性 利用流和Lambda表达式对List集合进行处理

    Lambda表达式处理List 最近在做项目的过程中经常会接触到 lambda 表达式,随后发现它基本上可以替代所有 for 循环,包括增强for循环.也就是我认为,绝大部分的for循环都可以用 la ...

  3. Android TextView加上阴影效果

    <TextView android:id="@+id/test_shadow" android:layout_width="wrap_content" a ...

  4. Java绘图技术基础

    public class Demo1 extends JFrame{ MyPanel mp=null; public static void main(String[] args){ Demo1 de ...

  5. 【原创】rman 全库备份脚本

    rman 全库备份脚本 run { allocate channel d1 type disk; allocate channel d2 type disk; backup full database ...

  6. windows 路由

    route ? 查看帮助 route print 查看路由表 添加一条路由: route add 10.10.10.0 mask 255.255.255.0  192.168.1.1 #到达10.10 ...

  7. 支持JSONP跨域的对象

    支持JSONP跨域的对象 1:img 2:iframe 3:link 4:script 为什么,JSONP 最终选择是 script 实现呢?度娘来也! 平常我们进行JSONP请求数据,因为 json ...

  8. xampp、phpstudy安装phalcon

    1.下载扩展 https://github.com/phalcon/cphalcon/releases/tag/v3.4.1选择PHP对应版本的phalcon扩展 2.PHP.ini 配置phalco ...

  9. 学习爬虫:《Python网络数据采集》中英文PDF+代码

    适合爬虫入门的书籍<Python网络数据采集>,采用简洁强大的Python语言,介绍了网络数据采集,并为采集新式网络中的各种数据类型提供了全面的指导.第一部分重点介绍网络数据采集的基本原理 ...

  10. ERROR in xxxx.js from UglifyJS——配置版本混杂版

    常规解决套路可以参考这篇:https://segmentfault.com/a/11... 我采用了上面的做法,依然没法解决.我采用的是vue-cli脚手架自动生成的项目结构: vue-cli版本 2 ...