2014-02-12 78 views
2

更新:我已经了解了更多关于正在发生的事情,并在底部添加了新的信息。tomcat类加载器错误?

我有2个应用程序运行在tomcat下。首先加载App1,然后加载App2。如果应用1启动过程中运行到任何类型的错误,未能成功加载,我应用2的启动过程中出现此错误:

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为null。 (如果digestName为null,NPE将在对Strings.toUpperCase的调用中发生)。事实上,如果我在此处放置断点,则在错误情况下,调试器显示sha1(以及所有其他类似静态初始化的字段)为null。这些字段是私人的,没有方法允许修改这些字段。

这怎么可能?我认为也许我的DigestFactory源代码与我运行的jar不完全匹配,所以调试器误导了我,但它应该是正确的版本,而其他所有东西似乎都是一致的。

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

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

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

因此,作为一个实验,我从两个webapps中删除了包含DigestFactory的jar,并将它添加到tomcat/lib(以便它被共享,而不是任何webapp的一部分)。这就解决了这个问题 - 它的领域并没有被消除,大概是因为它不是犯罪Web应用程序的一部分。但是,这种方法是不可取的,不应该是必要的。这是一个tomcat的bug吗?

+0

第一个应用程序出现什么样的问题?这可能是解决这个难题的一个非常重要的信息。 –

+0

它在初始化弹簧上下文时发生在各种场景中。出于测试目的,我在ContextLoaderListener.contextInitialized()实现中强制使用空指针异常。 – Dana

回答

0

得出结论认为,这不得不说是一个Tomcat错误后,我从Tomcat 6升级到Tomcat 7,而且解决了这一问题。

4

我相信发生的事情是,当启动App1时,bouncycastle会在applications classloader下注册。如果我没有记错,那么提供程序就会通过一些静态初始化程序或方法注册到JVM类加载程序中。

当你的应用1崩溃(或重新部署),它的类加载器拆下,用它加载任何类,包括BouncyCastle的一起。其结果是JVM认为它仍然存在,因为它仍然被注册,而实际上它不是。

解决的办法是添加BouncyCastleProvider到jre/lib/security/java.security中的安全提供程序列表(我认为它是在旧版本的jre/lib/ext中),方法是添加一行类似于这样的:

security.provider [下一个可用编号] = org.bouncycastle.jce.provider.BouncyCastleProvider

您可能需要添加的jar文件有太多。

+0

另一种可能性是getDigest在DigestFactory完全初始化之前被调用。这是不应该发生的,但Tomcat的做一些奇怪的事情,再加上有当ThingA被初始化中初始化ThingB初始化为ThingC内可能出现一些“有趣”的场景... –

+0

@quarts这不对我有意义。 App2也加载这个类,并且该类存在,只有它的字段为空。但由于没有任何关于这是有道理的,我确实尝试了你的建议,并没有改变行为。 – Dana

3

我发现类似相同的行为时,BouncyCastle的供应商在我Web应用程序的WEB-INF/lib目录中inlcuded,我已经重装我的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) 

,其中设置在myEJB无国籍其中初始化BC提供商@PostConstruct注释的方法。

@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是最后提供商所以在你的情况下,它可以是不同的。

与我已删除从我的web应用程序的一切bcprov-jdk15on-152.jar文件的工作完美。

所以我觉得埃纳尔的回答是适当的。