️This article has been over 2 years since the last update.
JMX在实际开发中很多人都用过,鼠标点一下就可以执行Java代码。本文适用于已经会在JConsole等工具中调用JMX,但希望进一步了解JMX的人。
什么是JMX,有什么用?
JMX是 Java 管理扩展(Java Management Extensions, JSR 160)的缩写,实际上就是一个基于TCP的远程eval后门,是sun闭源的实现,在日常开发中,一般用于
- 热部署: 比如Log4j(更新Log等级), Script(比如Java内置js实现浅层定制等), Hibernate对象(数据库字段)等。这里可以搞出很多黑科技,提高开发效率
- 监控统计: 查看DCS缓存命中率,Zk健康,MQ流控,DDS热点等中间件的状态。
- 远程操作: 远程清空缓存,dump数据等,这个一般用得很少。
从鼠标点击到Bean被调用的端到端流程分析
下面以zkServer为例,Zookeeper提供了很多JMX调试接口,在Jconsole都是可以直接点击的。
为了方便折腾Zk,我们首先下载zk的源码工程,然后用IDEA导入后,配置如下Debug的参数,然后点击启动即可
1 | Main class: org.apache.zookeeper.server.quorum.QuorumPeerMain |
接着在ZooKeeperServerBean
中的resetStatistics
打上断点。然后打开Jconsole,找到此Bean,点击此方法运行。
没有意外地话,断点应该被Trigger上了,这里我们就可以通过调用栈进行简要分析了。
通过对调用栈分析,我们可以发现
- JVM专门开了一个RMI(Java Method Invocation)的线程进行监听,并自己通过TCP搞了一套协议(也就是说可以用WireShark抓包),通过对TCP的InputStream进行查看,可以发现JMX实现了自己的应用层抽象语法。
- 通过一个大for/switch实现了路由匹配,并通过一个Map找到Method进行invoke调用:
javax.management.remote.rmi.RMIConnection.invoke()
- 最终路由到实际Bean,并执行Java代码。
可以看出,JMX主要是在做传输协议,更像是在做【通信】,这部分具体分析工作量非常大,本文就不分析了。
如何开发自己第一个JMX?
我个人不太喜欢从零开始创建一个工程,而是喜欢先定制,再移植出公共模块(详见我之前写的团队技能培训相关Tag)。同样以Zookeeper为例,我们可以按照如下方法进行折腾:
- 写一个扩展ZooKeeperServerBean的class,并注册到zk上
- 尝试添加属性与方法到刚刚写的class上
- 删除extend的类,尝试自己实现MBean接口造轮子
这样步骤有一个好处就是,你最开始学JMX就接触到的是行业水平最高的实践,比如MBeanRegistry
的单例化,ObjectName
的生成方法,JConsole对应Class的直观映射,都是很好的封装。
经过折腾,例子如下
1 | // 接口必须是 MXBean 的后缀 |
实现类
1 | public class CalcBean implements CalcMXBean{ |
然后我们再进行注册就搞定了
1 | //如下代码放到 org.apache.zookeeper.server.ZooKeeperServer#registerJMX |
这样我们就成功地设计了一个简陋的JMX计算器,由于它可以eval任何js脚本,因此再加一些黑科技就相当于是一个后门了(生产环境绝不能这样搞)。
总结
JMX并不是什么非常专业高深的技术,因为它在各种框架中早被高度封装,它主要代码量体现在闭源的【通信】代码上。因此作为开发,只需要合理地暴露接口即可
- 只要写一个
/.*MXBean/
的接口与其实现类,并配置好ObjectName,就可以注册到JMX中,然后通过JConsole或者MBeanServerConnection
进行invoke - JMX与REST服务类似,只是一个桩,代码实现应该是调用Spring或者微服务,而不是全部写到MXBean中
- 本文没有讲get/set属性,这个可以下来试一下,本文实验入门成功后就可以直接看文档了
开发JMX业务需要注意什么?
JMX提供了直接调用Java代码的能力,因此非常方便开发定位,同时也有极大的安全风险
- 业务要满足安全要求: 比如不能接收String去eval,不能跳过鉴权去操作业务,最好不要返回任何业务信息,而只返回一个统计。这类代码可以私下调试用,但是不能上代码库,更不能说去配置一个开关在生产环境上屏蔽后门。也就是说,最终发布的二进制不能有任何有安全风险的JMX调用。
- 在Product环境中要关闭JMX: 这个一般在setEnv.sh等类似文件中均可以配置,如果开发有调试需求,可以配置鉴权的Key,并只监听Localhost,避免在公网暴露。
- 当前的TCP实现是sun的闭源实现,如果对安全性要求极高的企业级应用,需要自己实现定制私有协议的JMX(jmx.default.class.loader)。