https://issues.apache.org/bugzilla/show_bug.cgi?id=56989
Bug ID: 56989 Summary: Classes being loaded from wrong web app. Product: Tomcat 7 Version: 7.0.55 Hardware: All OS: All Status: NEW Severity: critical Priority: P2 Component: Catalina Assignee: dev@tomcat.apache.org Reporter: m.marti...@ll.mit.edu Actually a more correct description might be "Classes NOT being reloaded from correct web app. The following scenario results in classes loaded from one web app showing up in another web app, in place of the correct classes that should have been loaded from the latter. The example uses Spring Security to demonstrate the problem, but after examining the Spring code I don't believe it is the problem at all and the problem can be produced in other code. Scenario: Two web apps deployed into the same Tomcat container, "war1" and "war2". The server.xml is configured to use mutual SSL on the connector so requests must have an acceptable x509 certificate to get through. Inside each war is a copy of the Spring security framework (though this can be produced with other classes, this is easy to reproduce). The exact version probably doesn't matter, but in this case it was: ..tomcat/webapps/war1/WEB-INF/lib/spring-security-core-3.1.4.RELEASE.jar ..tomcat/webapps/war1/WEB-INF/lib/spring-security-web-3.1.4.RELEASE.jar and ..tomcat/webapps/war2/WEB-INF/lib/spring-security-core-3.1.4.RELEASE.jar ..tomcat/webapps/war2/WEB-INF/lib/spring-security-web-3.1.4.RELEASE.jar (along with associated dependencies in each case). There are no other copies of the Spring libraries anywhere. Both web apps declare a Spring FilterChain which, naturally, starts with the org.springframework.security.web.context.SecurityContextPersistenceFilter class. <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy"> <constructor-arg> <list> <security:filter-chain pattern="/resources/**" filters="none" /> <security:filter-chain pattern="/login**" filters="none"/> <!-- All authenticated requests must go through this chain --> <security:filter-chain request-matcher-ref="httpsRequestMatcher" filters= "securityContextPersistenceFilter, securityContextHolderAwareRequestFilter, myAuthenticationFilter" /> </list> </constructor-arg> </bean> The "securityContextPersistenceFilter" is fairly simple, if you have not looked at their source code. All it is doing is asserting that a Spring SecurityContext object exists at the start of the request, putting it into a ThreadLocal and then, at the end of the request, clearing it from the ThreadLocal and saving the object in the session. The "securityContextHolderAwareRequestFilter" is another standard Spring filter that simply wraps the request in a wrapper that knows how to get some standard security pieces out of the SecurityContext, if it exists and is setup. The "myAuthenticationFilter" bean is a our own Filter that simply confirms whether the DN of the user certificate is acceptable and if so, creates an appropriate "org.springframework.security.core.Authentication" object that the following servlet application will understand and places it into the SecurityContextHolder. Pretty standard stuff: public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException{ //... Authentication myAuth = //... create authentication. SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication(myAuth); //... chain.doFilter(request,response); } Pretty simple. When either of the web apps is deployed alone, this works perfect. However, when both are deployed into the same tomcat, it only works perfect for the first web app you access with a request. Upon sending a request to the second war, the above code will fail because the SecurityContext object is built from classes loaded from the first war1! Upon debugging this problem, and very carefully and using the Class.getProtectionDomain().getCodeSource() API to printout the exact source of each of the relevant classes involved, the following are true: When making the first request against war1 all classes properly load from war1. When making the next request against war2: SecurityContextHolder -> properly loaded from war2 Authentication -> properly loaded from war2 ThreadLocalSecurityContextHolderStrategy --> LOADED FROM WAR1!!! SecurityContextImpl -> LOADED FROM WAR1! This is bizarre and seems like an explicit violation of the specified class loader violation. If you flip the problem around and access war2 first after startup, then the problem instead shows up when you subsequently try to access war2. The way the Spring code is structured is pretty simple. The SecurityContextPersistenceFilter makes use of static calls on the SecurityContextHolder class to create and 'hold' the SecurityContext object. The SecurityContextHolder has a static field called 'strategy'. It has a static initializer that instantiates the ThreadLocalSecurityContextHolderStrategy and assigns it to the field. You can get to it with getContextHolderStrategy() accessor. The strategy object itself maintains a ThreadLocal that is used to hold the SecurityContext object. It also provides the 'createEmptyContext()' method which is used during the persistence Filter phase to create the original new SecurityContextImpl object. The SecurityContextPersistenceFilter always makes sure to clear ThreadLocal upon request termination via a 'finally' clause. All those steps seem to execute correctly when I step through the code. The problem seems to be that when the second web app is being invoked and the SecurityContextHolder class is loaded from war2, it's static fields are not properly isolated. I created an alternative 'SecurityContextHolderStrategy' implementation for the SecurityContextHolder to use that printed out debug statements (including identifying the code source) in it's constructor. I can see that it gets properly instantiated for each web app when each is loaded. But when I check the object that is actually attached to the SecurityContextHolder.strategy field in the second war, it points to the strategy object from the first war. I consider this to a pretty serious bug, obviously. I have a workaround for this particular occurrence of the issue. In "MyAuthenticationFilter", the very first time we get a request from an unauthenticated user, the SecurityContext object is by definition empty and the way Spring implements it, the hash code for an empty SecurityContextImpl object will always be "-1". So I can detect that it is a new, empty object without having to cast it to a SecurityContext object (which would fail because that interface class is loaded from the current web app). I also can check the class loader against the class loader used to load the SecurityContextHolder and if they are not equal that indicates the problem (since they are _supposed_ to be loaded from the same jar). Once I detect the problem, I can then simply instantiate a new SecurityContextImpl object (using the class from the current, correct class loader) and setting that new object into the SecurityContextHolder. This has to be done with reflection to avoid the class cast exceptions: Class contextHolderClass = SecurityContextHolder.class; Method getContext = contextHolderClass.getMethod("getContext",(Class[])null); Object context = getContext.invoke(null,(Object[])null); if(context.hashCode()==-1){ //this indicates it is empty. if(context.getClass().getClassLoader() != contextHolderCls.getClassLoader()){ //it was not loaded by the correct classloader (amazingly - this can happen!) //create a new one String clsName = context.getClass().getName(); Class cls = contextHolderCls.getClassLoader().loadClass(clsName); context = cls.newInstance(); //now, set this object back into the context holder Method[] methods = contextHolderCls.getMethods(); Method setContext = null; for(Method m : methods){ if("setContext".equals(m.getName())){ setContext = m; break; } } setContext.invoke(null, context); } } The use of the contextHolder.getClassLoader().loadClass() method and cls.newInstance() to create the new SecurityContextImpl is probably anal-retentive caution. It should work by simply invoking "new SecurityContextImpl()". This works around this particular instance of the problem and now both web apps are able to co-exist perfectly. Notes: We replicated this problem with the following platform variations: Tomcat v7.0.55 & v7.0.42 OS: Linux (two flavors of Centos 6.5), Windows 7 and Mac OS 10.8.5 Java: Java 7 v1.7.0_65 & v1.7.0_40 Finally - I think it is important to emphasize that while the above workaround helps fix the occurrence relative to the Spring SecurityContext API, I do not consider this a 'fix' at all for the core problem, which must be somewhere in the web app class loader implementation. The problem can be reproduced with any java code that follows the same pattern as these Spring classes, which are not doing anything unusual. I hope this is helpful! -- You are receiving this mail because: You are the assignee for the bug. --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org