0

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?

codependent
  • 23,193
  • 31
  • 166
  • 308
  • Try using the `RuntimeHintsRegistrar` to register a custom hint. You can register the `basicAuthenticationFilter` for reflections using `hints.reflection().registerMethod(ReflectionUtils.findMethod(SecurityConfiguration.class, "basicAuthenticationFilter"), ExecutableMode.INVOKE)`. See the custom hints [documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html#native-image.advanced.custom-hints). – Eleftheria Stein-Kousathana Jan 02 '23 at 08:08
  • It worked. If you add your comment as an answer I'll mark it as accepted. – codependent Jan 02 '23 at 21:53

1 Answers1

1

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);
    }

}