由防止表单重复提交引发的一系列问题--servletRequest的复制、body值的获取
生活随笔
收集整理的這篇文章主要介紹了
由防止表单重复提交引发的一系列问题--servletRequest的复制、body值的获取
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
@Time:2019年1月4日 16:19:19 @Author:QGuo 背景:最開(kāi)始打算寫個(gè)防止表單重復(fù)提交的攔截器;網(wǎng)上見(jiàn)到一種不錯(cuò)的方式,比較合適前后端分離,校驗(yàn)在后臺(tái)實(shí)現(xiàn);
@SameUrlData
public AjaxMessage monGraphSave(@RequestBody MonGraphFB monGraphFB){} ====================代碼結(jié)束=================== 整理至此,主要有以下注意點(diǎn); ①、得考慮post請(qǐng)求參數(shù)獲取的特殊性 ②、request.getInputStream() 只能獲取一次,要想可以多次讀取,得繼承HttpServletRequestWrapper,讀出來(lái)--放回去 ③、過(guò)濾器的目的是可以直接讀取request里面的body ④、request參數(shù)body可能很大,可以取hash值。 ⑤、key、value的存儲(chǔ),需要設(shè)置過(guò)期時(shí)間; 心得: 其實(shí)我覺(jué)得防止表單重復(fù)提交這個(gè)功能,作用不是特別大;因?yàn)橹灰S便加一個(gè)參數(shù),就可以把需要的參數(shù)重復(fù)添加進(jìn)系統(tǒng)中; 只能做到,防止用戶誤操作,點(diǎn)擊了多次這種情況;(一般前端也會(huì)做處理的,但萬(wàn)一前端抽風(fēng)自動(dòng)發(fā)起了多次請(qǐng)求呢); 只能說(shuō)一定程度上 更加完善吧 改進(jìn): 可以在SameUrlDataInterceptor攔截器中,添加response響應(yīng)內(nèi)容,讓用戶知道自己重復(fù)提交了。 很簡(jiǎn)單不舉例了;
?
我在此基礎(chǔ)上,將key,value。Objects.hashCode()了下 因?yàn)閞equest的body 可能太大,過(guò)長(zhǎng); 但不保證存在不同的object生成的哈希值卻相同,但是我們目的只是為了防止重復(fù)提交而已,不同對(duì)象生成哈希值相同的機(jī)率很小。 ==========================代碼==============================1、HttpServletRequestReplacedFilter 過(guò)濾器.
目的:post請(qǐng)求時(shí),復(fù)制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 {@Overridepublic void destroy() {}@Overridepublic 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, 參數(shù)值在request body中以 a=1&b=2&c=3...形式存在,//若直接構(gòu)造BodyReaderHttpServletRequestWrapper,在將流讀取并存到copy字節(jié)數(shù)組里之后,//httpRequest.getParameterMap()將返回空值!//若運(yùn)行一下 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);}}@Overridepublic void init(FilterConfig arg0) throws ServletException {} } HttpServletRequestReplacedFilter 2、HttpServletRequestWrapper --復(fù)制ServletRequest 目的在于:使servletRequest可以重復(fù)獲取inputStream、reader; 1 package com.kdgz.service; 2 3 import javax.servlet.ReadListener; 4 import javax.servlet.ServletInputStream; 5 import javax.servlet.http.HttpServletRequest; 6 import javax.servlet.http.HttpServletRequestWrapper; 7 import java.io.BufferedReader; 8 import java.io.ByteArrayInputStream; 9 import java.io.IOException; 10 import java.io.InputStreamReader; 11 import java.nio.charset.Charset; 12 import java.util.Enumeration; 13 import java.util.Map; 14 15 /** 16 * @author QGuo 17 * @date 2019/1/3 15:05 18 */ 19 public class BodyHttpServletRequestWrapper extends HttpServletRequestWrapper { 20 21 private byte[] body; 22 23 public byte[] getBody() { return body; } 24 25 public void setBody(byte[] body) { this.body = body; } 26 27 public BodyHttpServletRequestWrapper(HttpServletRequest request) throws IOException { 28 super(request); 29 body = this.getBodyString(request).getBytes(Charset.forName("UTF-8")); 30 } 31 32 @Override 33 public BufferedReader getReader() throws IOException { 34 return new BufferedReader(new InputStreamReader(getInputStream(),"UTF-8")); 35 } 36 37 @Override 38 public ServletInputStream getInputStream() throws IOException { 39 40 final ByteArrayInputStream bais = new ByteArrayInputStream(this.body); 41 42 return new ServletInputStream() { 43 @Override 44 public boolean isFinished() { return false; } 45 46 @Override 47 public boolean isReady() { return false; } 48 49 @Override 50 public void setReadListener(ReadListener readListener) {} 51 52 @Override 53 public int read() throws IOException { return bais.read(); } 54 }; 55 } 56 57 @Override 58 public String getHeader(String name) { return super.getHeader(name); } 59 60 @Override 61 public Enumeration<String> getHeaderNames() { return super.getHeaderNames(); } 62 63 @Override 64 public Enumeration<String> getHeaders(String name) { return super.getHeaders(name); } 65 66 @Override 67 public Map<String, String[]> getParameterMap() { return super.getParameterMap(); } 68 69 public String getBodyString(ServletRequest request) { 70 StringBuilder sb = new StringBuilder(); 71 InputStream inputStream = null; 72 BufferedReader reader = null; 73 try { 74 inputStream = request.getInputStream(); 75 reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); 76 String line = ""; 77 while ((line = reader.readLine()) != null) { 78 sb.append(line); 79 } 80 } catch (IOException e) { 81 e.printStackTrace(); 82 } finally { 83 if (inputStream != null) { 84 try { 85 inputStream.close(); 86 } catch (IOException e) { 87 e.printStackTrace(); 88 } 89 } 90 if (reader != null) { 91 try { 92 reader.close(); 93 } catch (IOException e) { 94 e.printStackTrace(); 95 } 96 } 97 } 98 return sb.toString(); 99 } 100 } HttpServletRequestWrapper?
3、web.xml 中添加過(guò)濾器
1 <filter> 2 <filter-name>httpServletRequestFilter</filter-name> 3 <filter-class>com.kdgz.service.HttpServletRequestReplacedFilter</filter-class> 4 </filter> 5 <filter-mapping> 6 <filter-name>httpServletRequestFilter</filter-name> 7 <url-pattern>/*</url-pattern> 8 </filter-mapping>?
4、添加自定義注解
1 package com.kdgz.annotation; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Retention; 5 import java.lang.annotation.RetentionPolicy; 6 import java.lang.annotation.Target; 7 8 /** 9 * @author QGuo 10 * @date 2018/12/24 13:58 11 * 一個(gè)用戶 相同url 同時(shí)提交 相同數(shù)據(jù) 驗(yàn)證 12 */ 13 @Target(ElementType.METHOD) 14 @Retention(RetentionPolicy.RUNTIME) 15 public @interface SameUrlData { 16 } @SameUrlData?
5、添加攔截器
1 package com.kdgz.service; 2 3 import com.alibaba.fastjson.JSON; 4 import com.kdgz.annotation.SameUrlData; 5 import org.apache.commons.lang3.StringUtils; 6 import org.springframework.data.redis.core.StringRedisTemplate; 7 import org.springframework.web.method.HandlerMethod; 8 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 9 10 import javax.annotation.Resource; 11 import javax.servlet.http.HttpServletRequest; 12 import javax.servlet.http.HttpServletResponse; 13 import java.io.IOException; 14 import java.lang.reflect.Method; 15 import java.util.HashMap; 16 import java.util.Map; 17 import java.util.Objects; 18 import java.util.concurrent.TimeUnit; 19 20 /** 21 * 一個(gè)用戶 相同url 同時(shí)提交 相同數(shù)據(jù) 驗(yàn)證 22 * 主要通過(guò) session中保存到的url 和 請(qǐng)求參數(shù)。如果和上次相同,則是重復(fù)提交表單 23 * 24 * @author QGuo 25 * @date 2018/12/24 14:02 26 */ 27 public class SameUrlDataInterceptor extends HandlerInterceptorAdapter { 28 @Resource 29 StringRedisTemplate stringRedisTemplate; 30 31 @Override 32 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 33 if (handler instanceof HandlerMethod) { 34 HandlerMethod handlerMethod = (HandlerMethod) handler; 35 Method method = handlerMethod.getMethod(); 36 SameUrlData annotation = method.getAnnotation(SameUrlData.class); 37 if (annotation != null) { 38 if (repeatDataValidator(request)) {//如果重復(fù)相同數(shù)據(jù) 39 //在此可添加response響應(yīng)內(nèi)容,提醒用戶重復(fù)提交了 40 return false; 41 } else 42 return true; 43 } 44 return true; 45 } else { 46 return super.preHandle(request, response, handler); 47 } 48 } 49 50 /** 51 * 驗(yàn)證同一個(gè)url數(shù)據(jù)是否相同提交 ,相同返回true 52 * 53 * @param httpServletRequest 54 * @return 55 */ 56 public boolean repeatDataValidator(HttpServletRequest httpServletRequest) throws IOException { 57 Map<String, String[]> parameterMap = new HashMap(httpServletRequest.getParameterMap()); 58 //刪除參數(shù)中的v;(v參數(shù)為隨機(jī)生成的字符串,目的是為了每次訪問(wèn)都是最新值,既然要防止重復(fù)提交,需要剔除此參數(shù)) 59 if (parameterMap.containsKey("v")) 60 parameterMap.remove("v"); 61 //每一位登錄者都有唯一一個(gè)token認(rèn)證 62 String tokens = ""; 63 if (parameterMap.get("token").length > 0) 64 tokens = parameterMap.get("token")[0]; 65 String method = httpServletRequest.getMethod().toUpperCase();//請(qǐng)求類型,GET、POST 66 String params; 67 if (StringUtils.equals(method, "POST")) {//post請(qǐng)求時(shí) 68 BodyHttpServletRequestWrapper requestWrapper = new BodyHttpServletRequestWrapper((HttpServletRequest) httpServletRequest); 69 byte[] bytes = requestWrapper.getBody(); 70 if (bytes.length != 0) { 71 params = JSON.toJSONString(new String(bytes, "UTF-8").trim()); 72 } else {//若body被清空,則說(shuō)明參數(shù)全部被填充到Parameter集合中了 73 /** 74 * 當(dāng)滿足一下條件時(shí),就會(huì)被填充到parameter集合中 75 * 1:是一個(gè)http/https請(qǐng)求 76 * 2:請(qǐng)求方法是post 77 * 3:請(qǐng)求類型(content-Type)是application/x-www-form-urlencoded 78 * 4: Servlet調(diào)用了getParameter系列方法 79 */ 80 Map<String, String[]> map = new HashMap(requestWrapper.getParameterMap()); 81 // 去除 v 參數(shù) 82 if (map.containsKey("v")) 83 map.remove("v"); 84 params = JSON.toJSONString(map); 85 } 86 } else { 87 params = JSON.toJSONString(parameterMap); 88 } 89 90 String url = String.valueOf(Objects.hashCode(httpServletRequest.getRequestURI() + tokens)); 91 Map<String, String> map = new HashMap<String, String>(); 92 map.put(url, params); 93 //防止參數(shù)過(guò)多,string過(guò)大;現(xiàn)將儲(chǔ)存為 hash編碼; 94 String nowUrlParams = String.valueOf(Objects.hashCode(map)); 95 String preUrlParams = stringRedisTemplate.opsForValue().get(url); 96 if (preUrlParams == null) {//如果上一個(gè)數(shù)據(jù)為null,表示還沒(méi)有訪問(wèn)頁(yè)面 97 //設(shè)置過(guò)期時(shí)間為3分鐘 98 stringRedisTemplate.opsForValue().set(url, nowUrlParams, 3, TimeUnit.MINUTES); 99 return false; 100 } else if (preUrlParams.equals(nowUrlParams)) {//否則,已經(jīng)訪問(wèn)過(guò)頁(yè)面 101 //如果上次url+數(shù)據(jù)和本次url+數(shù)據(jù)相同,則表示重復(fù)添加數(shù)據(jù) 102 return true; 103 } else {//如果上次 url+數(shù)據(jù) 和本次url加數(shù)據(jù)不同,則不是重復(fù)提交,更新 104 stringRedisTemplate.opsForValue().set(url, nowUrlParams, 3, TimeUnit.MINUTES); 105 return false; 106 } 107 } 108 } SameUrlDataInterceptor?
使用的時(shí)候,只要在接口上,添加注解即可 例如: @RequestMapping(value = "v1.0/monGraphSave")@SameUrlData
public AjaxMessage monGraphSave(@RequestBody MonGraphFB monGraphFB){} ====================代碼結(jié)束=================== 整理至此,主要有以下注意點(diǎn); ①、得考慮post請(qǐng)求參數(shù)獲取的特殊性 ②、request.getInputStream() 只能獲取一次,要想可以多次讀取,得繼承HttpServletRequestWrapper,讀出來(lái)--放回去 ③、過(guò)濾器的目的是可以直接讀取request里面的body ④、request參數(shù)body可能很大,可以取hash值。 ⑤、key、value的存儲(chǔ),需要設(shè)置過(guò)期時(shí)間; 心得: 其實(shí)我覺(jué)得防止表單重復(fù)提交這個(gè)功能,作用不是特別大;因?yàn)橹灰S便加一個(gè)參數(shù),就可以把需要的參數(shù)重復(fù)添加進(jìn)系統(tǒng)中; 只能做到,防止用戶誤操作,點(diǎn)擊了多次這種情況;(一般前端也會(huì)做處理的,但萬(wàn)一前端抽風(fēng)自動(dòng)發(fā)起了多次請(qǐng)求呢); 只能說(shuō)一定程度上 更加完善吧 改進(jìn): 可以在SameUrlDataInterceptor攔截器中,添加response響應(yīng)內(nèi)容,讓用戶知道自己重復(fù)提交了。 很簡(jiǎn)單不舉例了;
轉(zhuǎn)載于:https://www.cnblogs.com/QGuo/p/10234386.html
總結(jié)
以上是生活随笔為你收集整理的由防止表单重复提交引发的一系列问题--servletRequest的复制、body值的获取的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: mybatis-generator自动生
- 下一篇: 势能线段树/吉司机线段树-我没有脑子