如何强制 tomcat 重新加载受信任的证书?

发布于 2024-11-04 03:54:51 字数 1466 浏览 1 评论 0原文

我的 Web 应用程序使用 2 向 SSL 连接器(又名“客户端身份验证”):

<Connector port="8084" SSLEnabled="true" maxThreads="10" minSpareThreads="3" maxSpareThreads="5"
             enableLookups="false" disableUploadTimeout="true" acceptCount="100" scheme="https" secure="true"
             clientAuth="true" truststoreFile="conf/keystore.kst" truststoreType="JCEKS" sslProtocol="TLS" URIEncoding="UTF-8"
             keystoreFile="conf/keystore.kst" keystoreType="JCEKS" keyAlias="myAlias"
             ciphers="TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,SSL_RSA_WITH_3DES_EDE_CBC_SHA"/>

我的问题是,当 Tomcat 服务器正在运行并且我使用新的受信任证书更新密钥库,甚至从中删除受信任证书时,连接器不会“注意到这些变化。

到目前为止我已经尝试过:

1)停止、重新初始化(反射)和启动连接器 - 不起作用。

2) 实现我自己的 SSLContext,从密钥库重新加载证书。 好吧,这里我错过了向 tomcat 注册此 SSLContext 的部分(以便 tomcat 将在连接器中使用它来进行新的传入连接)

有很多关于此问题的帖子,但没有真正的解决方案:

http://www.delphifaq.com/faq/f5003.shtml

http://jcalcote.wordpress.com/tag/truststore
(本文仅描述如何从客户端重新创建 SSLcontext(缺少服务器端))

有什么想法吗?

还有另一个相关问题:

如何强制 Tomcat Web 应用程序在更新后重新加载信任存储

但答案还不够,因为我不想构建新的类加载器。

谢谢。

My WebApp uses a Connector for 2-Way SSL (aka "Client Authentication"):

<Connector port="8084" SSLEnabled="true" maxThreads="10" minSpareThreads="3" maxSpareThreads="5"
             enableLookups="false" disableUploadTimeout="true" acceptCount="100" scheme="https" secure="true"
             clientAuth="true" truststoreFile="conf/keystore.kst" truststoreType="JCEKS" sslProtocol="TLS" URIEncoding="UTF-8"
             keystoreFile="conf/keystore.kst" keystoreType="JCEKS" keyAlias="myAlias"
             ciphers="TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,SSL_RSA_WITH_3DES_EDE_CBC_SHA"/>

My problem is that while the Tomcat server is running and I update the keystore with new trusted certifictaes , or even delete trusted certificates from it , the connector doesn't notice the changes.

What I've tried so far:

1) Stopping , Re-Initializing (reflection) and starting the Connector - didn't work.

2) Implementing my own SSLContext that reloads the certificates from the keystore.
Well , here I'm missing the part of registering this SSLContext with tomcat (so that tomcat will use it in the connector for new incoming connections)

There are many posts on this matter but no real solution:

http://www.delphifaq.com/faq/f5003.shtml

http://jcalcote.wordpress.com/tag/truststore
(This article describes only how to recreate SSLcontext from the client side (missing the server side))

Any Ideas?

There's another related question :

How do I force a tomcat web application reload the trust store after I update it

but the answer there is not sufficient since I don't want to build a new ClassLoader.

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

沐歌 2024-11-11 03:54:51

现在从 Tomcat v8.5.24 开始有一个解决方案。

他们引入了两种方法:
reloadSslHostConfig(String hostName) - 重新加载特定主机
reloadSslHostConfigs() - 重新加载所有

它们可以通过多种方式调用:

  1. 使用 jmx
  2. 使用管理器服务
  3. 通过制定自定义协议 - 我在研究过程中发现了这种方式

方式 1 和方式 2 的详细信息可以轻松在线获得。

如何使用方法 3 的详细信息:

  1. 创建一个扩展您选择的协议的类,例如。 Http11NioProtocol
  2. 覆盖所需的方法,只在其中调用 super 以保持默认行为
  3. 在此类中创建一个线程,以时不时地调用 reloadSslHostConfigs 方法 将
  4. 此类打包在 jar 中,并将该 jar 放入 tomcat 的 lib 文件夹
  5. 在 server.xml 中的连接器中编辑协议使用此自定义协议

查找下面的示例代码:

主协议类:

    package com.myown.connector;

    import java.io.File;
    import java.io.InputStream;
    import java.lang.reflect.Field;
    import java.net.URL;
    import java.net.URLConnection;
    import java.nio.file.StandardCopyOption;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ConcurrentMap;

    import javax.management.MalformedObjectNameException;
    import javax.management.ObjectName;
    import javax.net.ssl.SSLSessionContext;

    import org.apache.coyote.http11.Http11NioProtocol;
    import org.apache.juli.logging.Log;
    import org.apache.juli.logging.LogFactory;
    import org.apache.tomcat.util.modeler.Registry;
    import org.apache.tomcat.util.net.AbstractEndpoint;
    import org.apache.tomcat.util.net.AbstractJsseEndpoint;
    import org.apache.tomcat.util.net.GetSslConfig;
    import org.apache.tomcat.util.net.SSLContext;
    import org.apache.tomcat.util.net.SSLHostConfig;
    import org.apache.tomcat.util.net.SSLHostConfigCertificate;
    import org.apache.tomcat.util.net.SSLImplementation;
    import org.apache.tomcat.util.net.SSLUtil;

    public class ReloadProtocol extends Http11NioProtocol {

        private static final Log log = LogFactory.getLog(Http12ProtocolSSL.class);

        public ReloadProtocol() {
            super();
            RefreshSslConfigThread refresher = new 
                  RefreshSslConfigThread(this.getEndpoint(), this);
            refresher.start();
        }

        @Override
        public void setKeystorePass(String s) {
            super.setKeystorePass(s);
        }

        @Override
        public void setKeyPass(String s) {
            super.setKeyPass(s);
        }

        @Override
        public void setTruststorePass(String p) {
            super.setTruststorePass(p);
        }

        class RefreshSslConfigThread extends Thread {

            AbstractJsseEndpoint<?> abstractJsseEndpoint = null;
            Http11NioProtocol protocol = null;

            public RefreshSslConfigThread(AbstractJsseEndpoint<?> abstractJsseEndpoint, Http11NioProtocol protocol) {
                this.abstractJsseEndpoint = abstractJsseEndpoint;
                this.protocol = protocol;
            }

            public void run() {
                int timeBetweenRefreshesInt = 1000000; // time in milli-seconds
                while (true) {
                    try {
                            abstractJsseEndpoint.reloadSslHostConfigs();
                            System.out.println("Config Updated");
                    } catch (Exception e) {
                        System.out.println("Problem while reloading.");
                    }
                    try {
                        Thread.sleep(timeBetweenRefreshesInt);
                    } catch (InterruptedException e) {
                        System.out.println("Error while sleeping");
                    }
                }
            }
       }
}

server.xml 中的连接器应将其作为协议提及:

<Connector protocol="com.myown.connector.ReloadProtocol"
 ..........

希望这会有所帮助。

There is now a solution to this starting with Tomcat v8.5.24.

They introduced 2 methods named:
reloadSslHostConfig(String hostName) - to reload a specific host
reloadSslHostConfigs() - reload all

They can be called in various ways:

  1. Using jmx
  2. Using manager service
  3. By making custom protocol - I found this way during my research

Details of way 1 and way 2 are easily available online.

Details of how to go about using way 3:

  1. Make a class extending the protocol of your choice for eg. Http11NioProtocol
  2. Override the required methods and just call super in them to keep default behavior
  3. Make a thread in this class to call reloadSslHostConfigs method time to time
  4. Package this class in a jar and put that jar in tomcat's lib folder
  5. Edit protocol in connector in server.xml to use this custom defined protocol

Find sample code below:

Main protocol class:

    package com.myown.connector;

    import java.io.File;
    import java.io.InputStream;
    import java.lang.reflect.Field;
    import java.net.URL;
    import java.net.URLConnection;
    import java.nio.file.StandardCopyOption;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ConcurrentMap;

    import javax.management.MalformedObjectNameException;
    import javax.management.ObjectName;
    import javax.net.ssl.SSLSessionContext;

    import org.apache.coyote.http11.Http11NioProtocol;
    import org.apache.juli.logging.Log;
    import org.apache.juli.logging.LogFactory;
    import org.apache.tomcat.util.modeler.Registry;
    import org.apache.tomcat.util.net.AbstractEndpoint;
    import org.apache.tomcat.util.net.AbstractJsseEndpoint;
    import org.apache.tomcat.util.net.GetSslConfig;
    import org.apache.tomcat.util.net.SSLContext;
    import org.apache.tomcat.util.net.SSLHostConfig;
    import org.apache.tomcat.util.net.SSLHostConfigCertificate;
    import org.apache.tomcat.util.net.SSLImplementation;
    import org.apache.tomcat.util.net.SSLUtil;

    public class ReloadProtocol extends Http11NioProtocol {

        private static final Log log = LogFactory.getLog(Http12ProtocolSSL.class);

        public ReloadProtocol() {
            super();
            RefreshSslConfigThread refresher = new 
                  RefreshSslConfigThread(this.getEndpoint(), this);
            refresher.start();
        }

        @Override
        public void setKeystorePass(String s) {
            super.setKeystorePass(s);
        }

        @Override
        public void setKeyPass(String s) {
            super.setKeyPass(s);
        }

        @Override
        public void setTruststorePass(String p) {
            super.setTruststorePass(p);
        }

        class RefreshSslConfigThread extends Thread {

            AbstractJsseEndpoint<?> abstractJsseEndpoint = null;
            Http11NioProtocol protocol = null;

            public RefreshSslConfigThread(AbstractJsseEndpoint<?> abstractJsseEndpoint, Http11NioProtocol protocol) {
                this.abstractJsseEndpoint = abstractJsseEndpoint;
                this.protocol = protocol;
            }

            public void run() {
                int timeBetweenRefreshesInt = 1000000; // time in milli-seconds
                while (true) {
                    try {
                            abstractJsseEndpoint.reloadSslHostConfigs();
                            System.out.println("Config Updated");
                    } catch (Exception e) {
                        System.out.println("Problem while reloading.");
                    }
                    try {
                        Thread.sleep(timeBetweenRefreshesInt);
                    } catch (InterruptedException e) {
                        System.out.println("Error while sleeping");
                    }
                }
            }
       }
}

Connector in server.xml should mention this as the protocol:

<Connector protocol="com.myown.connector.ReloadProtocol"
 ..........

Hope this helps.

国产ˉ祖宗 2024-11-11 03:54:51

如果您的连接器将 bindOnInit 属性设置为 false(从 Tomcat 6.x 开始存在),则:

控制何时绑定连接器使用的套接字。默认情况下它
连接器启动时绑定,连接器解除绑定时
连接器损坏。如果设置为 false,则套接字将在以下情况下绑定
连接器启动并在停止时解除绑定。

来自 org.apache.tomcat.util.net.AbstractEndpoint Tomcat 8.0.29 的代码片段:

public final void start() throws Exception {
    if (bindState == BindState.UNBOUND) {
        bind();
        bindState = BindState.BOUND_ON_START;
    }
    startInternal();
}

public final void stop() throws Exception {
    stopInternal();
    if (bindState == BindState.BOUND_ON_START) {
        unbind();
        bindState = BindState.UNBOUND;
    }
}

然后您可以停止 &更新密钥和信任存储后,通过 JMX 启动连接器。

In case your connector has the bindOnInit property set to false (exist starting Tomcat 6.x), which:

Controls when the socket used by the connector is bound. By default it
is bound when the connector is initiated and unbound when the
connector is destroyed. If set to false, the socket will be bound when
the connector is started and unbound when it is stopped.

Code snippets from org.apache.tomcat.util.net.AbstractEndpoint Tomcat 8.0.29:

public final void start() throws Exception {
    if (bindState == BindState.UNBOUND) {
        bind();
        bindState = BindState.BOUND_ON_START;
    }
    startInternal();
}

public final void stop() throws Exception {
    stopInternal();
    if (bindState == BindState.BOUND_ON_START) {
        unbind();
        bindState = BindState.UNBOUND;
    }
}

Then you can stop & start the connector through JMX after updates to your keys and trust stores.

智商已欠费 2024-11-11 03:54:51

Tomcat HTTP/1.1 协议处理程序可以重新加载密钥库。

如果您使用嵌入式 Tomcat 或有某种方式访问​​ Tomcat 连接器,那么您可以要求协议处理程序按需重新加载密钥库和信任库,而无需重新启动连接器。

void addConnector(TomcatEmbedded tomcatEmbedded) {
    // Create a connector using the HTTP/1.1 protocol handler
    Connector connector = new Connector("HTTP/1.1");
    connector.setPort(8080);

    tomcatEmbedded.addConnector(connector);
}


void reload(Connector connector) {
    ProtocolHandler protocolHandler = connector.getProtocolHandler();
    if (protocolHandler instanceof Http11NioProtocol) {
        Http11NioProtocol http11NioProtocol = (Http11NioProtocol)protocolHandler;
        // reload the key and trust stores
        http11NioProtocol.reloadSslHostConfigs();
    }
}

The Tomcat HTTP/1.1 protocol handler can reload keystores.

If you are using embedded Tomcat or have some way of accessing the Tomcat Connector, then then you ask the protocol hander to reload the keystores and trust stores on demand without restarting the connector.

void addConnector(TomcatEmbedded tomcatEmbedded) {
    // Create a connector using the HTTP/1.1 protocol handler
    Connector connector = new Connector("HTTP/1.1");
    connector.setPort(8080);

    tomcatEmbedded.addConnector(connector);
}


void reload(Connector connector) {
    ProtocolHandler protocolHandler = connector.getProtocolHandler();
    if (protocolHandler instanceof Http11NioProtocol) {
        Http11NioProtocol http11NioProtocol = (Http11NioProtocol)protocolHandler;
        // reload the key and trust stores
        http11NioProtocol.reloadSslHostConfigs();
    }
}
山色无中 2024-11-11 03:54:51

最简单的方法是以编程方式读取密钥库,从中获取 SSL 上下文并使用它来建立连接。

private SSLContext buildSslSocketContext() {

        logger.info("Started checking for certificates and if it finds the certificates will be loaded…..");

        String keyStoreLoc = //KEYSTORE LOCATION;
        String password = //KEYSTORE_PASSWORD;
        SSLContext context = null;
        
        try {
            // Create a KeyStore containing our trusted CAs
            KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
            InputStream in = null;

            try {
                in = new FileInputStream(keyStoreLoc);
                keystore.load(in,password.toCharArray());
            }catch(Exception e) {
                logger.error("Unable to load keystore "+e.getMessage());
            }finally {
                if(in != null) {
                    in.close(); 
                }               
            }

            // Create a TrustManager that trusts the CAs in our KeyStore
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keystore);

            // Create an SSLContext that uses our TrustManager
            context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);
            logger.info("Completed loading of certificates.");

        } catch (Exception e) {
            logger.error("unable to create ssl context "+e.getMessage());
        }
        return context;
    } 

    ClientBuilder clientBuilder =  null;

        try {
            SSLContext sslContext = buildSslSocketContext();
            clientBuilder = ClientBuilder.newBuilder();
            if (sslContext != null) {
                clientBuilder.sslContext(sslContext);
            } else {
                logger.info("SSL conext is missing");
            }
            client = clientBuilder.build(); //use this client to make  http connection
        }catch(Exception e) {
            logger.error("unable to get ssl conext for client :"+e.getMessage());
        }

The easiest way is read the keystore programmatically, get a SSL context from that and use it to make connection.

private SSLContext buildSslSocketContext() {

        logger.info("Started checking for certificates and if it finds the certificates will be loaded…..");

        String keyStoreLoc = //KEYSTORE LOCATION;
        String password = //KEYSTORE_PASSWORD;
        SSLContext context = null;
        
        try {
            // Create a KeyStore containing our trusted CAs
            KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
            InputStream in = null;

            try {
                in = new FileInputStream(keyStoreLoc);
                keystore.load(in,password.toCharArray());
            }catch(Exception e) {
                logger.error("Unable to load keystore "+e.getMessage());
            }finally {
                if(in != null) {
                    in.close(); 
                }               
            }

            // Create a TrustManager that trusts the CAs in our KeyStore
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keystore);

            // Create an SSLContext that uses our TrustManager
            context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);
            logger.info("Completed loading of certificates.");

        } catch (Exception e) {
            logger.error("unable to create ssl context "+e.getMessage());
        }
        return context;
    } 

    ClientBuilder clientBuilder =  null;

        try {
            SSLContext sslContext = buildSslSocketContext();
            clientBuilder = ClientBuilder.newBuilder();
            if (sslContext != null) {
                clientBuilder.sslContext(sslContext);
            } else {
                logger.info("SSL conext is missing");
            }
            client = clientBuilder.build(); //use this client to make  http connection
        }catch(Exception e) {
            logger.error("unable to get ssl conext for client :"+e.getMessage());
        }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文