`
wxyfighting
  • 浏览: 191699 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

如何修改request的parameter的几种方式

 
阅读更多

这篇文章仅仅用来参考,本身不想写,request之所以不想让你修改parameter的值,就是因为这个东西一般不然改,有人问我为什么不让改,表面上说我只能说这属于篡改数据,因为这个使用户的请求数据,如果被篡改就相当于篡改消息,如果你一天给别人发消息发的是:你好,而对方收到的是:fuck you!,你会怎么想,呵呵!当然它主要是怕不安全把参数数据该乱了,因为程序员毕竟是自己写程序,尤其是在公共程序里面写,后台程序员发现自己的数据不对,也找不到原因;一般WEB应用会提供一个attribute来提供自己的参数设置,这样就OK了,但是有些人就是那么变态说为啥就不能改呢,面向对象不是相互的么,有get应该有set的呀,我只能说,面向对象来自于生活现实,生活现实中每天逛大街,街上有很多形形色色如花似玉的,但是又可能你只能看,不能摸,更不能XX,呵呵,否则一个异常就出来了:臭流氓!

呵呵,不过就技术的角度来讲,能实现吗,当然可以,没有不可以实现的,源码之下,了无秘密,这是一个大牛说的,那么我们先来思考下有那些实现的方式:

1、我自己new一个request,然后放到容器里头,放那呢?等会来说,先记录下。

2、如果我能改掉request里面的值,那就好了呗,好的,先记录下,等会来想怎么改。


先说第一种方式,我自己new一个,呵呵,怎么new,怎么让其他的程序知道。

new的两种方式之一(开始思考的起源):

先说new的方式,在不知道具体的容器怎么实现HttpSevletRequest的时候,很简单,我自己写个类,implementsHttpServletRequest呵呵,这个貌似很简单,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,所以里面有些东西可能获取的内容并不是你特别想要的,他实现的方式和第一中方式类似,通常用在测试框架中。


分享到:
评论

相关推荐

    haproxy-2.0.5_for_windows.rar

      7、HAProxy负载均衡算法具体有如下几种:      ①roundrobin,表示简单的轮询,这个不多说,这个是负载均衡基本都具备的;      ②static-rr,表示根据权重;      ③leastconn,表示最少连接者先...

    springmybatis

    下面对这几个配置文件解释下: 1.Configuration.xml 是 mybatis 用来建立 sessionFactory 用的,里面主要包含了数据库连接相关东西,还有 java 类所对应的别名,比如 ...

    DWR.xml配置文件说明书(含源码)

    DWR 自动将DOM、DOM4J、JDOM和XOM转换成DOM树,前面这几种类型都仅仅返回Document,Element,Node.DWR会自动将这些转换成浏览器DOM对象.通常在启动JDOM Converter时会有一个提示信息,除非你想采用JDOMconverter否则可以...

    ssh(structs,spring,hibernate)框架中的上传下载

    Spring针对几个著名的Web服务器的数据源提供了相应的JDBC抽取器:  •WebLogic:WebLogicNativeJdbcExtractor  •WebSphere:WebSphereNativeJdbcExtractor  •JBoss:JBossNativeJdbcExtractor  在定义了JDBC...

    107个常用javascript语句

    59.contentEditable可设置元素是否可被修改,isContentEditable返回是否可修改的状態. 60.isDisabled判断是否为禁止状態.disabled设置禁止状態 61.length取得长度,返回整型数值 62.addBehavior()是一种JS调用的外部...

    Oracle数据库管理员技术指南

    1.7.4 修改 SYSTEM 用户的缺省和临时 表空间 1.7.5 更改 SYS 和 SYSTEM 的缺省 口令 1.7.6 建立其他用户和模式对象 1.7.7 启用归档日志方式 1.7.8 进行数据库完全备份 1.7.9 配置数据库自动启动和关闭 ...

    亮剑.NET深入体验与实战精要2

    5.2.4 实现SqlParameter方式 222 5.2.5 实现多数据库的访问 223 5.3 常用经典SQL语句 224 5.4 事务处理 226 5.4.1 SQL和存储过程级别的事务 227 5.4.2 ADO.NET级别的事务 229 5.4.3 ASP.NET页面级别的事务 230 ...

    亮剑.NET深入体验与实战精要3

    5.2.4 实现SqlParameter方式 222 5.2.5 实现多数据库的访问 223 5.3 常用经典SQL语句 224 5.4 事务处理 226 5.4.1 SQL和存储过程级别的事务 227 5.4.2 ADO.NET级别的事务 229 5.4.3 ASP.NET页面级别的事务 230 ...

    内存管理内存管理内存管理

    内存管理内幕 dragonimp's blog coder.developer.[designer].ArchitecturE.manager.^_^... posts - 29, comments - 121, trackbacks - 27 My Links Home Contact ...文中将为您提供如何管理内存的细节,然后...

    操作系统(内存管理)

    然后,当通过 free() 将该指针传递回来时,我们只需要倒退几个内存字节就可以再次找到这个结构。 在讨论分配内存之前,我们将先讨论释放,因为它更简单。为了释放内存,我们必须要做的惟一一件事情就是,获得我们...

    入门学习Linux常用必会60个命令实例详解doc/txt

    建议在/mnt里建几个/mnt/cdrom、/mnt/floppy、/mnt/mo等目录,当作目录的专用挂载点。举例而言,如要挂载下列5个设备,其执行指令可能如下 (假设都是Linux的ext2系统,如果是Windows XX请将ext2改成vfat): 软盘 ==...

Global site tag (gtag.js) - Google Analytics