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.
It looks like that the patch fixed the native compilation, but still, there's an issue with PWA icons at runtime
See https://vaadin.com/forum/t/graalvm-native-image-and-vaadinsecurityfilterchainbean/166290/6 (thanks @simasch for reporting)
java.lang.NoClassDefFoundError: Could not initialize class javax.imageio.ImageIO
at com.vaadin.flow.server.PwaRegistry.getBaseImage(PwaRegistry.java:369) ~[na:na]
at com.vaadin.flow.server.PwaRegistry.initializeResources(PwaRegistry.java:137) ~[na:na]
at com.vaadin.flow.server.PwaRegistry.<init>(PwaRegistry.java:113) ~[na:na]
at com.vaadin.flow.server.PwaRegistry.getInstance(PwaRegistry.java:300) ~[na:na]
at java.base@22/java.util.Optional.map(Optional.java:260) ~[jtaf4:na]
at com.vaadin.flow.server.VaadinServletService.getPwaRegistry(VaadinServletService.java:233) ~[jtaf4:24.4.0.beta5]
at com.vaadin.flow.server.VaadinService.lambda$createRequestHandlers$20741bb$1(VaadinService.java:340) ~[jtaf4:24.4.0.beta5]
at com.vaadin.flow.server.communication.PwaHandler.handleRequest(PwaHandler.java:140) ~[na:na]
at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1584) ~[jtaf4:24.4.0.beta5]
at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:398) ~[jtaf4:24.4.0.beta5]
at com.vaadin.flow.spring.SpringServlet.service(SpringServlet.java:106) ~[jtaf4:na]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[jtaf4:6.0]
The problem seems to be that call to javax.imageio.ImageIO.read() is PwaRegistry is not recognized/registered by native compilation, probably because of missing native registration for System.load() and/or System.(g|s)etProperty() (used to set the AWT headless property)
Once java.imageio.ImageIO problem is fixed, another issue arises. The login form header contents are not visible wiht the native image
A reflection hint is missing for com.vaadin.flow.component.login.LoginI18n$Header
This ticket/PR has been released with Vaadin 24.5.0.alpha1 and is also targeting the upcoming stable 24.5.0 version.
This ticket/PR has been released with Vaadin 24.3.14.