Overview
Esta guía le muestra cómo integrar MongoDB Cifrado a nivel de campo del lado del cliente(CSFLE) con Spring Data MongoDB.
Nota
Si no ha trabajado con CSFLE antes, consulte la guía de tutoriales de CSFLE en el manual del servidor MongoDB.
Si no ha trabajado con Spring Data MongoDB antes, consulte la documentación de Spring Data MongoDB.
Repositorio de GitHub
Para ver el código fuente de esta guía, consulte el repositorio mongodb-java-spring-boot-csfle en GitHub.
También puede recuperar el código fuente de su repositorio de GitHub ejecutando el siguiente comando:
git clone git@github.com:mongodb-developer/mongodb-java-spring-boot-csfle.git
Requisitos previos
Antes de utilizar esta guía, realice los siguientes pasos:
Instalar Java 17
Instalar la biblioteca compartida de cifrado automático de MongoDB v7.0.2 o posterior
Revise el archivo README.md para obtener información sobre cómo ejecutar el código de ejemplo.
También necesita acceso a un clúster MongoDB que ejecute MongoDB v7.0.2 o posterior.
Características de ejemplo
Este ejemplo proporciona código con plantilla para un entorno de producción e incluye las siguientes características:
Múltiples colecciones cifradas
Generación automatizada de esquemas JSON
Esquema JSON del lado del servidor
Clústeres separados para claves de cifrado de datos (DEK) y colecciones cifradas
Generación o recuperación automatizada de DEK
Extensión de evaluación del lenguaje de expresión Spring (SpEL)
Repositorios implementados automáticamente
Documentación de OpenAPI 3.0.1
La plantilla sigue los principios SOLID para facilitar la lectura y la reutilización del código. Para obtener más información sobre SOLID, consulte SOLID en Wikipedia.
Diagramas de proyectos
El siguiente diagrama muestra los componentes necesarios para crear un sistema habilitado para CSFLE. MongoClient Que puede cifrar y descifrar campos automáticamente. Las flechas del diagrama muestran las dependencias y relaciones entre los componentes.

Las flechas en el diagrama muestran dependencias y relaciones entre componentes.
Una vez que la aplicación establece una conexión con MongoDB, utiliza una arquitectura de tres niveles para exponer una API REST y administrar la comunicación con la base de datos MongoDB, como se muestra en el siguiente diagrama:

Crear la colección Key Vault
Esta sección le muestra cómo crear la colección de almacén de claves y su índice único.
Si está comenzando desde un nuevo clúster de MongoDB, primero debe crear la colección de almacén de claves y su índice único en el campo keyAltNames.
Configurar Key Vault y DEK
La clase KeyVaultAndDekSetup crea la colección de almacén de claves y su índice único mediante una conexión estándar a MongoDB. A continuación, crea las DEK necesarias para cifrar los documentos de cada colección cifrada.
El siguiente código muestra la clase KeyVaultAndDekSetup:
public class KeyVaultAndDekSetup { private static final Logger LOGGER = LoggerFactory.getLogger(KeyVaultAndDekSetup.class); private final KeyVaultService keyVaultService; private final DataEncryptionKeyService dataEncryptionKeyService; private String CONNECTION_STR; public KeyVaultAndDekSetup(KeyVaultService keyVaultService, DataEncryptionKeyService dataEncryptionKeyService) { this.keyVaultService = keyVaultService; this.dataEncryptionKeyService = dataEncryptionKeyService; } public void postConstruct() { LOGGER.info("=> Start Encryption Setup."); LOGGER.debug("=> MongoDB Connection String: {}", CONNECTION_STR); MongoClientSettings mcs = MongoClientSettings.builder() .applyConnectionString(new ConnectionString(CONNECTION_STR)) .build(); try (MongoClient client = MongoClients.create(mcs)) { LOGGER.info("=> Created the MongoClient instance for the encryption setup."); LOGGER.info("=> Creating the encryption key vault collection."); keyVaultService.setupKeyVaultCollection(client); LOGGER.info("=> Creating the Data Encryption Keys."); EncryptedCollectionsConfiguration.encryptedEntities.forEach(dataEncryptionKeyService::createOrRetrieveDEK); LOGGER.info("=> Encryption Setup completed."); } catch (Exception e) { LOGGER.error("=> Encryption Setup failed: {}", e.getMessage(), e); } } }
Importante
En producción, puede crear manualmente la colección de almacén de claves y su índice único en el campo keyAltNames. Después de crearlos, puede eliminar este código.
Este código utiliza una conexión MongoClient estándar (no habilitada para CSFLE) y temporal en un bloque trycon recursos para crear una colección y un índice en el clúster MongoDB.
Crear la colección Key Vault
La clase KeyVaultServiceImpl crea la colección de almacén de claves y el índice único keyAltNames.
El siguiente código muestra la clase KeyVaultServiceImpl:
public class KeyVaultServiceImpl implements KeyVaultService { private static final Logger LOGGER = LoggerFactory.getLogger(KeyVaultServiceImpl.class); private static final String INDEX_NAME = "uniqueKeyAltNames"; private String KEY_VAULT_DB; private String KEY_VAULT_COLL; public void setupKeyVaultCollection(MongoClient mongoClient) { LOGGER.info("=> Setup the key vault collection {}.{}", KEY_VAULT_DB, KEY_VAULT_COLL); MongoDatabase db = mongoClient.getDatabase(KEY_VAULT_DB); MongoCollection<Document> vault = db.getCollection(KEY_VAULT_COLL); boolean vaultExists = doesCollectionExist(db, KEY_VAULT_COLL); if (vaultExists) { LOGGER.info("=> Vault collection already exists."); if (!doesIndexExist(vault)) { LOGGER.info("=> Unique index created on the keyAltNames"); createKeyVaultIndex(vault); } } else { LOGGER.info("=> Creating a new vault collection & index on keyAltNames."); createKeyVaultIndex(vault); } } private void createKeyVaultIndex(MongoCollection<Document> vault) { Bson keyAltNamesExists = exists("keyAltNames"); IndexOptions indexOpts = new IndexOptions().name(INDEX_NAME) .partialFilterExpression(keyAltNamesExists) .unique(true); vault.createIndex(new BsonDocument("keyAltNames", new BsonInt32(1)), indexOpts); } private boolean doesCollectionExist(MongoDatabase db, String coll) { return db.listCollectionNames().into(new ArrayList<>()).stream().anyMatch(c -> c.equals(coll)); } private boolean doesIndexExist(MongoCollection<Document> coll) { return coll.listIndexes() .into(new ArrayList<>()) .stream() .map(i -> i.get("name")) .anyMatch(n -> n.equals(INDEX_NAME)); } }
Después de crear la colección y el índice del almacén de claves, puede cerrar la conexión estándar de MongoDB.
Crear las claves de cifrado de datos
Esta sección le muestra cómo crear las DEK mediante la conexión ClientEncryption.
Configurar el cliente de Key Vault
La clase MongoDBKeyVaultClientConfiguration crea un bean ClientEncryption que DataEncryptionKeyService utiliza para crear los DEK.
El siguiente código muestra la clase MongoDBKeyVaultClientConfiguration:
public class MongoDBKeyVaultClientConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(MongoDBKeyVaultClientConfiguration.class); private final KmsService kmsService; private String CONNECTION_STR; private String KEY_VAULT_DB; private String KEY_VAULT_COLL; private MongoNamespace KEY_VAULT_NS; public MongoDBKeyVaultClientConfiguration(KmsService kmsService) { this.kmsService = kmsService; } public void postConstructor() { this.KEY_VAULT_NS = new MongoNamespace(KEY_VAULT_DB, KEY_VAULT_COLL); } /** * MongoDB Encryption Client that can manage Data Encryption Keys (DEKs). * * @return ClientEncryption MongoDB connection that can create or delete DEKs. */ public ClientEncryption clientEncryption() { LOGGER.info("=> Creating the MongoDB Key Vault Client."); MongoClientSettings mcs = MongoClientSettings.builder() .applyConnectionString(new ConnectionString(CONNECTION_STR)) .build(); ClientEncryptionSettings ces = ClientEncryptionSettings.builder() .keyVaultMongoClientSettings(mcs) .keyVaultNamespace(KEY_VAULT_NS.getFullName()) .kmsProviders(kmsService.getKmsProviders()) .build(); return ClientEncryptions.create(ces); } }
Puedes crear un ClientEncryption bean mediante el Sistema de Gestión de Claves (KMS) y usarlo para generar las DEK. Necesitas una DEK por cada colección cifrada.
Implementar el servicio de clave de cifrado de datos
La clase DataEncryptionKeyServiceImpl crea y almacena las DEK. Al evaluar las expresiones SpEL en las entidades para crear los esquemas JSON, debe recuperar las DEK.
El siguiente código muestra la clase DataEncryptionKeyServiceImpl:
public class DataEncryptionKeyServiceImpl implements DataEncryptionKeyService { private static final Logger LOGGER = LoggerFactory.getLogger(DataEncryptionKeyServiceImpl.class); private final ClientEncryption clientEncryption; private final Map<String, String> dataEncryptionKeysB64 = new HashMap<>(); private String KMS_PROVIDER; public DataEncryptionKeyServiceImpl(ClientEncryption clientEncryption) { this.clientEncryption = clientEncryption; } public Map<String, String> getDataEncryptionKeysB64() { LOGGER.info("=> Getting Data Encryption Keys Base64 Map."); LOGGER.info("=> Keys in DEK Map: {}", dataEncryptionKeysB64.entrySet()); return dataEncryptionKeysB64; } public String createOrRetrieveDEK(EncryptedEntity encryptedEntity) { Base64.Encoder b64Encoder = Base64.getEncoder(); String dekName = encryptedEntity.getDekName(); BsonDocument dek = clientEncryption.getKeyByAltName(dekName); BsonBinary dataKeyId; if (dek == null) { LOGGER.info("=> Creating Data Encryption Key: {}", dekName); DataKeyOptions dko = new DataKeyOptions().keyAltNames(of(dekName)); dataKeyId = clientEncryption.createDataKey(KMS_PROVIDER, dko); LOGGER.debug("=> DEK ID: {}", dataKeyId); } else { LOGGER.info("=> Existing Data Encryption Key: {}", dekName); dataKeyId = dek.get("_id").asBinary(); LOGGER.debug("=> DEK ID: {}", dataKeyId); } String dek64 = b64Encoder.encodeToString(dataKeyId.getData()); LOGGER.debug("=> Base64 DEK ID: {}", dek64); LOGGER.info("=> Adding Data Encryption Key to the Map with key: {}", encryptedEntity.getEntityClass().getSimpleName()); dataEncryptionKeysB64.put(encryptedEntity.getEntityClass().getSimpleName(), dek64); return dek64; } }
Nota
Debido a que los DEK se almacenan en un mapa, no es necesario recuperarlos nuevamente más tarde para los esquemas JSON.
Definir entidades
Spring Data MongoDB utiliza un modelo centrado en POJO para implementar los repositorios y mapear los documentos a las colecciones de MongoDB.
Crear la entidad Persona
El siguiente código muestra la clase PersonEntity:
public class PersonEntity { private ObjectId id; private String firstName; private String lastName; private String ssn; private String bloodType; public PersonEntity() { } public PersonEntity(ObjectId id, String firstName, String lastName, String ssn, String bloodType) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.ssn = ssn; this.bloodType = bloodType; } public String toString() { return "PersonEntity{" + "id=" + id + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", ssn='" + ssn + '\'' + ", bloodType='" + bloodType + '\'' + '}'; } public ObjectId getId() { return id; } public void setId(ObjectId id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getSsn() { return ssn; } public void setSsn(String ssn) { this.ssn = ssn; } public String getBloodType() { return bloodType; } public void setBloodType(String bloodType) { this.bloodType = bloodType; } }
La clase anterior contiene toda la información necesaria para automatizar CSFLE. La clase utiliza la anotación @Encrypted de las siguientes maneras:
La anotación de clase
@Encryptedcontiene la expresión SpEL#{mongocrypt.keyId(#target)}. Esta expresión especifica elkeyIdde la DEK generada o recuperada en un paso anterior. CSFLE utiliza esta DEK para el cifrado.La anotación
@Encrypteden el campossnespecifica que requiere un algoritmo determinista.La anotación
@Encrypteden el campobloodTypeespecifica que requiere un algoritmo aleatorio.
El esquema JSON generado por Spring Data MongoDB para esta entidad es el siguiente:
{ "encryptMetadata": { "keyId": [ { "$binary": { "base64": "WyHXZ+53SSqCC/6WdCvp0w==", "subType": "04" } } ] }, "type": "object", "properties": { "ssn": { "encrypt": { "bsonType": "string", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" } }, "bloodType": { "encrypt": { "bsonType": "string", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" } } } }
Configurar la evaluación SpEL
La clase EntitySpelEvaluationExtension permite la evaluación de la expresión SpEL. El siguiente código muestra esta clase:
public class EntitySpelEvaluationExtension implements EvaluationContextExtension { private static final Logger LOGGER = LoggerFactory.getLogger(EntitySpelEvaluationExtension.class); private final DataEncryptionKeyService dataEncryptionKeyService; public EntitySpelEvaluationExtension(DataEncryptionKeyService dataEncryptionKeyService) { this.dataEncryptionKeyService = dataEncryptionKeyService; } public String getExtensionId() { return "mongocrypt"; } public Map<String, Function> getFunctions() { try { return Collections.singletonMap("keyId", new Function( EntitySpelEvaluationExtension.class.getMethod("computeKeyId", String.class), this)); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } public String computeKeyId(String target) { String dek = dataEncryptionKeyService.getDataEncryptionKeysB64().get(target); LOGGER.info("=> Computing dek for target {} => {}", target, dek); return dek; } }
Nota
Esta clase recupera las DEK y las compara con target. En este ejemplo, target es PersonEntity.
Generar esquemas JSON y configurar MongoClient
Las siguientes secciones muestran cómo generar esquemas JSON en un proyecto Spring Data MongoDB.
Comprender el proceso de generación de esquemas
Para generar los esquemas JSON, se necesitan las entidades MappingContext creadas mediante la configuración automática de Spring Data. Esta configuración automática crea la conexión MongoClient y la MongoTemplate.
Sin embargo, para crear el MongoClient con el cifrado automático habilitado, necesita esquemas JSON.
La solución es inyectar la creación del esquema JSON en el proceso de autoconfiguración instanciando el bean MongoClientSettingsBuilderCustomizer.
Configurar el cliente seguro de MongoDB
El siguiente código muestra la clase MongoDBSecureClientConfiguration:
public class MongoDBSecureClientConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(MongoDBSecureClientConfiguration.class); private final KmsService kmsService; private final SchemaService schemaService; private String CRYPT_SHARED_LIB_PATH; private String CONNECTION_STR_DATA; private String CONNECTION_STR_VAULT; private String KEY_VAULT_DB; private String KEY_VAULT_COLL; private MongoNamespace KEY_VAULT_NS; public MongoDBSecureClientConfiguration(KmsService kmsService, SchemaService schemaService) { this.kmsService = kmsService; this.schemaService = schemaService; } public void postConstruct() { this.KEY_VAULT_NS = new MongoNamespace(KEY_VAULT_DB, KEY_VAULT_COLL); } public MongoClientSettings mongoClientSettings() { LOGGER.info("=> Creating the MongoClientSettings for the encrypted collections."); return MongoClientSettings.builder().applyConnectionString(new ConnectionString(CONNECTION_STR_DATA)).build(); } public MongoClientSettingsBuilderCustomizer customizer(MappingContext mappingContext) { LOGGER.info("=> Creating the MongoClientSettingsBuilderCustomizer."); return builder -> { MongoJsonSchemaCreator schemaCreator = MongoJsonSchemaCreator.create(mappingContext); Map<String, BsonDocument> schemaMap = schemaService.generateSchemasMap(schemaCreator) .entrySet() .stream() .collect(toMap(e -> e.getKey().getFullName(), Map.Entry::getValue)); Map<String, Object> extraOptions = Map.of("cryptSharedLibPath", CRYPT_SHARED_LIB_PATH, "cryptSharedLibRequired", true); MongoClientSettings mcs = MongoClientSettings.builder() .applyConnectionString( new ConnectionString(CONNECTION_STR_VAULT)) .build(); AutoEncryptionSettings oes = AutoEncryptionSettings.builder() .keyVaultMongoClientSettings(mcs) .keyVaultNamespace(KEY_VAULT_NS.getFullName()) .kmsProviders(kmsService.getKmsProviders()) .schemaMap(schemaMap) .extraOptions(extraOptions) .build(); builder.autoEncryptionSettings(oes); }; } }
Tip
Si desea utilizar diferentes políticas de retención de copias de seguridad para distintos clústeres, puede almacenar una DEK en cada clúster. Esto le permite borrar completamente una DEK de un clúster y todas las copias de seguridad, lo cual podría ser necesario para el cumplimiento legal.
Para obtenermás información, consulte CSFLE con el controlador Java.
Implementar el servicio de esquema
El siguiente código muestra la clase SchemaServiceImpl, que almacena los esquemas JSON generados en un mapa:
public class SchemaServiceImpl implements SchemaService { private static final Logger LOGGER = LoggerFactory.getLogger(SchemaServiceImpl.class); private Map<MongoNamespace, BsonDocument> schemasMap; public Map<MongoNamespace, BsonDocument> generateSchemasMap(MongoJsonSchemaCreator schemaCreator) { LOGGER.info("=> Generating schema map."); List<EncryptedEntity> encryptedEntities = EncryptedCollectionsConfiguration.encryptedEntities; return schemasMap = encryptedEntities.stream() .collect(toMap(EncryptedEntity::getNamespace, e -> generateSchema(schemaCreator, e.getEntityClass()))); } public Map<MongoNamespace, BsonDocument> getSchemasMap() { return schemasMap; } private BsonDocument generateSchema(MongoJsonSchemaCreator schemaCreator, Class<?> entityClass) { BsonDocument schema = schemaCreator.filter(MongoJsonSchemaCreator.encryptedOnly()) .createSchemaFor(entityClass) .schemaDocument() .toBsonDocument(); LOGGER.info("=> JSON Schema for {}:\n{}", entityClass.getSimpleName(), schema.toJson(JsonWriterSettings.builder().indent(true).build())); return schema; } }
La aplicación almacena sus esquemas JSON porque esta plantilla también implementa esquemas JSON del lado del servidor. Esta es una práctica recomendada para CSFLE.
Crear o actualizar colecciones cifradas
Comprender los esquemas JSON del lado del servidor
Solo se requieren los esquemas JSON del lado del cliente para la biblioteca compartida de cifrado automático. Sin embargo, sin los esquemas JSON del lado del servidor, otro cliente mal configurado o un administrador conectado directamente al clúster podría insertar o actualizar documentos sin cifrar los campos.
Para evitar esto, puede utilizar el esquema JSON del lado del servidor para imponer un tipo de campo en un documento.
El esquema JSON cambia con las diferentes versiones de su aplicación, por lo que es necesario actualizarlo cada vez que reinicie su aplicación.
Implementar la configuración de colecciones cifradas
El siguiente código muestra la clase EncryptedCollectionsSetup:
public class EncryptedCollectionsSetup { private static final Logger LOGGER = LoggerFactory.getLogger(EncryptedCollectionsSetup.class); private final MongoClient mongoClient; private final SchemaService schemaService; public EncryptedCollectionsSetup(MongoClient mongoClient, SchemaService schemaService) { this.mongoClient = mongoClient; this.schemaService = schemaService; } public void postConstruct() { LOGGER.info("=> Setup the encrypted collections."); schemaService.getSchemasMap() .forEach((namespace, schema) -> createOrUpdateCollection(mongoClient, namespace, schema)); } private void createOrUpdateCollection(MongoClient mongoClient, MongoNamespace ns, BsonDocument schema) { MongoDatabase db = mongoClient.getDatabase(ns.getDatabaseName()); String collStr = ns.getCollectionName(); if (doesCollectionExist(db, ns)) { LOGGER.info("=> Updating {} collection's server side JSON Schema.", ns.getFullName()); db.runCommand(new Document("collMod", collStr).append("validator", jsonSchemaWrapper(schema))); } else { LOGGER.info("=> Creating encrypted collection {} with server side JSON Schema.", ns.getFullName()); db.createCollection(collStr, new CreateCollectionOptions().validationOptions( new ValidationOptions().validator(jsonSchemaWrapper(schema)))); } } public BsonDocument jsonSchemaWrapper(BsonDocument schema) { return new BsonDocument("$jsonSchema", schema); } private boolean doesCollectionExist(MongoDatabase db, MongoNamespace ns) { return db.listCollectionNames() .into(new ArrayList<>()) .stream() .anyMatch(c -> c.equals(ns.getCollectionName())); } }
Admite múltiples entidades
La plantilla de esta guía puede admitir cualquier número de entidades.
Para agregar otro tipo de entidad, cree los componentes de la arquitectura de tres niveles como se describe en los pasos anteriores. Luego, agregue la entidad a la clase EncryptedCollectionsConfiguration, como se muestra en el siguiente ejemplo:
public class EncryptedCollectionsConfiguration { public static final List<EncryptedEntity> encryptedEntities = List.of( new EncryptedEntity("mydb", "persons", PersonEntity.class, "personDEK"), new EncryptedEntity("mydb", "companies", CompanyEntity.class, "companyDEK")); }
Debido a que agregó la anotación @Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic") a los campos en la clase de entidad, el marco utiliza el esquema JSON del lado del servidor para generar automáticamente las DEK y crear la colección cifrada.
Consulta por un campo cifrado
Esta plantilla implementa el método findFirstBySsn(ssn). Puede usar este método para buscar el documento de una persona por su número de Seguro Social, incluso si este campo está cifrado.
Nota
Este método funciona únicamente porque se utiliza un algoritmo de cifrado determinista.
Implementar el repositorio de personas
El siguiente código muestra la interfaz PersonRepository:
public interface PersonRepository extends MongoRepository<PersonEntity, String> { PersonEntity findFirstBySsn(String ssn); }
Recursos adicionales
Si tiene preguntas, abra un problema en el repositorio mongodb-java-spring-boot-csfle o haga una pregunta en el Foro de la comunidad MongoDB.
Para obtener más información sobre CSFLE, consulte los siguientes recursos:
CSFLE con el controlador Java (sin Spring Data)
Para obtener más información sobre Spring Data MongoDB, consulte los siguientes recursos:
Puedes ver una versión de esta guía en formato de vídeo en el canal de YouTube de MongoDB.