Closed
Description
Hi all,
I am currently trying to implement explicit client side field level encryption according to https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo.encryption.explicit
It works when I annotate simple types like String. However if I annotate a custom "Address" like type encryption works and my object is saved encrypted to mongo db. Although trying to read it results in a ClassCastException
in the MongoEncryptionConverter
:
java.lang.ClassCastException: class org.bson.Document cannot be cast to class org.bson.BsonValue (org.bson.Document and org.bson.BsonValue are in unnamed module of loader 'app')
at org.springframework.data.mongodb.core.convert.encryption.MongoEncryptionConverter.lambda$decrypt$1(MongoEncryptionConverter.java:102)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.springframework.data.mongodb.core.convert.encryption.MongoEncryptionConverter.decrypt(MongoEncryptionConverter.java:101)
at org.springframework.data.mongodb.core.convert.encryption.EncryptingConverter.read(EncryptingConverter.java:32)
at org.springframework.data.mongodb.core.convert.encryption.MongoEncryptionConverter.read(MongoEncryptionConverter.java:65)
at org.springframework.data.mongodb.core.convert.encryption.MongoEncryptionConverter.read(MongoEncryptionConverter.java:48)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1920)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$AssociationAwareMongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1983)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$AssociationAwareMongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1944)
at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:71)
at org.springframework.data.mapping.model.SpELExpressionParameterValueProvider.getParameterValue(SpELExpressionParameterValueProvider.java:49)
at org.springframework.data.mapping.model.KotlinClassGeneratingEntityInstantiator$DefaultingKotlinClassInstantiatorAdapter.extractInvocationArguments(KotlinClassGeneratingEntityInstantiator.java:222)
at org.springframework.data.mapping.model.KotlinClassGeneratingEntityInstantiator$DefaultingKotlinClassInstantiatorAdapter.createInstance(KotlinClassGeneratingEntityInstantiator.java:196)
at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:98)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:491)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readDocument(MappingMongoConverter.java:459)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:395)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:391)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:102)
at org.springframework.data.mongodb.core.MongoTemplate$ReadDocumentCallback.doWith(MongoTemplate.java:3217)
at org.springframework.data.mongodb.core.MongoTemplate.executeFindMultiInternal(MongoTemplate.java:2851)
at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:2556)
at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:2537)
at org.springframework.data.mongodb.core.MongoTemplate.find(MongoTemplate.java:865)
at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.findAll(SimpleMongoRepository.java:354)
at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.findAll(SimpleMongoRepository.java:136)
at jdk.internal.reflect.GeneratedMethodAccessor36.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:288)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:136)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:120)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:516)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:168)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:72)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.repository.core.support.MethodInvocationValidator.invoke(MethodInvocationValidator.java:94)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:244)
at jdk.proxy3/jdk.proxy3.$Proxy154.findAll(Unknown Source)
at jdk.internal.reflect.GeneratedMethodAccessor35.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:244)
at jdk.proxy3/jdk.proxy3.$Proxy154.findAll(Unknown Source)
Probably relevant code parts:
@Configuration
class ClientEncryptionConfig(private val masterKeyProvider: MasterKeyProvider) {
@Bean
fun encryptionClient(): ClientEncryption {
val clientEncryptionSettings = ClientEncryptionSettings.builder()
.keyVaultMongoClientSettings(
MongoClientSettings.builder()
.applyConnectionString(MongoConfig.connectionString)
.build()
)
.keyVaultNamespace(MongoConfig.keyVaultNamespace)
.kmsProviders(masterKeyProvider.kmsProviders())
.build()
return ClientEncryptions.create(clientEncryptionSettings)
}
}
@Configuration
class MongoConfig(private val clientEncryption: ClientEncryption, private val masterKeyProvider: MasterKeyProvider, private val appContext: ApplicationContext): AbstractMongoClientConfiguration(){
companion object {
val vaultCollectionName = "__keyVault"
val dbName = "databaseName"
val keyVaultNamespace = "$dbName.$vaultCollectionName"
val connectionString = ConnectionString("connectionString")
}
@Bean
override fun mongoClient(): MongoClient {
val autoEncryptionSettings = AutoEncryptionSettings.builder()
.keyVaultNamespace(keyVaultNamespace)
.kmsProviders(masterKeyProvider.kmsProviders())
.bypassAutoEncryption(true)
.build()
return MongoClients.create(
MongoClientSettings.builder()
.applyConnectionString(connectionString)
.autoEncryptionSettings(autoEncryptionSettings)
.build()
)
}
@Bean
fun encryptingConverter(): MongoEncryptionConverter{
val encryption = MongoClientEncryption.just(clientEncryption)
val keyResolver = EncryptionKeyResolver.annotated{ _ -> EncryptionKey.keyAltName("demo-data-key")}
return MongoEncryptionConverter(encryption, keyResolver)
}
override fun configureConverters(converterConfigurationAdapter: MongoCustomConversions.MongoConverterConfigurationAdapter) {
converterConfigurationAdapter.registerPropertyValueConverterFactory(PropertyValueConverterFactory.beanFactoryAware(appContext))
}
override fun getDatabaseName(): String {
return "databaseName"
}
}
Here reading (or decrypting) fails with the mentioned ClassCastException
@Document
data class TestDocument(
val creationDate: Instant,
@ExplicitEncrypted(algorithm = EncryptionAlgorithms.AEAD_AES_256_CBC_HMAC_SHA_512_Random, keyAltName = "demo-data-key") val addresses: List<AddressDocument>
This works:
@Document
data class AddressDocument(
@ExplicitEncrypted(algorithm = EncryptionAlgorithms.AEAD_AES_256_CBC_HMAC_SHA_512_Random, keyAltName = "demo-data-key") private val zip: String,
@ExplicitEncrypted(algorithm = EncryptionAlgorithms.AEAD_AES_256_CBC_HMAC_SHA_512_Random, keyAltName = "demo-data-key") private val city: String)
Sorry if you are also scanning stackoverflow, then this is probably redundant. However I am not sure if I stumbled upon a bug or if I just misconfigured something.