flow
flow copied to clipboard
GraalVM native image and VaadinSecurityFilterChainBean
I tried to build a native image of my Vaadin Flow app (24.4.0.beta3)
The build works fine, but if I start the app, I get this exception:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Unsatisfied dependency expressed through method 'setFilterChains' parameter 0: Error creating bean with name 'VaadinSecurityFilterChainBean': Instantiation of supplied bean failed
at org.springframework.beans.factory.aot.AutowiredMethodArgumentsResolver.resolveArguments(AutowiredMethodArgumentsResolver.java:187) ~[na:na]
at org.springframework.beans.factory.aot.AutowiredMethodArgumentsResolver.resolve(AutowiredMethodArgumentsResolver.java:137) ~[na:na]
at org.springframework.beans.factory.aot.AutowiredMethodArgumentsResolver.resolve(AutowiredMethodArgumentsResolver.java:123) ~[na:na]
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration__Autowiring.apply(WebSecurityConfiguration__Autowiring.java:24) ~[na:na]
at org.springframework.beans.factory.support.InstanceSupplier$1.get(InstanceSupplier.java:83) ~[na:na]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:949) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1217) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1161) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[jtaf4:6.1.6]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:962) ~[jtaf4:6.1.6]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) ~[jtaf4:6.1.6]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[na:na]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[jtaf4:3.2.5]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[jtaf4:3.2.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) ~[jtaf4:3.2.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[jtaf4:3.2.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[jtaf4:3.2.5]
at ch.jtaf.Jtaf4Application.main(Jtaf4Application.java:20) ~[jtaf4:na]
at java.base@22/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH) ~[na:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'VaadinSecurityFilterChainBean': Instantiation of supplied bean failed
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1223) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1161) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1689) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1653) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeanCollection(DefaultListableBeanFactory.java:1543) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeans(DefaultListableBeanFactory.java:1511) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1392) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.aot.AutowiredMethodArgumentsResolver.resolveArguments(AutowiredMethodArgumentsResolver.java:179) ~[na:na]
... 24 common frames omitted
Caused by: java.lang.IllegalStateException: Could not locate field 'rolePrefix' on class class java.lang.Object
at org.springframework.security.util.FieldUtils.getField(FieldUtils.java:53) ~[na:na]
at org.springframework.security.util.FieldUtils.getField(FieldUtils.java:51) ~[na:na]
at org.springframework.security.util.FieldUtils.getField(FieldUtils.java:51) ~[na:na]
at com.vaadin.flow.spring.security.VaadinRolePrefixHolder.resetRolePrefix(VaadinRolePrefixHolder.java:127) ~[jtaf4:na]
at com.vaadin.flow.spring.security.VaadinRolePrefixHolder.resetRolePrefix(VaadinRolePrefixHolder.java:114) ~[jtaf4:na]
at java.base@22/java.util.Optional.ifPresent(Optional.java:178) ~[jtaf4:na]
at com.vaadin.flow.spring.security.VaadinRolePrefixHolder.resetRolePrefix(VaadinRolePrefixHolder.java:109) ~[jtaf4:na]
at com.vaadin.flow.spring.security.VaadinWebSecurity.lambda$filterChain$1(VaadinWebSecurity.java:155) ~[jtaf4:na]
at java.base@22/java.util.Optional.ifPresent(Optional.java:178) ~[jtaf4:na]
at com.vaadin.flow.spring.security.VaadinWebSecurity.filterChain(VaadinWebSecurity.java:154) ~[jtaf4:na]
at ch.jtaf.configuration.security.SecurityConfiguration$$SpringCGLIB$$0.CGLIB$filterChain$3(<generated>) ~[jtaf4:na]
at ch.jtaf.configuration.security.SecurityConfiguration$$SpringCGLIB$$FastClass$$0.invoke(<generated>) ~[jtaf4:na]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[jtaf4:6.1.6]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[na:na]
at ch.jtaf.configuration.security.SecurityConfiguration$$SpringCGLIB$$0.filterChain(<generated>) ~[jtaf4:na]
at com.vaadin.flow.spring.security.VaadinWebSecurity__BeanDefinitions.lambda$getVaadinSecurityFilterChainBeanInstanceSupplier$0(VaadinWebSecurity__BeanDefinitions.java:22) ~[na:na]
at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:68) ~[jtaf4:6.1.6]
at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$2(BeanInstanceSupplier.java:206) ~[na:na]
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) ~[jtaf4:6.1.6]
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) ~[jtaf4:6.1.6]
at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:218) ~[na:na]
at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:206) ~[na:na]
Comment by @marcushellberg: It seems like we need to add some more runtime hints for the AOT compiler
This particular issue should be fixed if we add
hints.reflection().registerType(VaadinSecurityFilterChainBean.class, MemberCategory.values());
to https://github.com/vaadin/flow/blob/main/vaadin-spring/src/main/java/com/vaadin/flow/spring/springnative/VaadinHintsRegistrar.java
But we need to check if there are other new classes or resources that need to be added as well.
I'm not sure if the suggested fix could simply be applied, as VaadinSecurityFilterChainBean
is actually a bean name for a SecurityFilterChain
instance, and its instantiation is failing due to reflection error that happens when VaadinRolePrefixHolder:: resetRolePrefix
is called.
The fact that the following exception is occurring is a bit weird:
Caused by: java.lang.IllegalStateException: Could not locate field 'rolePrefix' on class class java.lang.Object
at org.springframework.security.util.FieldUtils.getField(FieldUtils.java:53) ~[na:na]
at org.springframework.security.util.FieldUtils.getField(FieldUtils.java:51) ~[na:na]
at org.springframework.security.util.FieldUtils.getField(FieldUtils.java:51) ~[na:na]
at com.vaadin.flow.spring.security.VaadinRolePrefixHolder.resetRolePrefix(VaadinRolePrefixHolder.java:127) ~[jtaf4:na]
The following code is clearly filtering filters that are not of type SecurityContextHolderAwareRequestFilter
:
public void resetRolePrefix(DefaultSecurityFilterChain defaultSecurityFilterChain) {
defaultSecurityFilterChain.getFilters().stream()
.filter(filter -> SecurityContextHolderAwareRequestFilter.class
.isAssignableFrom(filter.getClass()))
.map(SecurityContextHolderAwareRequestFilter.class::cast)
.findFirst().ifPresent(this::resetRolePrefix);
}
So it is not clear why FieldUtils.getField(type, "rolePrefix")
is being called on type java.lan.Object
(according to the error log). The implementation of getField
shows what leads to that error log:
public static Field getField(Class<?> clazz, String fieldName) throws IllegalStateException {
Assert.notNull(clazz, "Class required");
Assert.hasText(fieldName, "Field name required");
try {
return clazz.getDeclaredField(fieldName);
}
catch (NoSuchFieldException ex) {
// Try superclass
if (clazz.getSuperclass() != null) {
return getField(clazz.getSuperclass(), fieldName);
}
throw new IllegalStateException("Could not locate field '" + fieldName + "' on class " + clazz);
}
}
The resetRolePrefix(Object source, Class<?> type)
gets called potentially for two object types: SecurityContextHolderAwareRequestWrapper
and SecurityContextHolderAwareRequestFilter
.
So we need to register reflection hints for these classes to make sure that fields information is available for the native image.
It's strange that we need to add hints for Spring Security classes. Sounds like we should create an issue on their end for them for a proper fix. But good in the short term.
Maybe not so strange. I suppose Spring never needs reflection on those classes
I chatted with them on Twitter and they said we can create an issue as they normally would have the hints already added in the framework.
But maybe it's that we use Spring Security in a non-standard way in this instance? I couldn't see the issue with a project from start.spring.io
My opinion is that we need the hint on our side because we are accessing a field on the spring classes by using reflection. I suppose Spring itself does not need to use reflection on those classes, so they do not need to add hints by default.