閱讀189 返回首頁    go 阿裏雲 go 技術社區[雲棲]


如何修改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)了繼續跟蹤到後麵這個Reuqestorg.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

  上一篇:go SecureCRT 常用命令
  下一篇:go 叫好不叫座 Windows 8的問題究竟出在哪?