I've successfully generated a native image of a Spring Cloud Gateway (2022.0.0 - Spring Boot 3.0.0) application implemented in Kotlin.
I have the following security configuration:
@EnableReactiveMethodSecurity
@Configuration(proxyBeanMethods = false)
class SecurityConfiguration(private val clientService: ClientService,
private val env: Environment) {
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http.requestCache {
it.requestCache(NoOpServerRequestCache.getInstance())
}.headers { headers ->
headers.frameOptions { frameOptions ->
frameOptions.disable()
}
}.csrf { it.disable() }
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
.addFilterAt(basicAuthenticationFilter(), SecurityWebFiltersOrder.HTTP_BASIC)
.authorizeExchange()
.pathMatchers("/actuator/health").permitAll()
...
.and()
.build()
}
@Bean
fun passwordEncoder(): PasswordEncoder {
return createDelegatingPasswordEncoder()
}
private fun basicAuthenticationFilter(): AuthenticationWebFilter {
val authManager = ApiAuthenticationManager(clientService)
val apiAuthenticationFilter = AuthenticationWebFilter(authManager)
return apiAuthenticationFilter
}
}
When I run the native image it crashes with the following exception:
kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Could not compute caller for function: private open fun basicAuthenticationFilter(): org.springframework.security.web.server.authentication.AuthenticationWebFilter defined in xxx.configuration.SecurityConfiguration[DeserializedSimpleFunctionDescriptor@3ff718f] (member = null)
at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:88)
at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:61)
at kotlin.reflect.jvm.internal.ReflectProperties$LazyVal.invoke(ReflectProperties.java:63)
at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32)
at kotlin.reflect.jvm.internal.KFunctionImpl.getCaller(KFunctionImpl.kt:61)
at kotlin.reflect.jvm.ReflectJvmMapping.getJavaMethod(ReflectJvmMapping.kt:63)
at kotlin.reflect.jvm.ReflectJvmMapping.getKotlinFunction(ReflectJvmMapping.kt:136)
at org.springframework.core.MethodParameter$KotlinDelegate.getGenericReturnType(MethodParameter.java:914)
at org.springframework.core.MethodParameter.getGenericParameterType(MethodParameter.java:510)
at org.springframework.core.SerializableTypeWrapper$MethodParameterTypeProvider.getType(SerializableTypeWrapper.java:291)
at org.springframework.core.SerializableTypeWrapper.forTypeProvider(SerializableTypeWrapper.java:107)
at org.springframework.core.ResolvableType.forType(ResolvableType.java:1413)
at org.springframework.core.ResolvableType.forMethodParameter(ResolvableType.java:1334)
at org.springframework.core.ResolvableType.forMethodParameter(ResolvableType.java:1316)
at org.springframework.core.ResolvableType.forMethodParameter(ResolvableType.java:1283)
at org.springframework.core.ResolvableType.forMethodReturnType(ResolvableType.java:1228)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryMethod(AbstractAutowireCapableBeanFactory.java:814)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineTargetType(AbstractAutowireCapableBeanFactory.java:681)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:652)
at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1632)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:559)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:531)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:106)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:745)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:565)
at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:432)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291)
at xxx.GatewayApplicationKt.main(GatewayApplication.kt:27)
Exception in thread "main" kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Could not compute caller for function: private open fun basicAuthenticationFilter(): org.springframework.security.web.server.authentication.AuthenticationWebFilter defined in xxx.configuration.SecurityConfiguration[DeserializedSimpleFunctionDescriptor@3ff718f] (member = null)
This call .addFilterAt(basicAuthenticationFilter(), SecurityWebFiltersOrder.HTTP_BASIC)
, that refers to a private function seems to be involving some kind of Kotlin refection.
Any ideas to fix this?
Spring Security uses reflection to get the class of a custom filter, in order to keep track of the order in the filter chain.
To run the code as a native image a reflection hint is needed to tell GraalVM that the private method basicAuthenticationFilter
needs to be available in the native image.
You can add custom hints using the RuntimeHintsRegistrar
. In this case you can register basicAuthenticationFilter
for reflection using:
public class MyRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
Method method = ReflectionUtils.findMethod(SecurityConfiguration.class, "basicAuthenticationFilter"
hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
}
}