Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring boot doesn't read Components after jar signing

I am developing a Spring Boot Application that serves REST HTTP(S) requests. (pretty common).

It works as it is supposed, but after the final (and working) jar is signed (by a valid certificate) all URL mappings stop working, returning only 404 to any request. (Note that the embedded Tomcat server starts without problems and I don't receive any exception)

After some debugging I found that the Java's default ClassLoader (Laucher$AppClassLoader) just doesn't return the classes within the packages I configurated (@ComponentScan) when the jar is signed.

//org.springframework.core.io.support.PathMatchingResourcePatternResolver
//Param 'path' has my valid, existing and desired package with @Controller or @Component inside
protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
            Set<Resource> result = new LinkedHashSet<Resource>(16);
            ClassLoader cl = getClassLoader(); //sun.misc.Laucher$AppClassLoader
            Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
            //Empty enumeration when jar is signed
            ...
}

I tried to use a custom Class Loader without success; same problem.

Since it works when I sign the jar with a self signed certificate, I think that there may be a problem with the signing process that was done by another person. But I can't find any proof of that.

It appears that once signed, I can't list the package content...

I'll try some more tests and add here if I consider useful...

UPDATE

After debugging with the help of a custom Class Loader, I found that:

((java.net.JarURLConnection)new java.net.URL("jar:file:/home/user/my-app-with-dependencies_signed.jar!/META-INF/MANIFEST.MF").openConnection()).getJarEntry(); 

Ok. Works.

((java.net.JarURLConnection)new java.net.URL("jar:file:/home/user/my-app-with-dependencies_signed.jar!/META-INF/").openConnection()).getJarEntry();

Doesn't work! >.< It throws

Exception occurred in target VM: JAR entry META-INF/ not found in /home/user/my-app-with-dependencies_signed.jar 
java.io.FileNotFoundException: JAR entry META-INF/ not found in /home/user/my-app-with-dependencies_signed.jar
    at sun.net.www.protocol.jar.JarURLConnection.connect(JarURLConnection.java:142)
    at sun.net.www.protocol.jar.JarURLConnection.getJarEntry(JarURLConnection.java:94)
...

This same second example works when trying to access a unsigned or self-signed jar.

This operation of opening the jar is performed by Spring when reading @Controller and @Component from the given packages in @ComponentScan.

In the same way, Java's Class Loader doesn't read directories content, only specified files.

this.getClass().getClassLoader(); //sun.misc.Launcher$AppClassLoader@18b4aac2
this.getClass().getClassLoader().getResources("META-INF/MANIFEST.MF").hasMoreElements(); //always true
this.getClass().getClassLoader().getResources("META-INF/").hasMoreElements(); //false when signed

UPDATE 2

I got the information about the signature. The people responsible for signatures and certificates actually uses a Windows applications that signs the jar with certificates from Windows-MY keystore and private key from a USB token.

Not that this is certainly the cause, but I think it is important to note that jarsigner is not used.

UPDATE 3

I created a github repository with a simple test case: https://github.com/jesjobom/signed-jar-class-loader-test

like image 241
Jairton Junior Avatar asked Feb 16 '18 20:02

Jairton Junior


1 Answers

I have reached a solution but the problem still exists.

When loading the classes I pointed via @ComponentScan Spring asks the ClassLoader (Laucher$AppClassLoader) for the java.net.URL for each package I informed. Since, for some unknown reason, I can't load packages/folders, I created a custom ClassLoader that always return the expected URL if the package is mine.

public class CustomClassLoader extends ClassLoader {

...

@Override
public Enumeration<URL> getResources(String name) throws IOException {
    if(name.startsWith("com/my/package/")) {
        readBasePath(); //obtains path to jar (e.g. "jar:file:/home/app.jar!/")
        List<URL> resources = new ArrayList<>();
        resources.add(new URL(basePath + name));
        return Collections.enumeration(resources);
    }
    return fallback.getResources(name); //default classloader
}
...
}

Even so, later, Spring tries to load the ".class" from the packages and fails for the same reasons... So, I created a custom implementation of PathMatchingResourcePatternResolver that will list all the content of the jar (this I can do!) and selects only those within the given package.

public class CustomPathMatchingResourceLoader extends PathMatchingResourcePatternResolver {

@Override
protected Set<Resource> doFindPathMatchingJarResources(final Resource rootDirResource, URL rootDirURL, String subPattern) throws IOException {

    try {

        String searchBase = ...; //package within jar
        String pathBase = ...; //path to jar

        URLConnection conn = new URL(pathBase).openConnection();

        Set<Resource> resources = new HashSet();
        JarFile file = ((JarURLConnection) conn).getJarFile();

        Enumeration<JarEntry> entries = file.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            if (entry.getName().startsWith(searchBase) && !entry.getName().endsWith("/")) {
                resources.add(new UrlResource(pathBase + entry.getName()));
            }
        }

        return resources;

    } catch (Exception e) {
        e.printStackTrace();
    }

    return super.doFindPathMatchingJarResources(rootDirResource, rootDirURL, subPattern);
}
...
}

So it worked without any interference in the signing process... I am pretty confident that signing with jarsigner would solve the problem, but I think that it'd be difficult...

Anyway, although it worked, it's not a solution. Therefore, I will not accept this answer as the correct one...

like image 139
Jairton Junior Avatar answered Sep 20 '22 01:09

Jairton Junior