繁体   English   中英

tomcat classloader bug?

[英]tomcat classloader bug?

更新:我已经了解了更多关于正在发生的事情并在底部添加了新信息。

我在tomcat下运行了2个应用程序。 首先加载App1,然后加载App2。 如果App1在启动期间遇到任何类型的错误并且无法成功加载,我在App2启动期间会收到此错误:

Caused by: java.security.NoSuchAlgorithmException: No such algorithm: RSA/NONE/OAEPWithSHA1AndMGF1Padding
    at javax.crypto.Cipher.getInstance(DashoA13*..)
    at javax.crypto.Cipher.getInstance(DashoA13*..)
    at com.jp.protection.security.BouncyCastleSecurityProvider.getCipher(BouncyCastleSecurityProvider.java:139)
    at com.jp.protection.security.BouncyCastleSecurityProvider.decode(BouncyCastleSecurityProvider.java:110)
    ... 70 more
Caused by: java.lang.NullPointerException
    at org.bouncycastle.jcajce.provider.util.DigestFactory.getDigest(DigestFactory.java:86)
    at org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.initFromSpec(CipherSpi.java:83)
    at org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.engineSetPadding(CipherSpi.java:214)
    at javax.crypto.Cipher$r.a(DashoA13*..)
    ... 74 more

请注意,最终原因是NullPointerException。 我下载了DigestFactory的源代码,它看起来像这样(只摘录了相关部分):

package org.bouncycastle.jcajce.provider.util;

public class DigestFactory
{
    private static Set sha1 = new HashSet();

    static
    {           
        sha1.add("SHA1");
        sha1.add("SHA-1");
    }

    public static Digest getDigest(
        String digestName) 
    {
        digestName = Strings.toUpperCase(digestName);

        if (sha1.contains(digestName))  ** line 86 where npe occurs**
        {
            return new SHA1Digest();
        }
    [...]

在第86行获取NPE的唯一方法是sha1为空。 (如果digestName为null,则NPE将在对Strings.toUpperCase的调用中出现。)实际上,如果我在此处设置断点,则在错误情形下,调试器将sha1(以及所有其他类似的静态初始化字段)显示为null。 这些字段是私有的,没有允许修改这些字段的方法。

这怎么可能? 我想也许我的DigestFactory源与我运行的jar不完全匹配所以调试器误导了我,但它应该是正确的版本,其他一切似乎排成一行。

在调试器下,我尝试在App2初始化的早期阶段,在异常发生之前调用DigestFactory.getDigest(“SHA-1”)(使用调试器的evaluate表达式),并且它成功返回。 这表明DigestFactory的静态字段已成功初始化,然后稍后设置为null,或者另一个类加载器具有不同版本的类(即使这种情况,也不解释它们如何为null)。

这个例外发生在第三方代码的2层深处(jproductivity Protection包使用bouncycastle)所以我对这种情况的控制是有限的。 但是我想首先了解这是如何可行的,并希望我能如何预防或解决它。 另一个谜团是为什么第一个应用程序的错误对第二个应用程序有任何影响 - 在tomcat下这些应该有单独的类加载器。 但如果第一个应用程序中没有错误,那么第二个应用程序中不会出现此问题。

更新:自从我发布这个以来我学到了更多。 当tomcat停止webapp时(在这种情况下由于启动错误),tomcat将使其类中的静态字段为空以避免内存泄漏。 所以这解释了我的不可变静态字段是如何设置为null的。 但是,这应该不会影响App2,因为它应该使用单独的类加载器。 但我查看了调试器,实际上两个webapps中的DigestFactory类都使用了相同的类加载器。 这与我能找到的所有tomcat文档相矛盾。 对于其他类(我自己的类),有不同的类加载器。 我想知道它是否与DigestFactory是静态的和不可变的有关,所以从理论上说它来自何处并不重要。

因此,作为一个实验,我从我的两个webapps中删除了包含DigestFactory的jar,并将其添加到tomcat / lib(这样它就可以共享,而不是webapp的一部分)。 这解决了这个问题 - 它的字段没有被删除,大概是因为它不是违规webapp的一部分。 然而,这种方法是不合需要的,并且不是必需的。 这是一个tomcat bug吗?

我相信正在发生的事情是,当您启动App1时,bouncycastle会在应用程序类加载器下注册。 如果我没记错的话,然后通过一些静态初始化器或方法将提供程序注册到JVM类加载器中。

当您的App1崩溃(或重新部署)时,它的类加载器以及它已加载的任何类(包括bouncycastle)都会被删除。 结果是JVM认为它仍然存在,因为它仍然注册,而实际上它不是。

解决方案是通过添加类似于以下的行,将BouncyCastleProvider添加到jre / lib / security / java.security中的安全提供程序列表(java 7位置,我认为它在旧版本的jre / lib / ext中):

security.provider。[next available number] = org.bouncycastle.jce.provider.BouncyCastleProvider

您可能还需要在那里添加jar文件。

当我的WebApp WEB-INF / lib中包含BouncyCastle提供程序并且我重新加载了我的tomcat应用程序时,我发现了类似的行为。 在我的案例中提出了以下赘言:

júl. 09, 2015 7:24:00 DE org.apache.catalina.loader.WebappClassLoader loadClass
INFO: Illegal access: this web application instance has been stopped already.  Could not load org.bouncycastle.jcajce.provider.digest.SHA1$PBEWithMacKeyFactory.  The eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and has no functional impact.
java.lang.IllegalStateException
        at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1612)
        at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1571)
        at org.apache.tomee.catalina.LazyStopWebappClassLoader.loadClass(LazyStopWebappClassLoader.java:129)
        at java.security.Provider$Service.getImplClass(Provider.java:1279)
        at java.security.Provider$Service.newInstance(Provider.java:1237)
        at sun.security.jca.GetInstance.getInstance(GetInstance.java:236)
        at javax.crypto.JceSecurity.getInstance(JceSecurity.java:116)
        at javax.crypto.SecretKeyFactory.getInstance(SecretKeyFactory.java:243)
        at org.bouncycastle.jcajce.util.ProviderJcaJceHelper.createSecretKeyFactory(Unknown Source)
        at org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.calculatePbeMac(Unknown Source)
        at org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.engineLoad(Unknown Source)
        at java.security.KeyStore.load(KeyStore.java:1214)
        at hu.myapp.mypackage.myEJB.setup(myEJB.java:154)

其中setup是myEJB无状态中的@PostConstruct注释方法,用于初始化BC提供程序。

@LocalBean
@Stateless
public class myEJB {
  ...
  private BouncyCastleProvider bc = null;
  private KeyStore ks = null;

  @PostConstruct
  protected void setup() {
        bc = new BouncyCastleProvider();
        ks = KeyStore.getInstance("PKCS12", bc);
        ...
  }
  ...
}

当我将提供程序bcprov-jdk15on-152.jar移动$ JAVA_HOME / jre / lib / ext中时 ,我已将以下行插入$ JAVA_HOME / jre / lib / security / java.security文件中:

  security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider

当然,在我的情况下,提供商索引是11,因为10是最后一个提供商,所以在你的情况下它可以是不同的。

我从我的webapp中删除了bcprov-jdk15on-152.jar文件,一切都很完美。

所以我认为Einar的答案是恰当的。

在得出结论认为这必须是一个tomcat bug之后,我从tomcat 6升级到了tomcat 7,这解决了这个问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM