谁说ParameterMap只能读不能写?
开发过javaweb项目的同学,应该都接触过ServeltRequest
吧?ServletRequest接口中有一个方法叫做getParameterMap()
,他会返回一个Map<String, String[]>
对象,里面含有Request的请求参数,例如GET请求时?后边的一堆参数。那如果我们能修改Map<String, String[]>
对象,岂不是能篡改浏览器请求时的一些参数?
1 ParameterMap
1.1 ServletRequest接口
服务器能从ServletRequest中篡改浏览器请求的参数?想想都令人兴奋,我们又多了一个可以个性化的地方。然而实际上是不可以的,我们来看看ServeltRequest
中getParameterMap()
方法的注释吧。
/** * Returns a java.util.Map of the parameters of this request. Request * parameters are extra information sent with the request. For HTTP * servlets, parameters are contained in the query string or posted form * data. * * @return an immutable java.util.Map containing parameter names as keys and * parameter values as map values. The keys in the parameter map are * of type String. The values in the parameter map are of type * String array. */ public Map<String, String[]> getParameterMap();
人家说了,返回的这个Map对象一定是不可变的。所以呢,就死了这条心吧。咱们还是看看tomcat中ServletRequest
的实现类里面到底是怎么构造不可变的Map。
1.2 Tomcat中的Request实现类
注意,以下凡是没有特殊说明的tomcat,其版本都是7.0.52
tomcat中ServletRequest
的实现类是org.apache.catalina.connector.Request
。在这个实现类中,方法getParameterMap()
是这样实现的。
/** * Returns a <code>Map</code> of the parameters of this request. * Request parameters are extra information sent with the request. * For HTTP servlets, parameters are contained in the query string * or posted form data. * * @return A <code>Map</code> containing parameter names as keys * and parameter values as map values. */ @Override public Map<String, String[]> getParameterMap() { if (parameterMap.isLocked()) { return parameterMap; } Enumeration<String> enumeration = getParameterNames(); while (enumeration.hasMoreElements()) { String name = enumeration.nextElement(); String[] values = getParameterValues(name); parameterMap.put(name, values); } parameterMap.setLocked(true); return parameterMap; }
如果属性parameterMap是上锁的,就返回这个属性。否则填充这个属性,然后上锁,返回属性。我就纳闷了,这个parameterMap高端啊,怎么就有一个判断是否上锁的方法,还有,这个属性在对象生成的时候已经做了初始化,所以它才可以直接调用这个属性的方法。
带着这些疑问,咱们来看看这个属性的初始化,其实很容易就能找到。
/** * Hash map used in the getParametersMap method. */ protected ParameterMap<String, String[]> parameterMap = new ParameterMap<>();
它就是在对象创建的时候创建了ParameterMap
类型的对象。
1.3 ParameterMap类
这个org.apache.catalina.util.ParameterMap
类,看来我们得重点关注了。打开这个类,我们发现它其实就是一个代理类,里边包含一个private final Map<K,V> delegatedMap;
属性,其次,还有一个private boolean locked = false;
。看到这里大家可能就明白了,无非是在做增删改操作的时候,先判断有没有锁,再执行操作,如果有锁,就抛出异常。
2 奇特的ApplicationHttpRequest
2.1 ApplicationHttpRequest
其实,上一小结已经点明,ServletRequest声明返回的Map是不可修改的,tomcat里也做到了不可修改。我们以后使用的时候注意一下就行,别自作聪明修改ParameterMap里的属性。
但是笔者是个较真的人,利用IDE,笔者也看到了别的实现类,其中org.apache.catalina.core.ApplicationHttpRequest
引起了笔者的注意。这个类翻译成中文就是应用级别的HTTP请求,那他有什么特殊点呢?它实际上也是一个代理类,里面包含类实际的Request对象,来看他的getParameterMap()
方法。
/** * Override the <code>getParameterMap()</code> method of the * wrapped request. */ @Override public Map<String, String[]> getParameterMap() { parseParameters(); return (parameters); } /** * Parses the parameters of this request. * * If parameters are present in both the query string and the request * content, they are merged. */ void parseParameters() { if (parsedParams) { return; } parameters = new HashMap<String, String[]>(); parameters = copyMap(getRequest().getParameterMap()); mergeParameters(); parsedParams = true; } /** * Perform a shallow copy of the specified Map, and return the result. * * @param orig Origin Map to be copied */ Map<String, String[]> copyMap(Map<String, String[]> orig) { if (orig == null) return (new HashMap<String, String[]>()); HashMap<String, String[]> dest = new HashMap<String, String[]>(); for (Map.Entry<String, String[]> entry : orig.entrySet()) { dest.put(entry.getKey(), entry.getValue()); } return (dest); }
这三个方法依次看下来,org.apache.catalina.util.ParameterMap
毛的没见到,只有HashMap,啥情况?,tomcat怎么留了这么一个口子?他是干什么用的?什么时候我们的程序能得到这个Request?
2.2 ApplicationDispatcher
带着这个疑问,笔者又深入的搜寻类一番,发现org.apache.catalina.core.ApplicationDispatcher
中在方法forward()和方法include()里对原始的Request包装上了ApplicationHttpRequest
。
这个ApplicationDispatcher实际上实现了javax.servlet.RequestDispatcher.RequestDispatcher
,而RequestDispatcher的作用是转发或者包含别的资源,例如JSP,Servlet。
说了,那么多,那到底怎么用呢?实际上ServletRequest有一个方法能够获取RequestDispatcher,然后再调用RequestDispatcher的forward或者include方法。
3 一个简单的实验
3.1 说明
笔者做了一个简单的实验,先说一下实验内容,在Controller的方法中,获取ParameterMap,然后给浏览器中显示它的类型。怎么对比呢?`
/forward0
直接获取ParameterMap类型/forward1
调用forward转发请求到/forward3
/forward2
调用include包含请求到/forward3
/forward4
(没有对应的RequestMapping)在Filter中调用forward转发请求到/forward3
/forward5
(没有对应的RequestMapping)在Filter中调用include包含请求到/forward3
/forward6
调用forward转发请求到/forward6
,注意这个只会调用一次,否则会进入死循环
3.2 代码
来看看Controller和Filter
ForwardController.java
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; @Controller public class ForwardController { @RequestMapping("/forward0") @ResponseBody public String forward0(ServletRequest request, ServletResponse response) { return request.getParameterMap().getClass().getCanonicalName(); } @RequestMapping("/forward1") public void forward1(ServletRequest request, ServletResponse response) throws ServletException, IOException { RequestDispatcher rd = request.getRequestDispatcher("/forward3"); rd.forward(request, response); } @RequestMapping("/forward2") public void forward2(ServletRequest request, ServletResponse response) throws ServletException, IOException { RequestDispatcher rd = request.getRequestDispatcher("/forward3"); rd.include(request, response); } @RequestMapping("/forward3") @ResponseBody public String forward3(ServletRequest request, ServletResponse response) { return request.getParameterMap().getClass().getCanonicalName(); } @RequestMapping("/forward6") @ResponseBody public String forward6(ServletRequest request, ServletResponse response) { return request.getParameterMap().getClass().getCanonicalName(); } }
ForwardFilter.java
package com.gavinzh.learn.web.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class ForwardFilter implements Filter{ public void init(FilterConfig filterConfig) throws ServletException { } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest hsr = (HttpServletRequest) servletRequest; if (hsr.getAttribute("ForwardFilter") ==null){ hsr.setAttribute("ForwardFilter",ForwardFilter.class); if (hsr.getRequestURI().equals("/forward4")){ RequestDispatcher rd = servletRequest.getRequestDispatcher("/forward3"); rd.forward(servletRequest,servletResponse); return; } if (hsr.getRequestURI().equals("/forward5")){ RequestDispatcher rd = servletRequest.getRequestDispatcher("/forward3"); rd.include(servletRequest,servletResponse); return; } if (hsr.getRequestURI().equals("/forward6")){ RequestDispatcher rd = servletRequest.getRequestDispatcher("/forward6"); rd.forward(servletRequest,servletResponse); return; } filterChain.doFilter(servletRequest,servletResponse); } filterChain.doFilter(servletRequest,servletResponse); } public void destroy() { } }
3.3 最终结果
上述代码怎么组织运行,笔者就不细讲了,网上例子很多。结果我来展示一下:
/forward0
:org.apache.catalina.util.ParameterMap/forward1
:java.util.HashMap/forward2
:java.util.HashMap/forward4
:java.util.HashMap/forward5
:java.util.HashMap/forward6
:java.util.HashMap
神奇不神奇?一个javaEE标准声明了是不可变对象,在这个实验里变成了可变对象。
4 奇袭ApplicationHttpRequest
话说到这里,大家对getParameterMap方法也有了个简单了解,如果想改变Map对象的K-V,你就搞一个转发请求。喝一杯咖啡,优雅地实现一些奇怪的逻辑。
不过笔者没有就此罢手,手贱登上了github,看了一下tomcat项目中的这个类。这个类代码的HashMap竟然被替代成了ParameterMap!!!
我就纳闷类,是谁改了这个bug?导致我不能利用这个bug做一些邪恶的事情。
当当当当,blame一下,找到了,ApplicationHttpRequest修改记录,是一个年轻小伙子16年左右修复了这个bug。Tomcat7.0.68版本,Tomcat8.0.14版本开始,这个bug被修复了。
是不是很气人,原先这个功能用的好好的,升了级竟然用不了了。
生气生气生气😠😠😠,怎么办怎么办怎么办,我想同学们已经有办法了。那就是反射ParameterMap,射射射,把locked属性,设置为可访问,然后将locked设置成false。
5 总结
笔者在这里和大家分享了一个小功能,小bug,耽误了大家的一些时间。但上边这些内容完全是笔者在生产开发中遇到的一些问题,笔者以有趣的方式来展示这些问题,以期和大家深入地探讨技术。
总结一下吧,ParameterMap这个Map是不可变的,建议大家还是别打这个对象的主意。为什么?javaEE标准里说了它是不可变的,那么各大Servlet容器厂商自然会以不同地方式实现这个不可变Map,今天你可以修改locked,明天一升级,人家改叫isLocked,那你的代码还能正常运行吗?
那有没有别的方式我们可以让它可变?有的,你写一个filter,在里面对request做一个包装,在getParameterMap时候,返回一个HashMap就可以了。
有趣吧?从可以修改ParameterMap到不能修改,到可以修改,再到建议不要修改,再到可以修改。每一步都是精华呀。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
对于Ping的过程,你真的了解吗?
一、概览 对于ping命令,想必只要是程序员都知道吧?当我们检查网络情况的时候,最先使用的命令肯定是ping命令吧?一般我们用ping查看网络情况,主要是检查两个指标,第一个是看看是不是超时,第二个看看是不是延迟太高。如果超时那么肯定是网络有问题啦(禁ping情况除外),如果延迟太高,网络情况肯定也是很糟糕的。那么对于ping命令的原理,ping是如何检查网络的?大家之前有了解吗?接下来我们来跟着ping命令走一圈,看看ping是如何工作的。 二、环境准备和抓包 2.1 环境准备 1.抓包工具。我这里使用Wireshark。 2.我准备了两台电脑,进行ping的操作。 ip地址分别为: A电脑:192.168.2.135 mac地址:98:22:EF:E8:A8:87 B电脑:192.168.2.179 MAC:90:A4:DE:C2:DF:FE 2.2 抓包操作 打开 Wireshark,选取指定的网卡进行抓包,进行ping操作,在A电脑上ping B电脑的ip 图a 抓包情况如下: 图b 这里先简单的介绍下Wireshark的控制面板,这个面板包含7个字段,分别是: NO.编号 T...
- 下一篇
分布式理论:深入浅出Paxos算法
前言 Paxos算法是用来解决分布式系统中,如何就某个值达成一致的算法。它晦涩难懂的程度完全可以跟它的重要程度相匹敌。目前关于paxos算法的介绍已经非常多,但大多数是和稀泥式的人云亦云,却很少有人能对提出自己的见解。本文试图从不一样的角度来对Paxos made simple的论文进行解释,而不仅仅是对论文的拙劣翻译,希望即使没有看过论文的同学也能看懂。 一致性问题 为了实现集群的高可用性,用户的数据往往要多重备份,多个副本虽然避免了单点故障,但同时也引入了新的挑战。 假设有一组服务器保存了用户的余额,初始是100块,现在用户提交了两个订单,一个订单是消费10元,一个订单是充值50元。由于网络错误和延迟等原因,导致一部分服务器只收到了第一个订单(余额更新为90元),一部分服务器只收到了第二个订单(余额更新为150元),还有一部分服务器两个订单都接收到了(余额更新为140元),这三者无法就最终余额达成一致。这就是一致性问题。 一致性算法并不保证所有提出的值都是正确的(这可能是安全管理员的职责)。我们假设所有提交的值都是正确的,算法需要对到底该选哪个做出决策,并使决策的结果被所有参与者获...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程