修改request的parameter的幾種方式總結
修改request的parameter的幾種方式總結
這篇文章僅僅用來參考,本身不想寫,request之所以不想讓你修改parameter的值,就是因為這個東西一般不然改,有人問我為什么不讓改,表面上說我只能說這屬于篡改數(shù)據(jù),因為這個使用戶的請求數(shù)據(jù),如果被篡改就相當于篡改消息,如果你一天給別人發(fā)消息發(fā)的是:你好,而對方收到的是:fuck you!,你會怎么想,呵呵!當然它主要是怕不安全把參數(shù)數(shù)據(jù)該亂了,因為程序員畢竟是自己寫程序,尤其是在公共程序里面寫,后臺程序員發(fā)現(xiàn)自己的數(shù)據(jù)不對,也找不到原因;一般WEB應用會提供一個attribute來提供自己的參數(shù)設置,這樣就OK了,但是有些人就是那么變態(tài)說為啥就不能改呢,面向對象不是相互的么,有get應該有set的呀,我只能說,面向對象來自于生活現(xiàn)實,生活現(xiàn)實中每天逛大街,街上有很多形形色色如花似玉的,但是又可能你只能看,不能摸,更不能XX,呵呵,否則一個異常就出來了:臭流氓!
呵呵,不過就技術的角度來講,能實現(xiàn)嗎,當然可以,沒有不可以實現(xiàn)的,源碼之下,了無秘密,這是一個大牛說的,
那么我們先來思考下有那些實現(xiàn)的方式:
1、我自己new一個request,然后放到容器里頭,放那呢?等會來說,先記錄下。
2、如果我能改掉request里面的值,那就好了唄,好的,先記錄下,等會來想怎么改。
先說第一種方式,我自己new一個,呵呵,怎么new,怎么讓其他的程序知道。
new的兩種方式之一(開始思考的起源):
先說new的方式,在不知道具體的容器怎么實現(xiàn)HttpSevletRequest的時候,很簡單,我自己寫個類,implements HttpServletRequest呵呵,這個貌似很簡單,OK,繼承下試一試:
public class HttpServletRequestExtend implements HttpServletRequest { .......實現(xiàn)代碼 }
此時提示需要有N多方法需要被實現(xiàn),例如:
getParameter、getAttribute、getAttributeNames、getCharacterEncoding、getContentLength、getContentType。。。。。。
等等幾十個方法,呵呵;
當然,你可以再構造方法里面將實際的request對象傳遞進來,如果是相同的方法,就這個request來實現(xiàn),如果需要自己處理的方法,就按照自己的方式來處理,這種包裝貌似簡單
自己定義parameter,就用一個
private Map<String , String[]>paramterMap = new HashMap<String , String[]>();
就可以簡單搞定,自己再搞個addParameter方法等等,就可以實現(xiàn)自己的功能。
不過寫起來挺費勁的,因為意味著你所有的方法都要去實現(xiàn)下,除非你其他的方法都不用,只用其中幾個方法而已,這就體現(xiàn)出一些接口的不足了。
但是這種方式是可行的,至少可以這樣說,只是很費勁而已,因為感覺冗余很厲害,也體現(xiàn)出接口的不足,和抽象類的價值,我們想要的只是重載那些我們想要重載的,原有的還是按照它原有的處理思路,此時,有一個叫HttpServletRequestWrapper的出現(xiàn)了;
new方式2:
繼承HttpServletRequestWrapper,其實就是上面那種方法多了一層繼承,將你的重復工作交予了它,你也可以這樣做,
全名為:javax.servlet.http.HttpServletRequestWrapper,看來也是一個擴展的通用接口,也就是會對request做一次包裝,OK;跟著進去發(fā)現(xiàn)它可以處理類似request一樣的差不多的內容,在這個基礎上做了一次包裝,你可以認為他就是對你自己new的那個,多了一層簡單擴展實現(xiàn),而你再這個基礎上,可以繼續(xù)繼承和重寫。
OK,此時你要重寫如何重寫呢,比如我們要重寫一個getParameter方法和getParameterValues方法,其余的方法保持和原來一致,我們在子類中,自己定義一個Map用來放參數(shù),結合request本身的參數(shù),加上外部其他自定義的參數(shù),做成一個新的參數(shù)表。
如下所示:
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.util.HashMap; import java.util.Map; public class ParameterRequestWrapper extends HttpServletRequestWrapper { private Map<String , String[]> params = new HashMap<String, String[]>(); @SuppressWarnings("unchecked") public ParameterRequestWrapper(HttpServletRequest request) { // 將request交給父類,以便于調用對應方法的時候,將其輸出,其實父親類的實現(xiàn)方式和第一種new的方式類似 super(request); //將參數(shù)表,賦予給當前的Map以便于持有request中的參數(shù) this.params.putAll(request.getParameterMap()); } //重載一個構造方法 public ParameterRequestWrapper(HttpServletRequest request , Map<String , Object> extendParams) { this(request); addAllParameters(extendObject);//這里將擴展參數(shù)寫入?yún)?shù)表 } @Override public String getParameter(String name) {//重寫getParameter,代表參數(shù)從當前類中的map獲取 String[]values = params.get(name); if(values == null || values.length == 0) { return null; } return values[0]; } public String[] getParameterValues(String name) {//同上 return params.get(name); } public void addAllParameters(Map<String , Object>otherParams) {//增加多個參數(shù) for(Map.Entry<String , Object>entry : otherParams.entrySet()) { addParameter(entry.getKey() , entry.getValue()); } } public void addParameter(String name , Object value) {//增加參數(shù) if(value != null) { if(value instanceof String[]) { params.put(name , (String[])value); }else if(value instanceof String) { params.put(name , new String[] {(String)value}); }else { params.put(name , new String[] {String.valueOf(value)}); } } } }
好了,兩種new的方式都有了,我們推薦那種?一般來說推薦第二種方式,至少他給你提供好了一些東西,不過怎么說呢,你要明白是怎么回事,第一種方式到第二種方式的演變是需要知道的,至少你要知道,效果是一樣的就是了,第一種方式里面有大量的方法需要重寫,第二種不需要,這屬于設計模式的知識,我們這不詳細探討了。
接下來我們說下將new出來的request如何使用,以及【讓業(yè)務層使用到】,以及我們要說的,這種方式的【缺陷是什么】,如何做沒有這種缺陷。
讓業(yè)務層知道的方式很簡單,最簡單的方式是:
你寫一個過濾器,在filter這個地方new了這個自己定義的request后,然后將在doFilter的時候,給的request就不是傳入的request,而是你自己new出來的,接下來所有的request都是你new出來的了,如下所示:
ParameterRequestWrapper requestWrapper = new ParameterRequestWrapper((HttpServletRequest)request); requestWrapper.addParameter("fff" , "我靠"); filterChain.doFilter(requestWrapper, servletResponse);
接下來,應用使用到的request對象,通過getParameter方法就能得到一個字符串叫:“我靠”,呵呵;注意,這個Fiter一定要在類似struts或者spring MVC之前處理。
還有什么方式呢,在傳入業(yè)務層之前你還可以做AOP,如果業(yè)務層的入口方法是傳入request的;還有些特殊自理,如struts2里面的request對象是通過:ServletActionContext.getRequest()來獲取的,而不是直接入?yún)⒌?,你只需要,在業(yè)務代碼調用前,調用代碼:
ServletActionContext.setRequest(HttpServletRequest request),參數(shù)是你自己new出來的這個request就可以了,簡單吧。方法多多,任意你選。
好,開心了一會,回到正題,有缺陷沒有,有的,肯定有的。是什么,是什么,是什么?
剛才重載方法的時候,Map是自己寫的,getParameter方法、getParameterValues方法是重寫了,但是,其他的方法呢?回答是其他方法還是用request以前的值,是的,是以前的值,但是子類的Map數(shù)據(jù)有增加,request實際沒增加,當你獲取getParameterMap、getParameterNames這些方法的時候,參數(shù)就又有問題了,會不一致,這個可以自己測試,當然,最直接的解決方法是將這些方法也給換掉,也沒問題,只要你愿意寫,呵呵!
接下來,我們介紹第二種方法,我不推薦使用,但是從技術角度,不得不說是一種方法,只是這種方法是讓java的安全機制在你面前裸奔,變得一絲不掛。
可能說到這里,很多人已經(jīng)知道我要說啥了,因為可以讓他變得一絲不掛的東西,沒幾樣,在這個層面,一般說的就是“反射”,是的,request既然不讓我改,那么我又想修改,那么我就用反射。
那么用反射的條件是什么?熟悉源碼,是的,你必須看懂request怎么獲取參數(shù)的,看源碼容易走入誤區(qū),雖然是錯誤的,但是我還是先說下我走入的那些個誤區(qū),然后再來說怎么實際的改東西。
我走入的誤區(qū),但是也跟蹤了源碼,因禍得福:
首先通過以下方式找到request的實例來自于哪里,是那個類(因為HttpServletRequest是一個接口),那個jar包:
request.getClass() 就獲取到是那個類,在tomcat下,看到是:org.apache.catalina.connector.RequestFacade這個類,其實看package就基本知道jar包的名稱是啥了
不過可以通過程序看下是啥:
request.getClass().getResource("").getPath()
可以得到request所在的jar包的源文件文件路徑。
或者這樣也可以:
request.getClass().getResource("/org/apache/catalina/connector/RequestFacade.class").getPath()
一樣可以獲取到,主要要加第一個反斜杠哦,否則會認為是當前class的相對路徑的,第一個為長度為0的字符串""就是指當前路徑了。
可以得到是tomcat下面的lib目錄下的catalina.jar這個包。
這些可以反編譯,也可以到官方下載源碼,我們下面來看看源碼:
我當時第一理解是getParameterMap獲取的map和getParameter時獲取參數(shù)的位置是一樣的,然后,我就想嘗試去修改這個Map,可惜當然獲取到這個map的時候,發(fā)生put、remove這些操作的時候,直接拋出異常:
IllegalStateException內容里面會提示:parameterMap.locked這樣的字樣在里面,為啥呢,我們進去看看:
先看看getParameterMap這個方法:
那么這個request是什么呢?看到定義:
protected Request request = null;
發(fā)現(xiàn)上面沒有import,那就應該是同一層包下面的Request類(我沒有直接跟蹤進去就是想要讓大家知道雖然簡單,但是容易混淆,在tomcat源碼中,不止有一個類叫Request,而且存在相互調用)
這個類的全名就是:
org.apache.catalina.connector.Request
跟蹤進去看看他的getParameterMap方法:
可以看到如果map被lock,直接返回,若沒有,則將里面做了一個填充的操作,然后再設置為Lock,很簡單吧。這個Lock貌似就和上面的異常有點關系了。
我們到這個parameterMap看看是什么類型,里面發(fā)生了什么:
protected ParameterMap parameterMap = new ParameterMap();
那么ParameterMap 是什么定義的呢:
public final class ParameterMap extends HashMap { ..... }
有點意思了,貌似找到組織了,竟然是HashMap的兒子,還有搞不定的嘛,眼看就要一切撥開云霧見青天了。
在看看里面的lock到底做了啥,找個put方法:
乖乖,終于找到兇手了,再看看其他的clear方法都做了類似操作,要修改這個怎么辦?簡單想辦法把這個Map拿到,然后setLock(false)然后就可以操作了,然后操作完再setLock(true)呵呵,怎么獲取到這個Map呢?
getParameterMap其實就是返回了他,將他強制類型轉換為ParameterMap,貌似不靠譜,因為這個Class不在你的應用內存里面,引用不到,不過可以做的是什么反射?
呵呵!簡單來說,獲取到這個Map后,假如被命名為map
Filed lockedField = map.getClass().getDeclaredField("locked"); lockedField.setAccessible(true);//打開訪問權限,讓他裸奔,private類型照樣玩他 lockedField.setBoolean(map, false);//將lock參數(shù)設置為false了,就是可以修改了 這下子爽了,可以調用map.put了 map.put("newNode" , new String[] {"阿拉拉拉"}); .... 調用完了,記得: lockedField.setBoolean(map, true);
否則看上述代碼,發(fā)現(xiàn)lock是false,會重新初始化,你的設置就悲劇了。
OK,這個時候發(fā)現(xiàn),request.getParameterMap對了,可是其他的貌似不對,getParameter、getParameterValues、getParameterNames這幾個都不對;
最后我發(fā)現(xiàn)我走錯了,下面開始糾正錯誤了:
跟蹤另外幾個方法進去:
發(fā)現(xiàn)也是在這個request里面,進去看看:
發(fā)現(xiàn)又出來一個coyoteRequest,又是哪里冒出來的:看定義:
protected org.apache.coyote.Request coyoteRequest;
這個類竟然也叫Request,而且是包裝在現(xiàn)在Request類里面的,這就是為什么開始我要說全名(org.apache.catalina.connector.Request)了繼續(xù)跟蹤到后面這個Reuqest(org.apache.coyote.Request)里面去過后,看這個里面的getParameters方法,因為可以看出Parameters獲取到,后面就是鍵值對了。
進去看下:
原來是一個屬性,看下屬性定義:
private Parameters parameters = new Parameters();
這個Parameters到底是啥東西,我能修改么?
public final class Parameters extends MultiMap { .... }
沒開始那么興奮,貌似沒見過MultiMap 是什么。
跟蹤進去,盡然沒發(fā)現(xiàn)父類,正在我納悶的時候,翻看這個Parameters的源碼的時候,發(fā)現(xiàn)沒用集成,用了下組合,呵呵:
private Hashtable<String,String[]> paramHashStringArray = new Hashtable<String,String[]>();
和我想想的差不多,再看看方法,有個addParameterValues,估計它就是用這個方法來設置參數(shù)的:
再確認下,發(fā)現(xiàn)getParameter、getParameterValues、getParameterNames都間接會直接調用這個hashtable;
這下笑了,因為找到了,就可以讓他裸奔。
要么找到這個parameter對象,然后調用方法addParam、addParameterValues這些方法,不過貌似要remove不行,還有,這個addParam通過上圖可以看到,如果同一個Key,存在參數(shù),不是替換,而是將結果的數(shù)組擴大,要替換還是不行,所以拿到paramHashStringArray這個值就可以干任何事情了,呵呵!
好,我們簡單寫個測試代碼,放在一個filter里面:
try { Class clazz = request.getClass(); Field requestField = clazz.getDeclaredField("request"); requestField.setAccessible(true); Object innerRequest = requestField.get(request);//獲取到request對象 //設置尚未初始化 (否則在獲取一些參數(shù)的時候,可能會導致不一致) Field field = innerRequest.getClass().getDeclaredField("parametersParsed"); field.setAccessible(true); field.setBoolean(innerRequest , false); Field coyoteRequestField = innerRequest.getClass().getDeclaredField("coyoteRequest"); coyoteRequestField.setAccessible(true); Object coyoteRequestObject = coyoteRequestField.get(innerRequest);//獲取到coyoteRequest對象 Field parametersField = coyoteRequestObject.getClass().getDeclaredField("parameters"); parametersField.setAccessible(true); Object parameterObject = parametersField.get(coyoteRequestObject);//獲取到parameter的對象 //獲取hashtable來完成對參數(shù)變量的修改 Field hashTabArrField = parameterObject.getClass().getDeclaredField("paramHashStringArray"); hashTabArrField.setAccessible(true); @SuppressWarnings("unchecked") Map<String,String[]> map = (Map<String,String[]>)hashTabArrField.get(parameterObject); map.put("fuck" , new String[] {"fuck you"}); //也可以通過下面的方法,不過下面的方法只能添加參數(shù),如果有相同的key,會追加參數(shù),即,同一個key的結果集會有多個 // Method method = parameterObject.getClass().getDeclaredMethod("addParameterValues" , String.class , String[].class); // method.invoke(parameterObject , "fuck" , new String[] {"fuck you!" , "sssss"}); } catch (Exception e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } System.out.println(request.getParameter("fuck"));
此時getParameter就能獲取到寫進去的值了哦,getParameterValues也是可以的;這種方式改掉的參數(shù),所有的getParameterMap(還沒調用過這個方法之前執(zhí)行上面的代碼)、getParameterNames全部都會被改掉。
測試OK了,發(fā)現(xiàn)上面的代碼有點亂,整理下,至少初始化的時候可以省掉很多反射的代碼:
定義幾個靜態(tài)變量,初始化的時候,暫時先別做任何動作:
private static Field requestField; private static Field parametersParsedField; private static Field coyoteRequestField; private static Field parametersField; private static Field hashTabArrField;
在static或放在filter的init方法中去執(zhí)行:
try { Class clazz = Class.forName("org.apache.catalina.connector.RequestFacade"); requestField = clazz.getDeclaredField("request"); requestField.setAccessible(true); parametersParsedField = requestField.getType().getDeclaredField("parametersParsed"); parametersParsedField.setAccessible(true); coyoteRequestField = requestField.getType().getDeclaredField("coyoteRequest"); coyoteRequestField.setAccessible(true); parametersField = coyoteRequestField.getType().getDeclaredField("parameters"); parametersField.setAccessible(true); hashTabArrField = parametersField.getType().getDeclaredField("paramHashStringArray"); hashTabArrField.setAccessible(true); } catch (Exception e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. }
這段代碼執(zhí)行后,反射的很多代碼就省下來了;
OK,生下來就是調用了,調用的時候,如果放在Utils里面,就提供靜態(tài)方法,放在Filter里面隨你,反正filter是單例的:
我們就想得到那個Map,所以就提供一個:getRequestMap方法就O了:
@SuppressWarnings("unchecked") private Map<String , String[]> getRequestMap(ServletRequest request) { try { Object innerRequest = requestField.get(request); parametersParsedField.setBoolean(innerRequest, true); Object coyoteRequestObject = coyoteRequestField.get(innerRequest); Object parameterObject = parametersField.get(coyoteRequestObject); return (Map<String,String[]>)hashTabArrField.get(parameterObject); } catch (IllegalAccessException e) { e.printStackTrace(); return Collections.emptyMap(); } }
doFilter的時候,調用下:
Map<String , String[]> map = getRequestMap(request); if(map != null) { map.put("fuck" , new String[] {"fuck you!"}); }
你就可以瘋狂設置你的參數(shù)了,呵呵,這個程序開始裸奔了,你clear掉,后臺的人瘋了,小心被槍斃,在一些特殊應用中,你可以嘗試去修改一些值達到一些特殊的目的,所以裸奔還是有意義的,呵呵!
最后再補充一種方式是:
MockHttpServletRequest,全名為:org.springframework.mock.web.MockHttpServletRequest,是spring提供的,前提是你用了spring 2.5或更高的版本,另外需要注意的是,這個spring僅僅提供一個模擬的request,所以里面有些東西可能獲取的內容并不是你特別想要的,他實現(xiàn)的方式和第一中方式類似,通常用在測試框架中。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
ImportBeanDefinitionRegistrar手動控制BeanDefinition創(chuàng)建注冊詳解
這篇文章主要為大家介紹了ImportBeanDefinitionRegistrar手動控制BeanDefinition創(chuàng)建注冊詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12SpringBoot + 微信公眾號JSAPI支付功能的實現(xiàn)
這篇文章主要介紹了SpringBoot + 微信公眾號JSAPI支付功能的實現(xiàn),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03Java 獲取Html文本中的img標簽下src中的內容方法
今天小編就為大家分享一篇Java 獲取Html文本中的img標簽下src中的內容方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-06-06springMVC向Controller傳值出現(xiàn)中文亂碼的解決方案
這篇文章主要介紹了springMVC向Controller傳值出現(xiàn)中文亂碼的解決方案,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02創(chuàng)建SpringBoot工程并集成Mybatis的方法
這篇文章主要介紹了創(chuàng)建SpringBoot工程并集成Mybatis,需要的朋友可以參考下2018-06-06JavaWeb Spring注解Annotation深入學習
這篇文章主要為大家詳細介紹了JavaWeb Spring注解Annotation,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-09-09Spring數(shù)據(jù)庫事務的實現(xiàn)機制講解
這篇文章主要介紹了Spring數(shù)據(jù)庫事務的實現(xiàn)機制講解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10