CGLIB: signer information does not match signer information of other classes
Today I’m going to describe the solution for the issue SPR-12833. The issue description says about a subset of problems which you’ll get using spring in a signed environment. The good thing about it, you need to sign jars of your application in limited cases. One of those cases is you need to distribute your application to clients by Java Web Start. And the root cause of this problem is the fact that built-in CGLIB doesn’t work properly in a signed environment. If you start your app from IDE all your classes aren’t signed, and this is a problem for CGLIB. There is a class org.springframework.cglib.core.ReflectUtils
and it uses PROTECTION_DOMAIN
which was retrieved from spring framework’s jar (in our case, all classes in this jar are signed), while all classes in our project aren’t signed until we pack it, sign, and distribute the application. Default classloader tries to compare certificates from spring framework’s classes with empty certificates list from project’s classes, here we get SecurityException
. This issue was solved in original CGLIB’s repository on Jul 18, 2014, but wasn’t merged into spring framework.
If you try to use proxies without any tricks, most likely you will get something like this:
Caused by: org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:237) at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:377) at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:317) at org.springframework.context.annotation.ConfigurationClassEnhancer.createClass(ConfigurationClassEnhancer.java:137) at org.springframework.context.annotation.ConfigurationClassEnhancer.enhance(ConfigurationClassEnhancer.java:109) at org.springframework.context.annotation.ConfigurationClassPostProcessor.enhanceConfigurationClasses(ConfigurationClassPostProcessor.java:400) ... 39 more Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:384) at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:219) ... 44 more Caused by: java.lang.SecurityException: class "com.dimafeng.test.MyTest$Config$$EnhancerBySpringCGLIB$$aec0d6cc"'s signer information does not match signer information of other classes in the same package at java.lang.ClassLoader.checkCerts(ClassLoader.java:895) at java.lang.ClassLoader.preDefineClass(ClassLoader.java:665) at java.lang.ClassLoader.defineClass(ClassLoader.java:758) ... 50 more
Spring framework uses CGLIB proxy generation for 2 operations:
- Java-based configuration using
@Configuration
annotation. - AOP proxies without interface or proxies which are marked with
proxy-target-class="true"
.
Java-based configuration
This class of problems can be solved really easy. The first comment in SPR-12833 says that if you use @Component
-based annotations instead of @Configuration
, spring won’t generate CGLIB proxy.
Quick example:
This simple test creates ApplicationContext
from the Java-based configuration in Config
. If you start it test will pass.
AOP proxies
The quickest solution of this issue is a usage of a custom class loader. Spring framework allows to use your own instance of the class loader for beans when you create your ApplicationContext
. It may look like this:
It’s important to pass false
as a second parameter - we don’t need to refresh context before a new class loader will be set. Now we can implement our UnsecureClassLoader
.
Here we have a little bit magic. That’s why you don’t need to include this code in production. This works well for tests and dev environment when you use a default class loader. Probably, this trick won’t work in next versions of java, if ClassLoader
structure is changed.
What we’ve done here. First of all, it’s important to set a parent class loader, it should be the class loader which is used during application start. In this case, classes will be validated in our class loader but will be stored in the parent. Then, we override value of package2certs
field - we should set map-mock instead. If you look at ClassLoader
’s sources, you’ll see:
If package2certs
always returns null
we’ll never get SecurityException
and it’s good in our case.