2014-02-12 69 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文件的工作完美。

所以我覺得埃納爾的回答是適當的。