Skip to content

Commit

Permalink
Improved java.net.Authenticator cleanup and workaround for https://is…
Browse files Browse the repository at this point in the history
  • Loading branch information
Mattias Jiderhamn committed Dec 6, 2013
1 parent 63d85b1 commit 16fc362
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 4 deletions.
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,13 @@
<version>2.1.19</version>
<scope>test</scope>
</dependency>
<!-- Could be removed if Mockit was used to mock ELContext -->
<!-- Test leak in CXF custom authenticator -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>2.6.10</version>
</dependency>
<!-- Could be removed if Mockito was used to mock ELContext -->
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.Authenticator;
import java.net.URL;
import java.sql.Driver;
Expand Down Expand Up @@ -595,15 +596,115 @@ protected void deregisterSecurityProviders() {
}
}

/** Clear the default java.net.Authenticator (in case current one is loaded in web app) */
/**
* Clear the default java.net.Authenticator (in case current one is loaded in web app).
* Includes special workaround for CXF issue https://issues.apache.org/jira/browse/CXF-5442
*/
protected void clearDefaultAuthenticator() {
final Authenticator defaultAuthenticator = getStaticFieldValue(Authenticator.class, "theAuthenticator");
final Authenticator defaultAuthenticator = getDefaultAuthenticator();
if(defaultAuthenticator == null || // Can both mean not set, or error retrieving, so unset anyway to be safe
isLoadedInWebApplication(defaultAuthenticator)) {
isLoadedInWebApplication(defaultAuthenticator)) {
if(defaultAuthenticator != null) // Log warning only if a default was actually found
warn("Unsetting default " + Authenticator.class.getName() + ": " + defaultAuthenticator);
Authenticator.setDefault(null);
}
else {
if("org.apache.cxf.transport.http.ReferencingAuthenticator".equals(defaultAuthenticator.getClass().getName())) {
/*
Needed since org.apache.cxf.transport.http.ReferencingAuthenticator is loaded by dummy classloader that
references webapp classloader via AccessControlContext + ProtectionDomain.
See https://issues.apache.org/jira/browse/CXF-5442
*/

final Class cxfAuthenticator = findClass("org.apache.cxf.transport.http.CXFAuthenticator");
if(cxfAuthenticator != null && isLoadedByWebApplication(cxfAuthenticator)) { // CXF loaded in this application
final Object cxfAuthenticator$instance = getStaticFieldValue(cxfAuthenticator, "instance");
if(cxfAuthenticator$instance != null) { // CXF authenticator has been initialized in this webapp
final Object authReference = getFieldValue(defaultAuthenticator, "auth");
if(authReference instanceof Reference) { // WeakReference
final Reference reference = (Reference) authReference;
final Object referencedAuth = reference.get();
if(referencedAuth == cxfAuthenticator$instance) { // References CXFAuthenticator of this classloader
warn("Default " + Authenticator.class.getName() + " was " + defaultAuthenticator + " that referenced " +
cxfAuthenticator$instance + " loaded by webapp");

// Let CXF unwrap in it's own way (in case there are multiple CXF webapps in the container)
reference.clear(); // Remove the weak reference before calling check()
try {
final Method check = defaultAuthenticator.getClass().getMethod("check");
check.setAccessible(true);
check.invoke(defaultAuthenticator);
}
catch (Exception e) {
error(e);
}
}
}
}
}
}

removeWrappedAuthenticators(defaultAuthenticator);

info("Default " + Authenticator.class.getName() + " not loaded in webapp: " + defaultAuthenticator);
}
}

/** Find default {@link Authenticator} */
protected Authenticator getDefaultAuthenticator() {
// Normally Corresponds to getStaticFieldValue(Authenticator.class, "theAuthenticator");
for(final Field f : Authenticator.class.getDeclaredFields()) {
if (f.getType().equals(Authenticator.class)) { // Supposedly "theAuthenticator"
try {
f.setAccessible(true);
return (Authenticator)f.get(null);
} catch (Exception e) {
error(e);
}
}
}
return null;
}

/**
* Recursively removed wrapped {@link Authenticator} loaded in this webapp. May be needed in case there are multiple CXF
* applications within the same container.
*/
protected void removeWrappedAuthenticators(final Authenticator authenticator) {
if(authenticator == null)
return;

try {
Class authenticatorClass = authenticator.getClass();
do {
for(final Field f : authenticator.getClass().getDeclaredFields()) {
if(Authenticator.class.isAssignableFrom(f.getType())) {
try {
final boolean isStatic = Modifier.isStatic(f.getModifiers()); // In CXF case this should be false
final Authenticator owner = isStatic ? null : authenticator;
f.setAccessible(true);
final Authenticator wrapped = (Authenticator)f.get(owner);
if(isLoadedInWebApplication(wrapped)) {
warn(Authenticator.class.getName() + ": " + wrapped + ", wrapped by " + authenticator +
", is loaded by webapp classloader");
f.set(owner, null); // For added safety
}
else {
removeWrappedAuthenticators(wrapped); // Recurse
}
} catch (Exception e) {
error(e);
}
}
}
authenticatorClass = authenticatorClass.getSuperclass();
} while (authenticatorClass != null && authenticatorClass != Object.class);
}
catch (Exception e) { // Should never happen
error(e);
}
}

/** This method is heavily inspired by org.apache.catalina.loader.WebappClassLoader.clearReferencesRmiTargets() */
protected void deregisterRmiTargets() {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package se.jiderhamn.classloader.leak.prevention;

import org.apache.cxf.transport.http.CXFAuthenticator;
import org.junit.Test;
import org.junit.runner.RunWith;
import se.jiderhamn.JUnitClassloaderRunner;
import se.jiderhamn.LeakPreventor;

/**
* Test case to verify that org.apache.cxf.transport.http.CXFAuthenticator causes classloader leaks.
* @author Mattias Jiderhamn
*/
@RunWith(JUnitClassloaderRunner.class)
@LeakPreventor(CXFAuthenticatorTest.Prevent.class)
public class CXFAuthenticatorTest {

@Test
public void triggetCxfAuthenticatorLeak() {
CXFAuthenticator.addAuthenticator();
}

public static class Prevent implements Runnable {
@Override
public void run() {
new ClassLoaderLeakPreventor() {
{
clearDefaultAuthenticator();
}
};

// This does not help, since WeakReference is not garbage collected until ClassLoader is GC:ed
/*
try {
// By requesting authentication, ReferencingAuthenticator should detect that CXFAuthenticator has been removed
Authenticator.requestPasswordAuthentication(null, 0, null, null, null);
}
catch (Exception e) {
// CS:IGNORE
}
*/
}
}
}

0 comments on commit 16fc362

Please sign in to comment.