Search code examples
spring-bootspring-securityspring-cloudgraalvmgraalvm-native-image

kotlin.reflect.jvm.internal.KotlinReflectionInternalError when running a native image with Spring Cloud Gateway and Spring Security


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?


Solution

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