在前面两篇文章中分别介绍了了如何使用jstatd和JMX来配置VisualVM做一个监控。但生产环境中,往往是需要经过合法性的校验才允许创建链接的,之前的方式适合测试环境操作。在生成环境中,强烈建议使用SSL认证。SSL需要提供证书才能访问,安全性较高。
第一步:制作keystore 和truststore
由于SSL是一个非对称加密协议,因此会有public key 和 private key,而public key、private key是保存在keystore里面的。
下面主要讲下client 和 server 的keystore 、truststore制作流程。主要步骤是
1、生成client keystore ,从client keystore导出cer, 将 cer 导入到server truststore(生成server truststore)
2、生成server keystore ,从server keystore导出cer, 将 cer 导入到client truststore(生成client truststore)
具体流程
1、client keystore
格式:
keytool -genkeypair \
-alias <client名称> \
-keyalg RSA \
-validity 365 \
-storetype pkcs12 \
-keystore <client名称>.keystore \
-storepass <client keystore password> \
-keypass <同 client keystore password> \
-dname "CN=<姓名> OU=<组织下属单位> O=<组织> L=<城市> S=<省> C=<国家>"
示例:
keytool -genkeypair \
-alias visualvm \
-keyalg RSA \
-validity 365 \
-storetype pkcs12 \
-keystore visualvm.keystore \
-storepass 123456 \
-keypass 123456 \
-dname "CN=chihay OU=Alphalion O=dev L=GZ S=GD C=CN"
2、client cer
格式:
keytool -exportcert \
-alias <client name> \
-storetype pkcs12 \
-keystore <client name>.keystore \
-file <client name>.cer \
-storepass <client keystore password>
示例:
keytool -exportcert \
-alias visualvm \
-storetype pkcs12 \
-keystore visualvm.keystore \
-file visualvm.cer \
-storepass 123456
3、生成server truststore
格式:
keytool -importcert \
-alias <client name> \
-file <client name(上一步生成的文件名)>.cer \
-keystore <servier name>.truststore \
-storepass <servier truststore password> \
-noprompt
示例:
keytool -importcert \
-alias visualvm \
-file visualvm.cer \
-keystore visualvm-test-app.truststore \
-storepass 789012 \
-noprompt
4、server keystore(跟第一步格式是一样的)
格式:
keytool -genkeypair \
-alias <server名称> \
-keyalg RSA \
-validity 365 \
-storetype pkcs12 \
-keystore <server名称>.keystore \
-storepass <server keystore password> \
-keypass <同 server keystore password> \
-dname "CN=<姓名> OU=<组织下属单位> O=<组织> L=<城市> S=<省> C=<国家>"
示例:
keytool -genkeypair \
-alias visualvm-test-app \
-keyalg RSA \
-validity 365 \
-storetype pkcs12 \
-keystore visualvm-test-app.keystore \
-storepass 012345 \
-keypass 012345 \
-dname "CN=chihay OU=Alphalion O=dev L=GZ S=GD C=CN"
5、server cer (跟第二步格式一样)
格式:
keytool -exportcert \
-alias <server name> \
-storetype pkcs12 \
-keystore <server name>.keystore \
-file <server name>.cer \
-storepass <server keystore password>
示例:
keytool -exportcert \
-alias visualvm-test-app \
-storetype pkcs12 \
-keystore visualvm-test-app.keystore \
-file visualvm-test-app.cer \
-storepass 012345
6、client truststore(跟第三步格式一样)
格式:
keytool -importcert \
-alias <server name> \
-file <server name(上一步生成的server文件名)>.cer \
-keystore <client name>.truststore \
-storepass <client truststore password> \
-noprompt
示例:
keytool -importcert \
-alias visualvm-test-app \
-file visualvm-test-app.cer \
-keystore visualvm.truststore \
-storepass 678901 \
-noprompt
最终生成六个文件
第二步:启动server服务
启动命令(注意参数名称和内容不要写错,否则client会报JMX链接不上):
java -jar -Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=<port> \
-Dcom.sun.management.jmxremote.rmi.port=<同样port> \
-Djava.rmi.server.hostname=<ip> \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=true \
-Dcom.sun.management.jmxremote.registry.ssl=true \
-Dcom.sun.management.jmxremote.ssl.need.client.auth=true \
-Djavax.net.ssl.keyStore=<path server keyStore> \
-Djavax.net.ssl.keyStorePassword=<server keyStorePassword> \
-Djavax.net.ssl.trustStore=<path server trustStore> \
-Djavax.net.ssl.trustStorePassword=<server trustStorePassword> \
visualvmtest-0.0.1-SNAPSHOT.jar
示例:
java -jar -Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=2223 \
-Dcom.sun.management.jmxremote.rmi.port=2223 \
-Djava.rmi.server.hostname=192.168.124.18 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=true \
-Dcom.sun.management.jmxremote.registry.ssl=true \
-Dcom.sun.management.jmxremote.ssl.need.client.auth=true \
-Djavax.net.ssl.keyStore=/opt/visualvm-test/keystore/visualvm-test-app.keystore \
-Djavax.net.ssl.keyStorePassword=012345 \
-Djavax.net.ssl.trustStore=/opt/visualvm-test/keystore/visualvm-test-app.truststore \
-Djavax.net.ssl.trustStorePassword=789012 \
visualvmtest-0.0.1-SNAPSHOT.jar
第三步:启动client
如果这个时候直接打开VisualVM app,是链接不上server的。server会报错:
2020-09-07 09:40:01.332 WARN 3092 --- [MI TCP Accept-0] sun.rmi.transport.tcp : RMI TCP Accept-0: accept loop for ServerSocket[addr=0.0.0.0/0.0.0.0,localport=44883] throws
java.net.SocketException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: sun.security.ssl.SSLContextImpl$DefaultSSLContext)
at javax.net.ssl.DefaultSSLSocketFactory.throwException(SSLSocketFactory.java:248) ~[na:1.8.0_171]
at javax.net.ssl.DefaultSSLSocketFactory.createSocket(SSLSocketFactory.java:270) ~[na:1.8.0_171]
at sun.management.jmxremote.ConnectorBootstrap$SslServerSocket.accept(ConnectorBootstrap.java:992) ~[na:1.8.0_171]
at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.executeAcceptLoop(TCPTransport.java:405) [na:1.8.0_171]
at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.run(TCPTransport.java:377) [na:1.8.0_171]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_171]
Caused by: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: sun.security.ssl.SSLContextImpl$DefaultSSLContext)
at java.security.Provider$Service.newInstance(Provider.java:1617) ~[na:1.8.0_171]
at sun.security.jca.GetInstance.getInstance(GetInstance.java:236) ~[na:1.8.0_171]
at sun.security.jca.GetInstance.getInstance(GetInstance.java:164) ~[na:1.8.0_171]
at javax.net.ssl.SSLContext.getInstance(SSLContext.java:156) ~[na:1.8.0_171]
at javax.net.ssl.SSLContext.getDefault(SSLContext.java:96) ~[na:1.8.0_171]
at javax.net.ssl.SSLSocketFactory.getDefault(SSLSocketFactory.java:122) ~[na:1.8.0_171]
at sun.management.jmxremote.ConnectorBootstrap$SslServerSocket.getDefaultSSLSocketFactory(ConnectorBootstrap.java:1008) ~[na:1.8.0_171]
at sun.management.jmxremote.ConnectorBootstrap$SslServerSocket.accept(ConnectorBootstrap.java:990) ~[na:1.8.0_171]
... 3 common frames omitted
Caused by: java.security.PrivilegedActionException: null
at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_171]
at sun.security.ssl.SSLContextImpl$DefaultManagersHolder.getKeyManagers(SSLContextImpl.java:822) ~[na:1.8.0_171]
at sun.security.ssl.SSLContextImpl$DefaultManagersHolder.<clinit>(SSLContextImpl.java:758) ~[na:1.8.0_171]
at sun.security.ssl.SSLContextImpl$DefaultSSLContext.<init>(SSLContextImpl.java:913) ~[na:1.8.0_171]
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_171]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_171]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_171]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_171]
at java.security.Provider$Service.newInstance(Provider.java:1595) ~[na:1.8.0_171]
at sun.security.jca.GetInstance.getInstance(GetInstance.java:236) ~[na:1.8.0_171]
at sun.security.jca.GetInstance.getInstance(GetInstance.java:164) ~[na:1.8.0_171]
at javax.net.ssl.SSLContext.getInstance(SSLContext.java:156) ~[na:1.8.0_171]
at javax.net.ssl.SSLContext.getDefault(SSLContext.java:96) ~[na:1.8.0_171]
at javax.net.ssl.SSLSocketFactory.getDefault(SSLSocketFactory.java:122) ~[na:1.8.0_171]
at javax.rmi.ssl.SslRMIServerSocketFactory.getDefaultSSLSocketFactory(SslRMIServerSocketFactory.java:368) ~[na:1.8.0_171]
at javax.rmi.ssl.SslRMIServerSocketFactory.<init>(SslRMIServerSocketFactory.java:180) ~[na:1.8.0_171]
at javax.rmi.ssl.SslRMIServerSocketFactory.<init>(SslRMIServerSocketFactory.java:118) ~[na:1.8.0_171]
at javax.rmi.ssl.SslRMIServerSocketFactory.<init>(SslRMIServerSocketFactory.java:80) ~[na:1.8.0_171]
at sun.management.jmxremote.ConnectorBootstrap$HostAwareSslSocketFactory.<init>(ConnectorBootstrap.java:891) ~[na:1.8.0_171]
at sun.management.jmxremote.ConnectorBootstrap$HostAwareSslSocketFactory.<init>(ConnectorBootstrap.java:884) ~[na:1.8.0_171]
at sun.management.jmxremote.ConnectorBootstrap$HostAwareSslSocketFactory.<init>(ConnectorBootstrap.java:872) ~[na:1.8.0_171]
at sun.management.jmxremote.ConnectorBootstrap.createSslRMIServerSocketFactory(ConnectorBootstrap.java:649) ~[na:1.8.0_171]
at sun.management.jmxremote.ConnectorBootstrap.exportMBeanServer(ConnectorBootstrap.java:774) ~[na:1.8.0_171]
at sun.management.jmxremote.ConnectorBootstrap.startRemoteConnectorServer(ConnectorBootstrap.java:468) ~[na:1.8.0_171]
at sun.management.Agent.startAgent(Agent.java:262) ~[na:1.8.0_171]
at sun.management.Agent.startAgent(Agent.java:452) ~[na:1.8.0_171]
Caused by: java.io.FileNotFoundException: 012345 (没有那个文件或目录)
at java.io.FileInputStream.open0(Native Method) ~[na:1.8.0_171]
at java.io.FileInputStream.open(FileInputStream.java:195) ~[na:1.8.0_171]
at java.io.FileInputStream.<init>(FileInputStream.java:138) ~[na:1.8.0_171]
at java.io.FileInputStream.<init>(FileInputStream.java:93) ~[na:1.8.0_171]
at sun.security.ssl.SSLContextImpl$DefaultManagersHolder$2.run(SSLContextImpl.java:826) ~[na:1.8.0_171]
at sun.security.ssl.SSLContextImpl$DefaultManagersHolder$2.run(SSLContextImpl.java:823) ~[na:1.8.0_171]
... 26 common frames omitted
正确方式应该是在$JAVA_HOME/bin目录下,使用jvisualvm 并带上keyPassword 和truststorePassword启动 client。这时会直接打开一个客户端。这是一个使用证书创建的链接。在创建的时候选不选SSL都可以的。
jvisualvm -J-Djavax.net.ssl.keyStore=/Users/yang/Documents/file/keystore/visualvm.keystore \
-J-Djavax.net.ssl.keyStorePassword=123456 \
-J-Djavax.net.ssl.trustStore=/Users/yang/Documents/file/keystore/visualvm.truststore \
-J-Djavax.net.ssl.trustStorePassword=678901
总结:
1、生成keyStore 和 trustStore 参数要写对,password要对应的上
2、启动的时候,参数值要写对,这点比较坑,写错了参数是会报链接不上。所以出现链接不上的时候要先确认参数对了,再去看是不是防火墙端口这些问题导致的。比如正确的密码是678901,但是写错成6789012的话就不行了。还有路径也会导致这些问题。