`
antxman
  • 浏览: 40178 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JMX实践-JMX连接端口

 
阅读更多
【转】JMX实践-JMX连接端口
首先要从JMX URL说起。如下是一个典型的JMX URL:
service:jmx:rmi://localhost:5000/jndi/rmi://localhost:6000/jmxrmi
这个JMX URL可以分为如下几个部分:
service:jmx: 这个是JMX URL的标准前缀,所有的JMX URL都必须以该字符串开头。
rmi: 这个是connector server的传输协议,在这个url中是使用rmi来进行传输的。JSR 160规定了所有connector server都必须至少实现rmi传输,是否还支持其他的传输协议依赖于具体的实现。比如MX4J就支持soap、soap+ssl、hessian、burlap等等传输协议。
localhost:5000: 这个是connector server的IP和端口,该部分是一个可选项,可以被省略掉。(具体参见下文解释)
/jndi/rmi://localhost:6000/jmxrmi: 这个是connector server的路径,具体含义取决于前面的传输协议。比如该URL中这串字符串就代表着该connector server的stub是使用jndi api绑定在rmi://localhost:6000/jmxrmi这个地址。
如果在服务器端,我们用该URL创建一个connector server,则大概流程如下:
将connect server(javax.management.remote.rmi.RMIConnectorServer)内部的server对象(javax.management.remote.rmi.RMIJRMPServerImpl)的rmi stub export到本地的5000端口,接收外部连接
通过jndi api将该stub绑定在rmi://localhost:6000/jmxrmi这个地址上,这需要在本地的6000端口上运行着一个rmiregistry,如果不存在则会抛出异常
......
可以看到,如果在服务器端创建connector server时,该URL的第三部分(即localhost:5000)如果省略的话,则connector server会随机任意选择一个可用的端口。
如果在客户端,我们通过该URL创建一个connector,则大概按照如下的流程:
通过jndi api到rmi://localhost:6000/jmxrmi这个地址取回stub
stub中已经包含了真实服务器的地址,所以可以直接根据该stub连接到真实的服务器
......
可以看到,如果在客户端创建connector时,该URL的第三部分可以被省略掉。事实上我们也经常这么做,比如上面这个示例,在客户端建立connector所用的JMX URL可以被省略为:
service:jmx:rmi:///jndi/rmi://localhost:6000/jmxrmi
从这个示例中我们也可以看到,如果采用rmi作为传输协议的话,客户端需要进行两个连接。首先客户端连接到rmiregistry上得到真实服务器的stub(如rmi://localhost:6000/rmxrmi),然后客户端再根据该stub连接到真实的服务器上(如rmi://localhost:5000)。
在Sun Java 5.0的实现当中,可以通过指定运行期属性来配置JMX。比如:
-Dcom.sun.management.jmxremote.port=6000
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.password.file=conf/jmxremote.password
-Dcom.sun.management.jmxremote.access.file=conf/jmxremote.access
除开密码和权限控制的配置外,这段运行期相当于一个如下的JMX URL:
service:jmx:rmi://localhost:0/jndi/rmi://localhost:6000/jmxrmi (红色部分可以被省略掉)
可以看到,通过指定com.sun.management.jmxremote.port属性,相当于指定了rmiregistry的运行端口,但是真正运行的服务器的stub export端口为0,会在运行期任意选择一个未被使用的端口号。如果该程序运行在防火墙后面,这个动态分配的端口号会让我们非常难以配置防火墙规则。
由于Sun Java 5.0实现的疏忽,并没有为我们提供一个运行期属性来配置stub export端口号,那么如何自定义一个JMX连接端口呢?
假设我们不传入任何运行期参数,仅通过代码来自定义一个connector server:
LocateRegistry.createRegistry(6000);
JMXConnectorServer cs = JMXConnectorServerFactory
                    .newJMXConnectorServer(url, env, beanServer);
cs.start();
第一行代码指明在6000端口创建一个rmiregistry。
第二行代码创建了一个connector server。第一个URL参数传入指定的JMXServiceURL,比如new JMXServiceURL("service:jmx:rmi://localhost:5000/jndi/rmi://localhost:6000/jmxrmi"),值得注意的是红色部分标出的IP和端口号要和第一步中创建的rmiregistry一致。第二个参数指定创建时的环境,我们可以暂时传入一个空Map。第三个参数指定要创建连接的MBeanServer,比如在Java 5.0下可以是ManagementFactory.getPlatformMBeanServer()。
第三行代码开启了创建的connector server。
这三行代码创建的connector server工作的很好,除了一点:真实的服务器没有任何认证和授权机制,会允许所有人访问。
自定义权限控制可以通过刚才第二行代码的第二个参数来进行设置:
Map env = new HashMap();
env.put(JMXConnectorServer.AUTHENTICATOR, JMXAuthenticator子类的实例);
比如在MX4J中就提供了一个mx4j.tools.remote.PasswordAuthenticator的类,通过配置文件来匹配用户名和密码;Sun也提供了一个com.sun.jmx.remote.security.JMXPluggableAuthenticator的类通过JAAS来配置验证和权限。用户可以参照这两个类来提供自定义的实现。这里提供一个简单的示例(不带任何效验机制,仅用于示例)
           env.put(JMXConnectorServer.AUTHENTICATOR, new JMXAuthenticator() {
                public Subject authenticate(Object credentials) {
                    String[] sCredentials = (String[]) credentials;
                    String userName = sCredentials[0];
                    String password = sCredentials[1];
                    if ("test".equals(userName) && "test".equals(password)) {
                        Set principals = new HashSet();
                        principals.add(new JMXPrincipal(user));
                        return new Subject(true, principals, Collections.EMPTY_SET,
                            Collections.EMPTY_SET);
                    }
                    throw new SecurityException("Authentication failed! "
                            + message);
                }
            });
加入这段代码后,真实的服务器必须要验证后才能访问,但又有了一个新问题:rmiregistry没有任何验证。这意味着其他人可以把一个不相干的stub绑定到同一地址,从而替换掉真实服务器stub。好在幸运的是,无法把一个服务器端类路径上不存在的stub类绑定到rmiregistry,否则真实的用户名和密码就有可能被泄漏出去。
如果直接通过rmi对外提供的接口,我们没有办法修正这个问题,除非将代码绑定于特定的Java实现,比如Sun就直接通过它自己的rmi实现类来做(所以通过运行期参数配置jmx生成的rmiregistry是只读的,没有该安全性问题)。具体可参见sun.management.Agent和sun.management.jmxremote包下相关类。
但是我们可以换一个思路,比如不绑定到rmiregistry而绑定到其他的实现了验证机制的jndi provider,例如ldap上。由于bind jndi的代码在相关connector server的实现类中,比如javax.management.remote.rmi.RMIConnectorServer中bind jndi的代码:
void bind(String jndiUrl, Hashtable attributes,
              RMIServer rmiServer, boolean rebind)
        throws NamingException, MalformedURLException {
        // if jndiURL is not null, we nust bind the stub to a
        // directory.
        InitialContext ctx =
            new InitialContext(attributes);
       
        if (rebind)
            ctx.rebind(jndiUrl, rmiServer);
        else
            ctx.bind(jndiUrl, rmiServer);
        ctx.close();
    }
它在传入attributes时并没有特定的jndi的INITIAL_CONTEXT_FACTORY,所以我们可以通过两种方法来指定特定的INITIAL_CONTEXT_FACTORY:
在类路径上放上一个jndi.properties,配置相应的jndi属性
在start connector server前,先在系统属性中指定相应的jndi属性,start后再把这些属性恢复成原有属性
其中第一种方式不需要额外的代码,但是可能会对系统中其他jndi的InitialContext产生影响;第二种方式稍微麻烦点,但是在实现上比较完美。
值得提到的是,如果采用rmiregistry,并且rmiregistry和server在同一个JVM内,stub和rmiregistry可以共享一个端口。比如可以使用如下的JMX URL:
service:jmx:rmi://localhost:6000/jndi/rmi://localhost:6000/jmxrmi
但注意一定要在同一JVM内,比如先用LocateRegistry.createRegistry(6000)创建本地rmiregistry,再通过该URL开启connector server,则可以共享6000端口。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics