如何修改request的parameter的幾種方式
這篇文章僅僅用來參考,本身不想寫,request之所以不想讓你修改parameter的值,就是因為這個東西一般不然改,有人問我為什麼不讓改,表麵上說我隻能說這屬於篡改數據,因為這個使用戶的請求數據,如果被篡改就相當於篡改消息,如果你一天給別人發消息發的是:你好,而對方收到的是:fuck you!,你會怎麼想,嗬嗬!當然它主要是怕不安全把參數數據該亂了,因為程序員畢竟是自己寫程序,尤其是在公共程序裏麵寫,後台程序員發現自己的數據不對,也找不到原因;一般WEB應用會提供一個attribute來提供自己的參數設置,這樣就OK了,但是有些人就是那麼變態說為啥就不能改呢,麵向對象不是相互的麼,有get應該有set的呀,我隻能說,麵向對象來自於生活現實,生活現實中每天逛大街,街上有很多形形色色如花似玉的,但是又可能你隻能看,不能摸,更不能XX,嗬嗬,否則一個異常就出來了:臭流氓!
嗬嗬,不過就技術的角度來講,能實現嗎,當然可以,沒有不可以實現的,源碼之下,了無秘密,這是一個大牛說的,那麼我們先來思考下有那些實現的方式:
1、我自己new一個request,然後放到容器裏頭,放那呢?等會來說,先記錄下。
2、如果我能改掉request裏麵的值,那就好了唄,好的,先記錄下,等會來想怎麼改。
先說第一種方式,我自己new一個,嗬嗬,怎麼new,怎麼讓其他的程序知道。
new的兩種方式之一(開始思考的起源):
先說new的方式,在不知道具體的容器怎麼實現HttpSevletRequest的時候,很簡單,我自己寫個類,implements HttpServletRequest嗬嗬,這個貌似很簡單,OK,繼承下試一試:
public class HttpServletRequestExtend implements HttpServletRequest { .......實現代碼 }
此時提示需要有N多方法需要被實現,例如:
getParameter、getAttribute、getAttributeNames、getCharacterEncoding、getContentLength、getContentType。。。。。。
等等幾十個方法,嗬嗬;
當然,你可以再構造方法裏麵將實際的request對象傳遞進來,如果是相同的方法,就這個request來實現,如果需要自己處理的方法,就按照自己的方式來處理,這種包裝貌似簡單
自己定義parameter,就用一個
private Map<String , String[]>paramterMap = new HashMap<String , String[]>();
就可以簡單搞定,自己再搞個addParameter方法等等,就可以實現自己的功能。
不過寫起來挺費勁的,因為意味著你所有的方法都要去實現下,除非你其他的方法都不用,隻用其中幾個方法而已,這就體現出一些接口的不足了。
但是這種方式是可行的,至少可以這樣說,隻是很費勁而已,因為感覺冗餘很厲害,也體現出接口的不足,和抽象類的價值,我們想要的隻是重載那些我們想要重載的,原有的還是按照它原有的處理思路,此時,有一個叫HttpServletRequestWrapper的出現了;
new方式2:
繼承HttpServletRequestWrapper,其實就是上麵那種方法多了一層繼承,將你的重複工作交予了它,你也可以這樣做,
全名為:javax.servlet.http.HttpServletRequestWrapper,看來也是一個擴展的通用接口,也就是會對request做一次包裝,OK;跟著進去發現它可以處理類似request一樣的差不多的內容,在這個基礎上做了一次包裝,你可以認為他就是對你自己new的那個,多了一層簡單擴展實現,而你再這個基礎上,可以繼續繼承和重寫。
OK,此時你要重寫如何重寫呢,比如我們要重寫一個getParameter方法和getParameterValues方法,其餘的方法保持和原來一致,我們在子類中,自己定義一個Map用來放參數,結合request本身的參數,加上外部其他自定義的參數,做成一個新的參數表。
如下所示:
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交給父類,以便於調用對應方法的時候,將其輸出,其實父親類的實現方式和第一種new的方式類似 super(request); //將參數表,賦予給當前的Map以便於持有request中的參數 this.params.putAll(request.getParameterMap()); } //重載一個構造方法 public ParameterRequestWrapper(HttpServletRequest request , Map<String , Object> extendParams) { this(request); addAllParameters(extendObject);//這裏將擴展參數寫入參數表 } @Override public String getParameter(String name) {//重寫getParameter,代表參數從當前類中的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) {//增加多個參數 for(Map.Entry<String , Object>entry : otherParams.entrySet()) { addParameter(entry.getKey() , entry.getValue()); } } public void addParameter(String name , Object value) {//增加參數 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如何使用,以及【讓業務層使用到】,以及我們要說的,這種方式的【缺陷是什麼】,如何做沒有這種缺陷。
讓業務層知道的方式很簡單,最簡單的方式是:
你寫一個過濾器,在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之前處理。
還有什麼方式呢,在傳入業務層之前你還可以做AOP,如果業務層的入口方法是傳入request的;還有些特殊自理,如struts2裏麵的request對象是通過:ServletActionContext.getRequest()來獲取的,而不是直接入參的,你隻需要,在業務代碼調用前,調用代碼:
ServletActionContext.setRequest(HttpServletRequest request),參數是你自己new出來的這個request就可以了,簡單吧。方法多多,任意你選。
好,開心了一會,回到正題,有缺陷沒有,有的,肯定有的。是什麼,是什麼,是什麼?
剛才重載方法的時候,Map是自己寫的,getParameter方法、getParameterValues方法是重寫了,但是,其他的方法呢?回答是其他方法還是用request以前的值,是的,是以前的值,但是子類的Map數據有增加,request實際沒增加,當你獲取getParameterMap、getParameterNames這些方法的時候,參數就又有問題了,會不一致,這個可以自己測試,當然,最直接的解決方法是將這些方法也給換掉,也沒問題,隻要你願意寫,嗬嗬!
接下來,我們介紹第二種方法,我不推薦使用,但是從技術角度,不得不說是一種方法,隻是這種方法是讓java的安全機製在你麵前裸奔,變得一絲不掛。
可能說到這裏,很多人已經知道我要說啥了,因為可以讓他變得一絲不掛的東西,沒幾樣,在這個層麵,一般說的就是“反射”,是的,request既然不讓我改,那麼我又想修改,那麼我就用反射。
那麼用反射的條件是什麼?熟悉源碼,是的,你必須看懂request怎麼獲取參數的,看源碼容易走入誤區,雖然是錯誤的,但是我還是先說下我走入的那些個誤區,然後再來說怎麼實際的改東西。
我走入的誤區,但是也跟蹤了源碼,因禍得福:
首先通過以下方式找到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時獲取參數的位置是一樣的,然後,我就想嚐試去修改這個Map,可惜當然獲取到這個map的時候,發生put、remove這些操作的時候,直接拋出異常:
IllegalStateException內容裏麵會提示:parameterMap.locked這樣的字樣在裏麵,為啥呢,我們進去看看:
先看看getParameterMap這個方法:
那麼這個request是什麼呢?看到定義:
protected Request request = null;
發現上麵沒有import,那就應該是同一層包下麵的Request類(我沒有直接跟蹤進去就是想要讓大家知道雖然簡單,但是容易混淆,在tomcat源碼中,不止有一個類叫Request,而且存在相互調用)
這個類的全名就是:
org.apache.catalina.connector.Request
跟蹤進去看看他的getParameterMap方法:
可以看到如果map被lock,直接返回,若沒有,則將裏麵做了一個填充的操作,然後再設置為Lock,很簡單吧。這個Lock貌似就和上麵的異常有點關係了。
我們到這個parameterMap看看是什麼類型,裏麵發生了什麼:
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參數設置為false了,就是可以修改了 這下子爽了,可以調用map.put了 map.put("newNode" , new String[] {"阿拉拉拉"}); .... 調用完了,記得: lockedField.setBoolean(map, true);
否則看上述代碼,發現lock是false,會重新初始化,你的設置就悲劇了。
OK,這個時候發現,request.getParameterMap對了,可是其他的貌似不對,getParameter、getParameterValues、getParameterNames這幾個都不對;
最後我發現我走錯了,下麵開始糾正錯誤了:
跟蹤另外幾個方法進去:
發現也是在這個request裏麵,進去看看:
發現又出來一個coyoteRequest,又是哪裏冒出來的:看定義:
protected org.apache.coyote.Request
coyoteRequest;
這個類竟然也叫Request,而且是包裝在現在Request類裏麵的,這就是為什麼開始我要說全名(org.apache.catalina.connector.Request)了繼續跟蹤到後麵這個Reuqest(org.apache.coyote.Request)裏麵去過後,看這個裏麵的getParameters方法,因為可以看出Parameters獲取到,後麵就是鍵值對了。
進去看下:
原來是一個屬性,看下屬性定義:
private Parameters parameters = new Parameters();
這個Parameters到底是啥東西,我能修改麼?
public final class Parameters extends MultiMap { .... }
沒開始那麼興奮,貌似沒見過MultiMap 是什麼。
跟蹤進去,盡然沒發現父類,正在我納悶的時候,翻看這個Parameters的源碼的時候,發現沒用集成,用了下組合,嗬嗬:
private Hashtable<String,String[]> paramHashStringArray = new Hashtable<String,String[]>();
和我想想的差不多,再看看方法,有個addParameterValues,估計它就是用這個方法來設置參數的:
再確認下,發現getParameter、getParameterValues、getParameterNames都間接會直接調用這個hashtable;
這下笑了,因為找到了,就可以讓他裸奔。
要麼找到這個parameter對象,然後調用方法addParam、addParameterValues這些方法,不過貌似要remove不行,還有,這個addParam通過上圖可以看到,如果同一個Key,存在參數,不是替換,而是將結果的數組擴大,要替換還是不行,所以拿到paramHashStringArray這個值就可以幹任何事情了,嗬嗬!
好,我們簡單寫個測試代碼,放在一個filter裏麵:
try { Class clazz = request.getClass(); Field requestField = clazz.getDeclaredField("request"); requestField.setAccessible(true); Object innerRequest = requestField.get(request);//獲取到request對象 //設置尚未初始化 (否則在獲取一些參數的時候,可能會導致不一致) 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來完成對參數變量的修改 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"}); //也可以通過下麵的方法,不過下麵的方法隻能添加參數,如果有相同的key,會追加參數,即,同一個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也是可以的;這種方式改掉的參數,所有的getParameterMap(還沒調用過這個方法之前執行上麵的代碼)、getParameterNames全部都會被改掉。
測試OK了,發現上麵的代碼有點亂,整理下,至少初始化的時候可以省掉很多反射的代碼:
定義幾個靜態變量,初始化的時候,暫時先別做任何動作:
private static Field requestField; private static Field parametersParsedField; private static Field coyoteRequestField; private static Field parametersField; private static Field hashTabArrField;
在static或放在filter的init方法中去執行:
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. }
這段代碼執行後,反射的很多代碼就省下來了;
OK,生下來就是調用了,調用的時候,如果放在Utils裏麵,就提供靜態方法,放在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!"}); }
你就可以瘋狂設置你的參數了,嗬嗬,這個程序開始裸奔了,你clear掉,後台的人瘋了,小心被槍斃,在一些特殊應用中,你可以嚐試去修改一些值達到一些特殊的目的,所以裸奔還是有意義的,嗬嗬!
最後再補充一種方式是:MockHttpServletRequest,全名為:org.springframework.mock.web.MockHttpServletRequest,是spring提供的,前提是你用了spring 2.5或更高的版本,另外需要注意的是,這個spring僅僅提供一個模擬的request,所以裏麵有些東西可能獲取的內容並不是你特別想要的,他實現的方式和第一中方式類似,通常用在測試框架中。
最後更新:2017-04-04 07:03:16