diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoJsonSchemaCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoJsonSchemaCreator.java index a37eef724..5e5bc5064 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoJsonSchemaCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoJsonSchemaCreator.java @@ -19,6 +19,7 @@ import java.util.HashSet; import java.util.Set; import java.util.function.Predicate; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; @@ -29,6 +30,7 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; +import org.springframework.data.mongodb.core.mapping.Unwrapped.Nullable; import org.springframework.data.mongodb.core.schema.JsonSchemaProperty; import org.springframework.data.mongodb.core.schema.MongoJsonSchema; import org.springframework.util.Assert; @@ -60,6 +62,7 @@ import org.springframework.util.Assert; * {@link org.bson.types.ObjectId} like {@link String} will be mapped to {@code type : 'object'} unless there is more * specific information available via the {@link org.springframework.data.mongodb.core.mapping.MongoId} annotation. *
+ * {@link Encrypted} properties will contain {@literal encrypt} information. * * @author Christoph Strobl * @since 2.2 @@ -83,21 +86,43 @@ public interface MongoJsonSchemaCreator { */ MongoJsonSchemaCreator filter(Predicate
+ * @Document
* @Encrypted(keyId = "4fPYFM9qSgyRAjgQ2u+IMQ==")
* public class Patient {
* private ObjectId id;
@@ -85,12 +86,27 @@ import java.lang.annotation.Target;
public @interface Encrypted {
/**
- * @return the key id to use. May contain a parsable {@link org.springframework.expression.Expression expression}.
+ * Get the {@code keyId} to use. The value must resolve to either the UUID representation of the key or a base64
+ * encoded value representing the UUID value.
+ *
+ * On {@link ElementType#TYPE} level the {@link #keyId()} can be left empty if explicitly set for fields.
+ * On {@link ElementType#FIELD} level the {@link #keyId()} can be left empty if inherited from
+ * {@literal encryptMetadata}.
+ *
+ * @return the key id to use. May contain a parsable {@link org.springframework.expression.Expression expression}. In
+ * this case the {@code #target} variable will hold the target element name.
*/
String[] keyId() default {};
/**
- * @return the algorithm.
+ * Set the algorithm to use.
+ *
+ * On {@link ElementType#TYPE} level the {@link #algorithm()} can be left empty if explicitly set for fields.
+ * On {@link ElementType#FIELD} level the {@link #algorithm()} can be left empty if inherited from
+ * {@literal encryptMetadata}.
+ *
+ * @return the encryption algorithm.
+ * @see org.springframework.data.mongodb.core.EncryptionAlgorithms
*/
String algorithm() default "";
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/MongoJsonSchema.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/MongoJsonSchema.java
index 8c4f7d64b..a14cde2d3 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/MongoJsonSchema.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/MongoJsonSchema.java
@@ -79,6 +79,7 @@ public interface MongoJsonSchema {
* {@link org.springframework.data.mongodb.core.convert.JsonSchemaMapper} to apply field name customization.
*
* @return never {@literal null}.
+ * @since 3.3
*/
Document schemaDocument();
diff --git a/src/main/asciidoc/reference/mongo-json-schema.adoc b/src/main/asciidoc/reference/mongo-json-schema.adoc
index 5a426061a..1b3c6ec48 100644
--- a/src/main/asciidoc/reference/mongo-json-schema.adoc
+++ b/src/main/asciidoc/reference/mongo-json-schema.adoc
@@ -225,6 +225,110 @@ MongoJsonSchema schema = MongoJsonSchema.builder()
----
====
+Instead of defining encrypted fields manually it is possible leverage the `@Encrypted` annotation as shown in the snippet below.
+
+.Client-Side Field Level Encryption via Json Schema
+====
+[source,java]
+----
+@Document
+@Encrypted(keyId = "xKVup8B1Q+CkHaVRx+qa+g==", algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Random") <1>
+static class Patient {
+
+ @Id String id;
+ String name;
+
+ @Encrypted <2>
+ String bloodType;
+
+ @Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic") <3>
+ Integer ssn;
+
+}
+----
+<1> Default encryption settings that will be set for `encryptMetadata`.
+<2> Encrypted field using default encryption settings.
+<3> Encrypted field overriding the default encryption algorithm.
+====
+
+[TIP]
+====
+The `@EncryptedAnnoation` supports resolving keyIds via SpEL Expressions.
+To do so additional environment metadata (via the `MappingContext`) is required and must be provided.
+
+[source,java]
+----
+@Document
+@Encrypted(keyId = "#{mongocrypt.keyId(#target)}")
+static class Patient {
+
+ @Id String id;
+ String name;
+
+ @Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Random")
+ String bloodType;
+
+ @Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
+ Integer ssn;
+}
+
+MongoJsonSchemaCreator schemaCreator = MongoJsonSchemaCreator.create(mappingContext);
+MongoJsonSchema personSchema = schemaCreator
+ .filter(MongoJsonSchemaCreator.encryptedOnly())
+ .createSchemaFor(Patient.class);
+----
+
+The `mongocrypt.keyId` function is defined via an `EvaluationContextExtension` as shown in the snippet below.
+Providing a custom extension provides the most flexible way of computing keyIds.
+
+[source,java]
+----
+public class EncryptionExtension implements EvaluationContextExtension {
+
+ @Override
+ public String getExtensionId() {
+ return "mongocrypt";
+ }
+
+ @Override
+ public Map getFunctions() {
+ return Collections.singletonMap("keyId", new Function(getMethod("computeKeyId", String.class), this));
+ }
+
+ public String computeKeyId(String target) {
+ // ... lookup via target element name
+ }
+}
+----
+
+To combine derived encryption settings with `AutoEncryptionSettings` in a Spring Boot application use the `MongoClientSettingsBuilderCustomizer`.
+
+[source,java]
+----
+@Bean
+MongoClientSettingsBuilderCustomizer customizer(MappingContext mappingContext) {
+ return (builder) -> {
+
+ // ... keyVaultCollection, kmsProvider, ...
+
+ MongoJsonSchemaCreator schemaCreator = MongoJsonSchemaCreator.create(mappingContext);
+ MongoJsonSchema patientSchema = schemaCreator
+ .filter(MongoJsonSchemaCreator.encryptedOnly())
+ .createSchemaFor(Patient.class);
+
+ AutoEncryptionSettings autoEncryptionSettings = AutoEncryptionSettings.builder()
+ .keyVaultNamespace(keyVaultCollection)
+ .kmsProviders(kmsProviders)
+ .extraOptions(extraOpts)
+ .schemaMap(Collections.singletonMap("db.patient", patientSchema.schemaDocument().toBsonDocument()))
+ .build();
+
+ builder.autoEncryptionSettings(autoEncryptionSettings);
+ };
+}
+----
+====
+
NOTE: Make sure to set the drivers `com.mongodb.AutoEncryptionSettings` to use client-side encryption. MongoDB does not support encryption for all field types. Specific data types require deterministic encryption to preserve equality comparison functionality.
[[mongo.jsonSchema.types]]