Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Junte-se a nós no Amazon Web Services re:Invent 2024! Saiba como usar o MongoDB para casos de uso de AI .
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
Javachevron-right

Como implementar a criptografia no nível do campo do lado do cliente (CSFLE) em Java com o Spring Data MongoDB

Maxime Beugnet, Megha Arora11 min read • Published Nov 06, 2023 • Updated Jan 27, 2024
SpringJava
APLICATIVO COMPLETO
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse exemplo de código
star-empty
star-empty
star-empty
star-empty
star-empty
social-githubVer código

Repositório do GitHub

O código-fonte deste modelo está disponível no GitHub:
1git clone git@github.com:mongodb-developer/mongodb-java-spring-boot-csfle.git
Para começar, você vai precisar de:
  • Java 17.
  • Um clusterMongoDB v7.0.2 ou superior.
Consulte o arquivoREADME.md para obter mais informações.

Vídeo

Este conteúdo também está disponível em formato de vídeo.

Introdução

Esta postagem explicará os principais detalhes da integração do MongoDB Client-Side Field Level Encryption (CSFLE) com Spring Data MongoDB.
No entanto, esta publicação não explicará a mecânica básica do CSFLE ou Spring Data MongoDB.
Se você achar que precisa de uma atualização sobre o CSFLE antes de trabalhar nessa peça mais complicada, posso recomendar alguns recursos para o CSFLE:
E para Spring Data MongoDB:
Esse modelo é significativamente maior do que outros modelos CSFLE on-line que você pode encontrar on-line. Ele tenta fornecer código reutilizável para um ambiente de produção real usando:
  • Múltiplas coleções criptografadas.
  • Geração automatizada de JSON schema.
  • Esquema JSON do lado do servidor.
  • Clusters separados para DEKs e collection criptografadas.
  • Geração ou recuperação automatizada de chaves de criptografia de dados.
  • Extensão de avaliação SpEL.
  • Repositórios implementados automaticamente.
  • Abra a documentação da API 3.0.1.
Enquanto codificava, também procurei respeitar os Princípios SOLID tanto quanto possível para aumentar a legibilidade, a usabilidade e a reutilização do código.

Diagramas de alto nível

Agora que estamos todos a bordo, aqui está um diagrama de alto nível das diferentes partes móveis necessárias para criar um MongoClient habilitado para CSFLE configurado corretamente que pode criptografar e descriptografar campos automaticamente.
Diagrama de alto nível do projeto
As setas podem significar coisas diferentes no diagrama:
  • "precisa ser feito antes"
  • "requer"
  • " dependência direta de "
Mas esperançosamente ajuda a explicar as dependências, a orquestração e o mecanismo interno da configuração CSFLE com Spring Data MongoDB.
Uma vez que a conexão com o MongoDB — capaz de criptografar e descriptografar os campos — é estabelecida, com a configuração e a biblioteca corretas, estamos apenas usando uma arquitetura clássica de três camadas para expor uma REST API e gerenciar a comunicação até o MongoDB database.
Arquitetura de três níveis
Aqui, não há nada complicado ou fascinante para discutir, portanto, não discutiremos isso nesta postagem.
Vamos agora nos concentrar em todas as partes complicadas deste modelo.

Criação da coleção de cofre de chaves

Como este é um tutorial, o código pode ser iniciado a partir de um cluster MongoDB em branco.
Portanto, o primeiro ponto de ordem é criar a key vault collection e seu índice único no campo keyAltNames.
1/**
2 * This class initialize the Key Vault (collection + keyAltNames unique index) using a dedicated standard connection
3 * to MongoDB.
4 * Then it creates the Data Encryption Keys (DEKs) required to encrypt the documents in each of the
5 * encrypted collections.
6 */
7@Component
8public class KeyVaultAndDekSetup {
9
10 private static final Logger LOGGER = LoggerFactory.getLogger(KeyVaultAndDekSetup.class);
11 private final KeyVaultService keyVaultService;
12 private final DataEncryptionKeyService dataEncryptionKeyService;
13 @Value("${spring.data.mongodb.vault.uri}")
14 private String CONNECTION_STR;
15
16 public KeyVaultAndDekSetup(KeyVaultService keyVaultService, DataEncryptionKeyService dataEncryptionKeyService) {
17 this.keyVaultService = keyVaultService;
18 this.dataEncryptionKeyService = dataEncryptionKeyService;
19 }
20
21 @PostConstruct
22 public void postConstruct() {
23 LOGGER.info("=> Start Encryption Setup.");
24 LOGGER.debug("=> MongoDB Connection String: {}", CONNECTION_STR);
25 MongoClientSettings mcs = MongoClientSettings.builder()
26 .applyConnectionString(new ConnectionString(CONNECTION_STR))
27 .build();
28 try (MongoClient client = MongoClients.create(mcs)) {
29 LOGGER.info("=> Created the MongoClient instance for the encryption setup.");
30 LOGGER.info("=> Creating the encryption key vault collection.");
31 keyVaultService.setupKeyVaultCollection(client);
32 LOGGER.info("=> Creating the Data Encryption Keys.");
33 EncryptedCollectionsConfiguration.encryptedEntities.forEach(dataEncryptionKeyService::createOrRetrieveDEK);
34 LOGGER.info("=> Encryption Setup completed.");
35 } catch (Exception e) {
36 LOGGER.error("=> Encryption Setup failed: {}", e.getMessage(), e);
37 }
38
39 }
40
41}
Na produção, você pode escolher criar a coleção do cofre de chaves e seu índice exclusivo no campokeyAltNamesmanualmente uma vez e remover o código, pois ele nunca mais será executado. Acho que só faz sentido mantê-lo se você estiver executando esse código em um pipeline de CI/CD.
Um aspecto importante a ser observado aqui é a dependência de um MongoClientcompletamente padrão (ou seja, não habilitado para CSFLE) e efêmero (uso de um bloco try-with-resources), pois já estamos criando uma coleção e um índice em nosso cluster do MongoDB.
1/**
2 * Initialization of the Key Vault collection and keyAltNames unique index.
3 */
4@Service
5public class KeyVaultServiceImpl implements KeyVaultService {
6
7 private static final Logger LOGGER = LoggerFactory.getLogger(KeyVaultServiceImpl.class);
8 private static final String INDEX_NAME = "uniqueKeyAltNames";
9 @Value("${mongodb.key.vault.db}")
10 private String KEY_VAULT_DB;
11 @Value("${mongodb.key.vault.coll}")
12 private String KEY_VAULT_COLL;
13
14 public void setupKeyVaultCollection(MongoClient mongoClient) {
15 LOGGER.info("=> Setup the key vault collection {}.{}", KEY_VAULT_DB, KEY_VAULT_COLL);
16 MongoDatabase db = mongoClient.getDatabase(KEY_VAULT_DB);
17 MongoCollection<Document> vault = db.getCollection(KEY_VAULT_COLL);
18 boolean vaultExists = doesCollectionExist(db, KEY_VAULT_COLL);
19 if (vaultExists) {
20 LOGGER.info("=> Vault collection already exists.");
21 if (!doesIndexExist(vault)) {
22 LOGGER.info("=> Unique index created on the keyAltNames");
23 createKeyVaultIndex(vault);
24 }
25 } else {
26 LOGGER.info("=> Creating a new vault collection & index on keyAltNames.");
27 createKeyVaultIndex(vault);
28 }
29 }
30
31 private void createKeyVaultIndex(MongoCollection<Document> vault) {
32 Bson keyAltNamesExists = exists("keyAltNames");
33 IndexOptions indexOpts = new IndexOptions().name(INDEX_NAME)
34 .partialFilterExpression(keyAltNamesExists)
35 .unique(true);
36 vault.createIndex(new BsonDocument("keyAltNames", new BsonInt32(1)), indexOpts);
37 }
38
39 private boolean doesCollectionExist(MongoDatabase db, String coll) {
40 return db.listCollectionNames().into(new ArrayList<>()).stream().anyMatch(c -> c.equals(coll));
41 }
42
43 private boolean doesIndexExist(MongoCollection<Document> coll) {
44 return coll.listIndexes()
45 .into(new ArrayList<>())
46 .stream()
47 .map(i -> i.get("name"))
48 .anyMatch(n -> n.equals(INDEX_NAME));
49 }
50}
Quando terminar, podemos fechar a conexão padrão do MongoDB.

Criação das chaves de criptografia de dados

Agora podemos criar as chaves de criptografia de dados (DEKs) usando a conexãoClientEncryption.
1/**
2 * ClientEncryption used by the DataEncryptionKeyService to create the DEKs.
3 */
4@Configuration
5public class MongoDBKeyVaultClientConfiguration {
6
7 private static final Logger LOGGER = LoggerFactory.getLogger(MongoDBKeyVaultClientConfiguration.class);
8 private final KmsService kmsService;
9 @Value("${spring.data.mongodb.vault.uri}")
10 private String CONNECTION_STR;
11 @Value("${mongodb.key.vault.db}")
12 private String KEY_VAULT_DB;
13 @Value("${mongodb.key.vault.coll}")
14 private String KEY_VAULT_COLL;
15 private MongoNamespace KEY_VAULT_NS;
16
17 public MongoDBKeyVaultClientConfiguration(KmsService kmsService) {
18 this.kmsService = kmsService;
19 }
20
21 @PostConstruct
22 public void postConstructor() {
23 this.KEY_VAULT_NS = new MongoNamespace(KEY_VAULT_DB, KEY_VAULT_COLL);
24 }
25
26 /**
27 * MongoDB Encryption Client that can manage Data Encryption Keys (DEKs).
28 *
29 * @return ClientEncryption MongoDB connection that can create or delete DEKs.
30 */
31 @Bean
32 public ClientEncryption clientEncryption() {
33 LOGGER.info("=> Creating the MongoDB Key Vault Client.");
34 MongoClientSettings mcs = MongoClientSettings.builder()
35 .applyConnectionString(new ConnectionString(CONNECTION_STR))
36 .build();
37 ClientEncryptionSettings ces = ClientEncryptionSettings.builder()
38 .keyVaultMongoClientSettings(mcs)
39 .keyVaultNamespace(KEY_VAULT_NS.getFullName())
40 .kmsProviders(kmsService.getKmsProviders())
41 .build();
42 return ClientEncryptions.create(ces);
43 }
44}
Podemos instanciar diretamente um bean ClientEncryptionusando oKMS e usá-lo para gerar nossos DEKs (um para cada coleção criptografada).
1/**
2 * Service responsible for creating and remembering the Data Encryption Keys (DEKs).
3 * We need to retrieve the DEKs when we evaluate the SpEL expressions in the Entities to create the JSON Schemas.
4 */
5@Service
6public class DataEncryptionKeyServiceImpl implements DataEncryptionKeyService {
7
8 private static final Logger LOGGER = LoggerFactory.getLogger(DataEncryptionKeyServiceImpl.class);
9 private final ClientEncryption clientEncryption;
10 private final Map<String, String> dataEncryptionKeysB64 = new HashMap<>();
11 @Value("${mongodb.kms.provider}")
12 private String KMS_PROVIDER;
13
14 public DataEncryptionKeyServiceImpl(ClientEncryption clientEncryption) {
15 this.clientEncryption = clientEncryption;
16 }
17
18 public Map<String, String> getDataEncryptionKeysB64() {
19 LOGGER.info("=> Getting Data Encryption Keys Base64 Map.");
20 LOGGER.info("=> Keys in DEK Map: {}", dataEncryptionKeysB64.entrySet());
21 return dataEncryptionKeysB64;
22 }
23
24 public String createOrRetrieveDEK(EncryptedEntity encryptedEntity) {
25 Base64.Encoder b64Encoder = Base64.getEncoder();
26 String dekName = encryptedEntity.getDekName();
27 BsonDocument dek = clientEncryption.getKeyByAltName(dekName);
28 BsonBinary dataKeyId;
29 if (dek == null) {
30 LOGGER.info("=> Creating Data Encryption Key: {}", dekName);
31 DataKeyOptions dko = new DataKeyOptions().keyAltNames(of(dekName));
32 dataKeyId = clientEncryption.createDataKey(KMS_PROVIDER, dko);
33 LOGGER.debug("=> DEK ID: {}", dataKeyId);
34 } else {
35 LOGGER.info("=> Existing Data Encryption Key: {}", dekName);
36 dataKeyId = dek.get("_id").asBinary();
37 LOGGER.debug("=> DEK ID: {}", dataKeyId);
38 }
39 String dek64 = b64Encoder.encodeToString(dataKeyId.getData());
40 LOGGER.debug("=> Base64 DEK ID: {}", dek64);
41 LOGGER.info("=> Adding Data Encryption Key to the Map with key: {}",
42 encryptedEntity.getEntityClass().getSimpleName());
43 dataEncryptionKeysB64.put(encryptedEntity.getEntityClass().getSimpleName(), dek64);
44 return dek64;
45 }
46
47}
Uma coisa a observar aqui é que estamos armazenando os DEKs em um mapa, para que não precisemos recuperá-los novamente mais tarde quando precisarmos deles para os JSON schemas.

entidades

Uma das principais áreas funcionais do Spring Data MongoDB é o modelo centralizado em POJO no qual ele depende para implementar os repositórios e mapear os documentos para as collection do MongoDB.
PersonEntity.java
1/**
2 * This is the entity class for the "persons" collection.
3 * The SpEL expression of the @Encrypted annotation is used to determine the DEK's keyId to use for the encryption.
4 *
5 * @see com.mongodb.quickstart.javaspringbootcsfle.components.EntitySpelEvaluationExtension
6 */
7@Document("persons")
8@Encrypted(keyId = "#{mongocrypt.keyId(#target)}")
9public class PersonEntity {
10 @Id
11 private ObjectId id;
12 private String firstName;
13 private String lastName;
14 @Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
15 private String ssn;
16 @Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Random")
17 private String bloodType;
18
19 // Constructors
20
21 @Override
22 // toString()
23
24 // Getters & Setters
25}
Como você pode ver acima, essa entidade contém todas as informações que precisamos para automatizar totalmente o CSFLE. Temos as informações necessárias para gerar o JSON schema:
  • Usando a expressão SpEL #{mongocrypt.keyId(#target)}, podemos preencher dinamicamente a DEK que foi gerada ou recuperada anteriormente.
  • ssn é um String que requer um algoritmo determinístico.
  • bloodType é um String que requer um algoritmo aleatório.
O JSON schema gerado tem a seguinte aparência:
1{
2 "encryptMetadata": {
3 "keyId": [
4 {
5 "$binary": {
6 "base64": "WyHXZ+53SSqCC/6WdCvp0w==",
7 "subType": "04"
8 }
9 }
10 ]
11 },
12 "type": "object",
13 "properties": {
14 "ssn": {
15 "encrypt": {
16 "bsonType": "string",
17 "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
18 }
19 },
20 "bloodType": {
21 "encrypt": {
22 "bsonType": "string",
23 "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random"
24 }
25 }
26 }
27}

Extensão de avaliação SEL

A avaliação da expressão SpEL só é possível por causa desta classe que adicionamos na configuração:
1/**
2 * Will evaluate the SePL expressions in the Entity classes like this: #{mongocrypt.keyId(#target)} and insert
3 * the right encryption key for the right collection.
4 */
5@Component
6public class EntitySpelEvaluationExtension implements EvaluationContextExtension {
7
8 private static final Logger LOGGER = LoggerFactory.getLogger(EntitySpelEvaluationExtension.class);
9 private final DataEncryptionKeyService dataEncryptionKeyService;
10
11 public EntitySpelEvaluationExtension(DataEncryptionKeyService dataEncryptionKeyService) {
12 this.dataEncryptionKeyService = dataEncryptionKeyService;
13 }
14
15 @Override
16 @NonNull
17 public String getExtensionId() {
18 return "mongocrypt";
19 }
20
21 @Override
22 @NonNull
23 public Map<String, Function> getFunctions() {
24 try {
25 return Collections.singletonMap("keyId", new Function(
26 EntitySpelEvaluationExtension.class.getMethod("computeKeyId", String.class), this));
27 } catch (NoSuchMethodException e) {
28 throw new RuntimeException(e);
29 }
30 }
31
32 public String computeKeyId(String target) {
33 String dek = dataEncryptionKeyService.getDataEncryptionKeysB64().get(target);
34 LOGGER.info("=> Computing dek for target {} => {}", target, dek);
35 return dek;
36 }
37}
Observe que é o local onde estamos recuperando os DEKs e combinando-os com o target: "PersonEntity", neste caso.

JSON schemas e a conexão do MongoClient

Na verdade, JSON schemas não são triviais de gerar em um projeto Spring Data MongoDB.
De fato, para gerar os JSON schemas, precisamos do MappingContext (as entidades, etc.) que é criado pela configuração automática do Spring Data que cria a conexãoMongoClient e o MongoTemplate...
Mas para criar o MongoClient - com a criptografia automática ativada - você precisa de JSON schemas!
Levei um tempo considerável para encontrar uma solução para esse impasse, e você pode aproveitar a solução agora mesmo!
A solução é injetar a criação do JSON schema no processo de configuração automática instanciando o beanMongoClientSettingsBuilderCustomizer .
1/**
2 * Spring Data MongoDB Configuration for the encrypted MongoClient with all the required configuration (jsonSchemas).
3 * The big trick in this file is the creation of the JSON Schemas before the creation of the entire configuration as
4 * we need the MappingContext to resolve the SpEL expressions in the entities.
5 *
6 * @see com.mongodb.quickstart.javaspringbootcsfle.components.EntitySpelEvaluationExtension
7 */
8@Configuration
9@DependsOn("keyVaultAndDekSetup")
10public class MongoDBSecureClientConfiguration {
11
12 private static final Logger LOGGER = LoggerFactory.getLogger(MongoDBSecureClientConfiguration.class);
13 private final KmsService kmsService;
14 private final SchemaService schemaService;
15 @Value("${crypt.shared.lib.path}")
16 private String CRYPT_SHARED_LIB_PATH;
17 @Value("${spring.data.mongodb.storage.uri}")
18 private String CONNECTION_STR_DATA;
19 @Value("${spring.data.mongodb.vault.uri}")
20 private String CONNECTION_STR_VAULT;
21 @Value("${mongodb.key.vault.db}")
22 private String KEY_VAULT_DB;
23 @Value("${mongodb.key.vault.coll}")
24 private String KEY_VAULT_COLL;
25 private MongoNamespace KEY_VAULT_NS;
26
27 public MongoDBSecureClientConfiguration(KmsService kmsService, SchemaService schemaService) {
28 this.kmsService = kmsService;
29 this.schemaService = schemaService;
30 }
31
32 @PostConstruct
33 public void postConstruct() {
34 this.KEY_VAULT_NS = new MongoNamespace(KEY_VAULT_DB, KEY_VAULT_COLL);
35 }
36
37 @Bean
38 public MongoClientSettings mongoClientSettings() {
39 LOGGER.info("=> Creating the MongoClientSettings for the encrypted collections.");
40 return MongoClientSettings.builder().applyConnectionString(new ConnectionString(CONNECTION_STR_DATA)).build();
41 }
42
43 @Bean
44 public MongoClientSettingsBuilderCustomizer customizer(MappingContext mappingContext) {
45 LOGGER.info("=> Creating the MongoClientSettingsBuilderCustomizer.");
46 return builder -> {
47 MongoJsonSchemaCreator schemaCreator = MongoJsonSchemaCreator.create(mappingContext);
48 Map<String, BsonDocument> schemaMap = schemaService.generateSchemasMap(schemaCreator)
49 .entrySet()
50 .stream()
51 .collect(toMap(e -> e.getKey().getFullName(),
52 Map.Entry::getValue));
53 Map<String, Object> extraOptions = Map.of("cryptSharedLibPath", CRYPT_SHARED_LIB_PATH,
54 "cryptSharedLibRequired", true);
55 MongoClientSettings mcs = MongoClientSettings.builder()
56 .applyConnectionString(
57 new ConnectionString(CONNECTION_STR_VAULT))
58 .build();
59 AutoEncryptionSettings oes = AutoEncryptionSettings.builder()
60 .keyVaultMongoClientSettings(mcs)
61 .keyVaultNamespace(KEY_VAULT_NS.getFullName())
62 .kmsProviders(kmsService.getKmsProviders())
63 .schemaMap(schemaMap)
64 .extraOptions(extraOptions)
65 .build();
66 builder.autoEncryptionSettings(oes);
67 };
68 }
69}
Uma coisa a observar aqui é a opção de separar os DEKs das coleções criptografadas em dois clusters MongoDB completamente separados. Isso não é obrigatório, mas pode ser um truque útil se você optar por ter uma política de retenção de backup diferente para os dois clusters. Isso pode ser interessante para o artigo 17 "Direito de apagamento", por exemplo, pois você pode garantir que uma DEK pode desaparecer completamente de seus sistemas (backup incluído). Falo mais sobre essa abordagem em minha postagem Java CSFLE.
Aqui está o serviço de JSON schema que armazena os JSON schemas gerados em um mapa:
1@Service
2public class SchemaServiceImpl implements SchemaService {
3
4 private static final Logger LOGGER = LoggerFactory.getLogger(SchemaServiceImpl.class);
5 private Map<MongoNamespace, BsonDocument> schemasMap;
6
7 @Override
8 public Map<MongoNamespace, BsonDocument> generateSchemasMap(MongoJsonSchemaCreator schemaCreator) {
9 LOGGER.info("=> Generating schema map.");
10 List<EncryptedEntity> encryptedEntities = EncryptedCollectionsConfiguration.encryptedEntities;
11 return schemasMap = encryptedEntities.stream()
12 .collect(toMap(EncryptedEntity::getNamespace,
13 e -> generateSchema(schemaCreator, e.getEntityClass())));
14 }
15
16 @Override
17 public Map<MongoNamespace, BsonDocument> getSchemasMap() {
18 return schemasMap;
19 }
20
21 private BsonDocument generateSchema(MongoJsonSchemaCreator schemaCreator, Class<?> entityClass) {
22 BsonDocument schema = schemaCreator.filter(MongoJsonSchemaCreator.encryptedOnly())
23 .createSchemaFor(entityClass)
24 .schemaDocument()
25 .toBsonDocument();
26 LOGGER.info("=> JSON Schema for {}:\n{}", entityClass.getSimpleName(),
27 schema.toJson(JsonWriterSettings.builder().indent(true).build()));
28 return schema;
29 }
30
31}
Estamos armazenando os JSON schemas porque esse modelo também implementa uma das boas práticas do CSFLE: JSON schemas do lado do servidor.

Crie ou atualize as coleções criptografadas

De fato, para fazer a criptografia e a descriptografia automáticas do CSFLE funcionarem, você não precisa dos JSON schemas do lado do servidor.
Apenas os do lado do cliente são necessários para a biblioteca compartilhada de criptografia automática. Mas nada impediria outro cliente mal configurado ou um administrador conectado diretamente ao cluster de inserir ou atualizar alguns documentos sem criptografar os campos.
Para impor isso, você pode usar o JSON schema do lado do servidor como faria para impor um tipo de campo em um documento, por exemplo.
Mas, como o JSON schema desenvolverá com as diferentes versões do seu aplicativo, os JSON schemas precisam ser atualizados de acordo sempre que você reiniciar o aplicativo.
1/**
2 * Create or update the encrypted collections with a server side JSON Schema to secure the encrypted field in the MongoDB database.
3 * This prevents any other client from inserting or editing the fields without encrypting the fields correctly.
4 */
5@Component
6public class EncryptedCollectionsSetup {
7
8 private static final Logger LOGGER = LoggerFactory.getLogger(EncryptedCollectionsSetup.class);
9 private final MongoClient mongoClient;
10 private final SchemaService schemaService;
11
12 public EncryptedCollectionsSetup(MongoClient mongoClient, SchemaService schemaService) {
13 this.mongoClient = mongoClient;
14 this.schemaService = schemaService;
15 }
16
17 @PostConstruct
18 public void postConstruct() {
19 LOGGER.info("=> Setup the encrypted collections.");
20 schemaService.getSchemasMap()
21 .forEach((namespace, schema) -> createOrUpdateCollection(mongoClient, namespace, schema));
22 }
23
24 private void createOrUpdateCollection(MongoClient mongoClient, MongoNamespace ns, BsonDocument schema) {
25 MongoDatabase db = mongoClient.getDatabase(ns.getDatabaseName());
26 String collStr = ns.getCollectionName();
27 if (doesCollectionExist(db, ns)) {
28 LOGGER.info("=> Updating {} collection's server side JSON Schema.", ns.getFullName());
29 db.runCommand(new Document("collMod", collStr).append("validator", jsonSchemaWrapper(schema)));
30 } else {
31 LOGGER.info("=> Creating encrypted collection {} with server side JSON Schema.", ns.getFullName());
32 db.createCollection(collStr, new CreateCollectionOptions().validationOptions(
33 new ValidationOptions().validator(jsonSchemaWrapper(schema))));
34 }
35 }
36
37 public BsonDocument jsonSchemaWrapper(BsonDocument schema) {
38 return new BsonDocument("$jsonSchema", schema);
39 }
40
41 private boolean doesCollectionExist(MongoDatabase db, MongoNamespace ns) {
42 return db.listCollectionNames()
43 .into(new ArrayList<>())
44 .stream()
45 .anyMatch(c -> c.equals(ns.getCollectionName()));
46 }
47
48}

Suporte a várias entidades

Um grande recurso desse modelo também é o suporte a várias entidades. Como você já deve ter notado, há um CompanyEntity e todos os seus componentes relacionados, mas o código é genérico o suficiente para lidar com qualquer quantidade de entidades, o que não costuma ser o caso em todos os outros tutoriais on-line.
Nesse modelo, se você quiser oferecer suporte a um terceiro tipo de entidade, basta criar os componentes da arquitetura de três camadas como de costume e adicionar sua entrada na classeEncryptedCollectionsConfiguration.
1/**
2 * Information about the encrypted collections in the application.
3 * As I need the information in multiple places, I decided to create a configuration class with a static list of
4 * the encrypted collections and their information.
5 */
6public class EncryptedCollectionsConfiguration {
7 public static final List<EncryptedEntity> encryptedEntities = List.of(
8 new EncryptedEntity("mydb", "persons", PersonEntity.class, "personDEK"),
9 new EncryptedEntity("mydb", "companies", CompanyEntity.class, "companyDEK"));
10}
Todo o resto, desde a geração do DEK até a criação da collection criptografada com o JSON schema do lado do servidor, é totalmente automatizado e tratado de forma transparente. Tudo o que você precisa fazer é especificar a anotação@Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")na classe da entidade e o campo será criptografado e descriptografado automaticamente para você quando estiver usando os repositórios implementados automaticamente (cortesia do Spring Data MongoDB, é claro!).

Query por um campo criptografado

Talvez você tenha notado, mas esse modelo implementa o métodofindFirstBySsn(ssn), o que significa que é possível recuperar um documento pessoal pelo número SSN, mesmo que esse campo esteja criptografado.
Observe que isso só funciona porque estamos usando um algoritmo de criptografia determinístico.
1/**
2 * Spring Data MongoDB repository for the PersonEntity
3 */
4@Repository
5public interface PersonRepository extends MongoRepository<PersonEntity, String> {
6
7 PersonEntity findFirstBySsn(String ssn);
8}

Encerrando

Obrigado por ler minha postagem!
Se você tiver alguma dúvida sobre o assunto, fique à vontade para abrir uma pergunta no Github ou fazer uma pergunta no MongoDB Community.
Sinta-se à vontade para me enviar um ping diretamente em sua postagem: @MaBeuLux88.
Solicitações pull e ideias de melhoria são muito bem-vindas!

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse exemplo de código
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Crie uma Java REST API com Quarkus e Eclipse JNoSQL para MongoDB


Jan 30, 2024 | 6 min read
Tutorial

Desenvolvimento sem servidor com AWS Lambda e MongoDB Atlas usando Java


Jul 20, 2023 | 6 min read
Artigo

Orquestração do MongoDB com o Spring e Atlas Kubernetes Operator


Jun 12, 2024 | 13 min read
exemplo de código

APIs REST com Java, Spring Boot e MongoDB


Oct 25, 2023 | 4 min read
Tecnologias Utilizadas
Linguagens
Tecnologias
Sumário