Java encontra Queryable Encryption: desenvolvendo um aplicativo de conta bancária seguro
Ricardo Mello14 min read • Published Oct 02, 2024 • Updated Oct 08, 2024
APLICATIVO COMPLETO
Avalie esse Tutorial
A criptografia é essencial para proteger dados convertendo informações legíveis (texto simples) em uma forma ininteligível (texto cifrado) usando técnicas criptográficas. Esse processo garante que, mesmo que partes não autorizadas obtenham acesso, elas não possam interpretar os dados sem a chave de descriptografia adequada. Ele executa um papel essencial na proteção de informações confidenciais, como detalhes pessoais, registros financeiros e comunicações confidenciais, estejam os dados armazenados ou em trânsito.
Os métodos tradicionais de criptografia, embora protejam os dados contra o acesso não autorizado, geralmente exigem a descriptografia dos dados antes que eles possam ser consultados ou analisados. Isso pode representar riscos de segurança, pois os dados são expostos em sua forma não criptografada durante o processamento. O objetivo é encontrar uma maneira de realizar queries em dados criptografados sem comprometer sua segurança.
A Queryable Encryption do MongoDB torna a proteção de seus dados mais fácil do que nunca. Ele garante que seus dados permaneçam criptografados, mesmo durante a execução de queries. Toda a criptografia e descriptografia acontece no lado do cliente , o que significa que o servidor nunca tem acesso a dados simples e legíveis. Isso também se aplica a serviços como o MongoDB Atlas e provedores de nuvem – eles só veem dados criptografados, o que aumenta significativamente a privacidade e a segurança dos seus dados. Isso protege você de ataques externos e garante que os administradores do banco de dados de dados, que têm acesso ao banco de dados de dados, mas não às chaves de criptografia, não possam visualizar ou entender seus dados. Então, mesmo que alguém consiga violar o servidor ou a infraestrutura de nuvem, tudo o que encontrará são informações criptografadas que não podem ser descriptografadas sem as chaves adequadas.
Neste tutorial, criaremos um aplicação Java chamado "BankAccount" para demonstrar como a Queryable Encryption funciona. Nosso objetivo é criar um aplicativo que salve e recupere documentos de um banco de dados de dados chamado "digitalBank". Esses documentos incluirão:
- accountHolderName.
- accountNumber (criptografado).
- cardVerificationCode (criptografado).
- accountBalance (criptografado).
Configuraremos o aplicação para criar um endpoint que nos permita pesquisar documentos usando dois tipos de query:
- Queries de igualdade: para encontrar documentos que correspondam a um accountNumber específico
- Queries de intervalo: Para encontrar documentos com valores dentro de um determinado intervalo (por exemplo, maior que um valor especificado)
O recurso Criptografia automática lidará com a criptografia e a descriptografia dos dados para nós, para que possamos nos concentrar na criação do aplicação sem precisar ser especialistas em criptografia.
Veja o que você precisará acompanhar neste tutorial:
- Comece a usar o MongoDB Atlas gratuitamente! Se você ainda não tiver uma conta, o MongoDB oferece um cluster Atlas gratuito para sempre.
- IDE de sua escolha
Queryable Encryption é um recurso do MongoDB projetado para permitir queries seguras em dados criptografados. Ele permite que campos criptografados sejam consultados usando vários operadores, como igualdade e faixa, mantendo a confidencialidade dos dados. Com a Queryable Encryption, você pode:
- Criptografe campos confidenciais diretamente do lado do cliente .
- Execute queries expressivas nos dados criptografados.
Esses processos são realizados sem que o servidor tenha acesso aos dados não criptografados. Informações confidenciais permanecem criptografadas em todas as etapas (em trânsito, em repouso, durante o uso e em backups) e só são descriptografadas no lado do cliente , onde você mantém o controle sobre as chaves de criptografia.
Esse método avançado de criptografia permite o armazenamento e a pesquisa seguros de dados, desenvolvido pelos principais especialistas na campo. É importante ressaltar que a tecnologia por trás dela foi publicada e minuciosamente revisada por pares, garantindo sua robustez e crédito.
Você pode encontrar mais informações sobre essa pesquisa e sua validação em Cryptographch Research Group.
Além disso, essa solução de criptografia não é apenas segura, mas também construída para ter um bom desempenho em cenários do mundo real, equilibrando alta segurança e usabilidade prática. A imagem acima demonstra o fluxo de Queryable Encryption para o aplicação que implementaremos
- O usuário realizará duas queries usando accountNumber e accountBalance.
- Essas queries serão detectadas pelo driver Java do MongoDB , que identificará que esses campos estão criptografados.
- Em seguida, o driver enviará uma query criptografada junto com um token para o servidor MongoDB .
- O MongoDB retornará os dados criptografados do banco de banco de dados.
- O driver Java descriptografará os dados e apresentará os resultados ao usuário.
É importante observar que os dados permanecem criptografados o tempo todo no banco de banco de dados. Sem a chave de descriptografia, mesmo com acesso raiz, seria impossível recuperar as informações dos campos criptografados.
Para atingir nosso objetivo, desenvolveremos um aplicação Spring Boot que fornecerá endpoints para inserir e recuperar dados. Para fazer isso, usaremos o Spring Initializr, como de costume, para configurar nosso aplicação. Configure seu aplicação de acordo com a imagem abaixo.
Nesta etapa, adicionaremos algumas dependências ao projeto e configuraremos o modelo de criptografia automática . Para começar, abra o arquivo pom.xml e inclua as seguintes dependências:
1 <dependency> 2 <groupId>org.projectlombok</groupId> 3 <artifactId>lombok</artifactId> 4 <optional>true</optional> 5 </dependency> 6 <dependency> 7 <groupId>org.mongodb</groupId> 8 <artifactId>mongodb-driver-sync</artifactId> 9 <version>5.1.3</version> 10 </dependency> 11 <dependency> 12 <groupId>org.mongodb</groupId> 13 <artifactId>mongodb-crypt</artifactId> 14 <version>1.11.0</version> 15 </dependency>
Para ativar a Queryable Encryption automática em nosso projeto Java , precisamos definir as configurações no arquivo .properties do aplicação. Essa configuração envolve dois componentes principais:
- Biblioteca compartilhada de criptografia automática: A biblioteca compartilhada de criptografia automática é crucial para gerenciar dados criptografados em seu aplicação. Ele lida com criptografia e descriptografia, garante que somente as operações suportadas sejam executadas em campos criptografados e gerencia quais campos são criptografados.
- Baixar a biblioteca: Obtenha a Biblioteca compartilhada de criptografia automática no Centro de Download do MongoDB. Selecione a versão apropriada (por exemplo, 0.0) e plataforma. Escolha o pacote crypt_shared e baixe-o.
2. KMS (KMS): o KMS é responsável por gerenciar e armazenar as chaves de criptografia usadas para proteger dados. Neste exemplo, estamos usando uma configuração local de KMS . No entanto, para ambientes de produção, é altamente recomendável usar KMS serviçoKMS para garantir maior segurança e escalabilidade.
Aqui estão as configurações que você precisará incluir no arquivo .properties do seu aplicação:
1 app.mongodb.uri=mongodb+srv://user:password@cluster0.cluster.mongodb.net/ 2 app.mongodb.kmsProviderName=local 3 app.mongodb.keyVaultNamespace=encryption.__keyVault 4 app.mongodb.encryptedDatabaseName=digitalBank 5 app.mongodb.encryptedCollectionName=accounts 6 app.mongodb.cryptSharedLibPath=/<path-to-lib>/mongo_crypt_v1.dylib
Entendendo as configurações:
- app.mongodb.uri=Your_CONNECTION_STRING
- Especifica sua string de conexão do MongoDB Atlas
- app.mongodb.kmsProviderName=local
- Especifica o nome do provedor de KMS
- app.mongodb.keyVaultNamespace=encryption.__keyVault
- Define o namespace do cofre de chaves onde as chaves de encriptação são armazenadas. Nesta configuração, as chaves são armazenadas na coleção __keyVault dentro do banco de banco de dados de criptografia. Essas chaves são protegidas com a chave do cliente, para que o servidor nunca as veja em um estado não criptografado. Isso garante que as chaves permaneçam protegidas e confidenciais.
- app.mongodb.encryptedDatabaseName=digitalBank
- Especifica o nome do banco de banco de dados onde os dados criptografados serão armazenados
- app.mongodb.encryptedCollectionName=contas
- Indica a collection dentro do banco de dados de dados onde os detalhes da conta criptografados serão mantidos
- app.mongodb.cryptSharedLibPath=/PATH_TO_LIB/mongo_crypt_v1.dylib
- Aponte para a localização da Biblioteca compartilhada de criptografia automática em seu sistema. Essa biblioteca dinâmica é essencial para habilitar a criptografia automática . Substitua /PATH_TO_LIB/ pelo caminho real para onde o mongo_crypt_v1.dylib está localizado em sua máquina.
Nesta seção, configuraremos as classes relacionadas à criptografia para habilitar a criptografia automática em nosso aplicação Spring Boot usando o MongoDB. Nosso objetivo é configurar os componentes necessários para gerenciar dados criptografados com segurança. Crie um novo arquivo Java denominado EncryptionConfig no pacote resources/config :
1 package com.mongodb.bankaccount.resources.config; 2 3 import com.mongodb.AutoEncryptionSettings; 4 import com.mongodb.ClientEncryptionSettings; 5 import com.mongodb.ConnectionString; 6 import com.mongodb.MongoClientSettings; 7 import com.mongodb.client.MongoDatabase; 8 import lombok.Data; 9 import org.springframework.boot.ApplicationRunner; 10 import org.springframework.boot.context.properties.ConfigurationProperties; 11 import org.springframework.context.annotation.Bean; 12 import org.springframework.context.annotation.Configuration; 13 import java.io.File; 14 import java.io.FileInputStream; 15 import java.io.FileOutputStream; 16 import java.io.IOException; 17 import java.security.SecureRandom; 18 import java.util.HashMap; 19 import java.util.Map; 20 21 22 23 24 public class EncryptionConfig { 25 26 private String uri; 27 private String kmsProviderName; 28 private String keyVaultNamespace; 29 private String encryptedDatabaseName; 30 private String encryptedCollectionName; 31 private String cryptSharedLibPath; 32 33 private static final String CUSTOMER_KEY_PATH = "src/main/resources/customer-key.txt"; 34 private static final int KEY_SIZE = 96; 35 private final Map<String, Map<String, Object>> kmsProviderCredentials = new HashMap<>(); 36 37 38 public ApplicationRunner createEncryptedCollectionRunner(MongoDatabase db, EncryptionFieldConfig encryptionFieldConfig) { 39 return args -> { 40 if (!encryptionFieldConfig.collectionExists(db, encryptedCollectionName)) { 41 encryptionFieldConfig.createEncryptedCollection(db, clientEncryptionSettings()); 42 } 43 }; 44 } 45 46 private ClientEncryptionSettings clientEncryptionSettings() throws Exception { 47 return ClientEncryptionSettings.builder() 48 .keyVaultMongoClientSettings(getMongoClientSettings()) 49 .keyVaultNamespace(keyVaultNamespace) 50 .kmsProviders(kmsProviderCredentials) 51 .build(); 52 } 53 54 protected MongoClientSettings getMongoClientSettings() throws Exception { 55 kmsProviderCredentials.put("local", createLocalKmsProvider()); 56 57 AutoEncryptionSettings autoEncryptionSettings = AutoEncryptionSettings.builder() 58 .keyVaultNamespace(keyVaultNamespace) 59 .kmsProviders(kmsProviderCredentials) 60 .extraOptions(createExtraOptions()) 61 .build(); 62 63 return MongoClientSettings.builder() 64 .applyConnectionString(new ConnectionString(uri)) 65 .autoEncryptionSettings(autoEncryptionSettings) 66 .build(); 67 } 68 69 private Map<String, Object> createLocalKmsProvider() throws IOException { 70 if (!isCustomerMasterKeyFileExists()) { 71 generateCustomerMasterKey(); 72 } 73 74 byte[] localCustomerMasterKey = readCustomerMasterKey(); 75 Map<String, Object> keyMap = new HashMap<>(); 76 keyMap.put("key", localCustomerMasterKey); 77 return keyMap; 78 } 79 80 private boolean isCustomerMasterKeyFileExists() { 81 return new File(CUSTOMER_KEY_PATH).isFile(); 82 } 83 84 private void generateCustomerMasterKey() throws IOException { 85 byte[] localCustomerMasterKey = new byte[KEY_SIZE]; 86 new SecureRandom().nextBytes(localCustomerMasterKey); 87 try (FileOutputStream stream = new FileOutputStream(CUSTOMER_KEY_PATH)) { 88 stream.write(localCustomerMasterKey); 89 } catch (IOException e) { 90 throw new IOException("Unable to write Customer Master Key file: " + e.getMessage(), e); 91 } 92 } 93 94 private byte[] readCustomerMasterKey() throws IOException { 95 byte[] localCustomerMasterKey = new byte[KEY_SIZE]; 96 97 try (FileInputStream fis = new FileInputStream(CUSTOMER_KEY_PATH)) { 98 int bytesRead = fis.read(localCustomerMasterKey); 99 if (bytesRead != KEY_SIZE) { 100 throw new IOException("Expected the customer master key file to be " + KEY_SIZE + " bytes, but read " + bytesRead + " bytes."); 101 } 102 } catch (IOException e) { 103 throw new IOException("Unable to read the Customer Master Key: " + e.getMessage(), e); 104 } 105 106 return localCustomerMasterKey; 107 } 108 109 private Map<String, Object> createExtraOptions() { 110 Map<String, Object> extraOptions = new HashMap<>(); 111 extraOptions.put("cryptSharedLibPath", cryptSharedLibPath); 112 return extraOptions; 113 } 114 }
Em nosso aplicação, a classeEncryptionConfig classe um papel crítico na configuração e no gerenciamento da criptografia para MongoDB. Veja um detalhamento de suas principais funcionalidades, com foco particular no KMS (KMS) e no caminho da biblioteca criptográfica (cryptSharedLibPath).
- Configuração doKMS (KMS):
- Fornecedor de KMS: a classe é configurada para usar um fornecedor de KMS, especificado pela propriedade kmsProviderName . Para desenvolvimento e teste local, usamos um fornecedor local de KMS. Essa configuração local simplifica o desenvolvimento, mas não é recomendada para produção.
- Chave mestra do cliente: a classe lida com a geração e o gerenciamento de uma chave mestre do cliente local. Primeiro, ele verifica se o arquivo de chave (customer-key.txt) existe. Caso contrário, a classe gera uma nova chave e a salva. Esta chave é crucial para criptografar e descriptografar dados. A chave é então lida do arquivo e usada no processo de criptografia.
- Credenciais do provedor KMS: O método createLocalKmsProvider() cria um mapa com a chave mestra do cliente , que é usada nos métodos clientEncryptionSettings() e getMongoClientSettings(). Essa configuração garante que o cliente MongoDB possa lidar com a criptografia corretamente.
- Caminho da biblioteca criptográfica (cryptSharedLibPath):
- Configuração do caminho da biblioteca: a propriedade cryptSharedLibPath especifica o caminho para a biblioteca compartilhada de criptografia automática. Essa biblioteca é essencial para ativar a criptografia automática de dados no MongoDB . O caminho para essa biblioteca está incluído nas configurações de criptografia por meio do método createExtraOptions(), permitindo que o cliente MongoDB utilize as funcionalidades criptográficas fornecidas pela biblioteca.
Principais funcionalidades da classe:
- Configurações de criptografia do cliente: o método clientEncryptionSettings() configura as configurações necessárias para gerenciar chaves e processos de criptografia. Essa configuração é crucial para garantir que os dados sejam criptografados e descriptografados de acordo com nossas especificações.
- Configurações do cliente MongoDB: o método getMongoClientSettings() incorpora configurações de criptografia automática na configuração do cliente MongoDB . Ele garante que o cliente possa se conectar à instância do MongoDB usando o URI fornecido e lidar com a criptografia conforme necessário.
- Criação de coleção criptografada: O método createEncryptedCollectionRunner() garante que uma coleção criptografada seja criada se ela ainda não existir. Esta configuração usa as configurações de criptografia definidas para garantir a segurança dos dados.
Em seguida, crie o EncryptionFieldConfig no pacote resources/config :
1 package com.mongodb.bankaccount.resources.config; 2 3 import com.mongodb.ClientEncryptionSettings; 4 import com.mongodb.client.MongoDatabase; 5 import com.mongodb.client.model.CreateCollectionOptions; 6 import com.mongodb.client.model.CreateEncryptedCollectionParams; 7 import com.mongodb.client.vault.ClientEncryptions; 8 import org.bson.*; 9 import org.springframework.context.annotation.Configuration; 10 import java.util.ArrayList; 11 import java.util.Arrays; 12 13 14 public class EncryptionFieldConfig { 15 16 protected boolean collectionExists(MongoDatabase db, String collectionName) { 17 return db.listCollectionNames().into(new ArrayList<>()).contains(collectionName); 18 } 19 20 protected void createEncryptedCollection(MongoDatabase db, ClientEncryptionSettings clientEncryptionSettings) { 21 var clientEncryption = ClientEncryptions.create(clientEncryptionSettings); 22 var encryptedCollectionParams = new CreateEncryptedCollectionParams("local") 23 .masterKey(new BsonDocument()); 24 25 var createCollectionOptions = new CreateCollectionOptions().encryptedFields(encryptFields()); 26 clientEncryption.createEncryptedCollection(db, "accounts", createCollectionOptions, encryptedCollectionParams); 27 } 28 29 private BsonDocument encryptFields() { 30 return new BsonDocument().append("fields", 31 new BsonArray(Arrays.asList( 32 createEncryptedField("accountNumber", "string", equalityQueryType()), 33 createEncryptedField("cardVerificationCode", "int", equalityQueryType()), 34 createEncryptedField("accountBalance", "double", rangeQueryType() 35 )))); 36 } 37 38 private BsonDocument createEncryptedField(String path, String bsonType, BsonDocument query) { 39 return new BsonDocument() 40 .append("keyId", new BsonNull()) 41 .append("path", new BsonString(path)) 42 .append("bsonType", new BsonString(bsonType)) 43 .append("queries", query); 44 } 45 46 private BsonDocument rangeQueryType() { 47 return new BsonDocument() 48 .append("queryType", new BsonString("range")) 49 .append("min", new BsonDouble(0)) 50 .append("max", new BsonDouble(999999999)) 51 .append("precision", new BsonInt32(2)); 52 } 53 54 private BsonDocument equalityQueryType() { 55 return new BsonDocument().append("queryType", new BsonString("equality")); 56 } 57 }
Na classe EncryptionFieldConfig , nos concentramos em especificar quais campos em nossa coleção MongoDB serão criptografados para garantir a segurança dos dados. Aqui está uma breve visão geral do que esta classe faz, com atenção para os campos que serão criptografados:
- Definindo campos criptografados:
- A função principal desta classe é configurar a criptografia em nível de campo. Usando o método encryptionFields() , definimos quais campos da coleção MongoDB precisam ser criptografados. Em nosso caso, estamos criptografando três campos críticos:
- accountNumber (tipo: string, tipo de query: igualdade): este campo contém informações confidenciais de conta que precisam de proteção.
- cardVerificationCode (tipo: int, tipo de consulta: igualdade): este campo contém códigos de verificação sensíveis, que devem ser criptografados para garantir a segurança.
- accountBalance (tipo: double, tipo de consulta: intervalo): esse campo representa dados financeiros, e a criptografia é aplicada com suporte para queries baseadas em intervalo para lidar com cálculos financeiros com segurança. Para queries de intervalo, os opcionais mín, máximo e precisão são muito importantes para o desempenho. Você pode encontrar mais sobre isso em Campos criptografados e queries ativadas
- Criando e gerenciando coleções criptografadas:
- O método createEncryptedCollection() é utilizado para verificar se a coleção especificada existe no banco de banco de dados. Caso contrário, o método cria uma nova coleção com as configurações de criptografia definidas. Essa configuração é gerenciada por ClientEncryptions.create(), que aplica as configurações de criptografia.
- Metadados de criptografia de campo:
- Para cada campo, o método createEncryptedField() especifica metadados essenciais, incluindo o caminho do campo, o tipo de BSON e o tipo de query. Esses metadados ajudam o MongoDB a entender como criptografar e descriptografar os dados e, ao mesmo tempo, oferecer suporte a várias operações de query.
- Tratamento de tipos de query:
- Diferentes tipos de query são atribuídos aos campos para controlar como os dados criptografados podem ser consultados. Por exemplo, queries de igualdade nos permitem pesquisar correspondências exatas, enquanto queries de intervalo nos permitem realizar pesquisas baseadas em intervalo em dados financeiros criptografados.
E agora, crie a classeMongoConfig no pacote resources/config :
1 package com.mongodb.bankaccount.resources.config; 2 3 import com.mongodb.MongoClientSettings; 4 import com.mongodb.client.MongoClient; 5 import com.mongodb.client.MongoClients; 6 import com.mongodb.client.MongoDatabase; 7 import org.bson.codecs.configuration.CodecProvider; 8 import org.bson.codecs.configuration.CodecRegistry; 9 import org.bson.codecs.pojo.PojoCodecProvider; 10 import org.springframework.context.annotation.Bean; 11 import org.springframework.context.annotation.Configuration; 12 import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry; 13 import static org.bson.codecs.configuration.CodecRegistries.fromProviders; 14 import static org.bson.codecs.configuration.CodecRegistries.fromRegistries; 15 16 17 public class MongoConfig { 18 19 private final EncryptionConfig encryptionConfig; 20 21 public MongoConfig(EncryptionConfig encryptionConfig) { 22 this.encryptionConfig = encryptionConfig; 23 } 24 25 26 public MongoClient mongoClient() throws Exception { 27 MongoClientSettings mongoClientSettings = encryptionConfig.getMongoClientSettings(); 28 return MongoClients.create(mongoClientSettings); 29 } 30 31 32 public MongoDatabase mongoDatabase(MongoClient mongoClient) { 33 CodecProvider pojoCodecProvider = PojoCodecProvider.builder().automatic(true).build(); 34 CodecRegistry pojoCodecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider)); 35 36 return mongoClient.getDatabase(encryptionConfig.getEncryptedDatabaseName()).withCodecRegistry(pojoCodecRegistry); 37 } 38 }
Esta classe é direta; ele contém apenas nossa configuração do MongoDB .
Agora, para finalizar, basta abrir a classeBankaccountApplication e adicionar a anotação @EnableConfigurationProperties(EncryptionConfig. classe) para habilitar a leitura de nossas propriedades nesta classe.
1 package com.mongodb.bankaccount; 2 3 import com.mongodb.bankaccount.resources.config.EncryptionConfig; 4 import org.springframework.boot.SpringApplication; 5 import org.springframework.boot.autoconfigure.SpringBootApplication; 6 import org.springframework.boot.context.properties.EnableConfigurationProperties; 7 8 9 10 public class BankaccountApplication { 11 12 public static void main(String[] args) { 13 SpringApplication.run(BankaccountApplication.class, args); 14 } 15 }
No final, teremos esta estrutura configurada:
Agora que temos nossos arquivos de criptografia e conexão de banco de dados de dados definidos, vamos criar nosso repositório que lidará com a comunicação com o Atlas e criaremos nossas queries. Para fazer isso, primeiro crie um registroBancoAccountEntity no pacote de recursos:
1 package com.mongodb.bankaccount.resources; 2 3 import com.mongodb.bankaccount.domain.BankAccount; 4 5 public record BankAccountEntity( 6 String accountHolderName, 7 String accountNumber, 8 int cardVerificationCode, 9 Double accountBalance) { 10 public BankAccount toDomain() { 11 return new BankAccount(accountHolderName, accountNumber, cardVerificationCode, accountBalance); 12 } 13 }
Em seguida, crie a classeBancoAccountRepository classe no pacote de recursos:
1 package com.mongodb.bankaccount.resources; 2 3 import com.mongodb.bankaccount.domain.BankAccount; 4 import com.mongodb.bankaccount.domain.BankAccountPort; 5 import com.mongodb.client.MongoCollection; 6 import com.mongodb.client.MongoDatabase; 7 import com.mongodb.client.result.InsertOneResult; 8 import org.bson.types.ObjectId; 9 import org.slf4j.Logger; 10 import org.slf4j.LoggerFactory; 11 import org.springframework.stereotype.Repository; 12 import java.util.ArrayList; 13 import java.util.List; 14 import java.util.Objects; 15 import java.util.stream.Collectors; 16 import static com.mongodb.client.model.Filters.eq; 17 import static com.mongodb.client.model.Filters.gt; 18 19 20 public class BankAccountRepository implements BankAccountPort { 21 private static final Logger logger = LoggerFactory.getLogger(BankAccountRepository.class); 22 private static final String COLLECTION_NAME = "accounts"; 23 private final MongoCollection<BankAccountEntity> collection; 24 25 BankAccountRepository(MongoDatabase mongoDatabase) { 26 this.collection = mongoDatabase.getCollection(COLLECTION_NAME, BankAccountEntity.class); 27 } 28 29 30 public String insert(BankAccount bankAccount) { 31 try { 32 InsertOneResult insertOneResult = collection.insertOne(bankAccount.toEntity()); 33 ObjectId result = Objects.requireNonNull(insertOneResult.getInsertedId()).asObjectId().getValue(); 34 35 logger.info("{} was created", result); 36 37 return result.toHexString(); 38 } catch (Exception e) { 39 logger.error(e.getMessage(), e); 40 throw new RuntimeException("Error inserting bank account", e); 41 } 42 } 43 44 45 public List<BankAccount> find() { 46 try { 47 ArrayList<BankAccountEntity> bankAccounts = new ArrayList<>(); 48 collection.find().into(bankAccounts); 49 50 return bankAccounts.stream() 51 .map(BankAccountEntity::toDomain) 52 .collect(Collectors.toList()); 53 54 } catch (Exception e) { 55 logger.error(e.getMessage(), e); 56 throw new RuntimeException("Error finding bank account", e); 57 } 58 } 59 60 61 public List<BankAccount> findByBalanceGreaterThan(double value) { 62 try { 63 ArrayList<BankAccountEntity> bankAccounts = new ArrayList<>(); 64 collection.find(gt("accountBalance", value)).into(bankAccounts); 65 66 return bankAccounts.stream() 67 .map(BankAccountEntity::toDomain) 68 .collect(Collectors.toList()); 69 70 } catch (Exception e) { 71 logger.error(e.getMessage(), e); 72 throw new RuntimeException("Error finding bank account", e); 73 } 74 } 75 76 77 public BankAccount findByAccountNumber(String accountNumber) { 78 try { 79 BankAccountEntity result = collection.find(eq("accountNumber", accountNumber)).first(); 80 81 if (result == null) { 82 return null; 83 } 84 85 return result.toDomain(); 86 87 } catch (Exception e) { 88 logger.error(e.getMessage(), e); 89 throw new RuntimeException("Error finding bank account", e); 90 } 91 92 } 93 }
Você pode estar se perguntando sobre alguns arquivos que ainda não criamos, mas não se preocupe, chegaremos a eles em breve. Antes de fazermos isso, vamos examinar o que nossa classe de repositório está fazendo:
A classeBancoAccountRepository classe configurada para interagir com nossa coleção de contas no MongoDB, fornecendo métodos para consultar e inserir dados.
Observe que temos um métodofindByBalanceGrandeerThan que executa uma consulta de intervalo para localizar contas com um saldo maior que um valor especificado. Também temos o métodofindByAccountNumber, que usa uma consulta de igualdade para localizar contas com um número de conta específico.
Além disso, você verá que BancoAccountRepository implementa uma interface chamada BancoAccountPort. Essa interface atua como um contrato, definindo os métodos que nossa classe de repositório deve implementar. Isso ajuda a isolar a lógica de domínio da camada de acesso a dados, proporcionando uma arquitetura mais limpa e testes mais fáceis.
Vamos continuar criando as classes de domínio restantes. Para fazer isso, criaremos um pacote de domínio e incluiremos o registroBankAccount:
1 package com.mongodb.bankaccount.domain; 2 3 import com.mongodb.bankaccount.application.web.BankResponse; 4 import com.mongodb.bankaccount.resources.BankAccountEntity; 5 6 public record BankAccount( 7 String accountHolderName, 8 String accountNumber, 9 int cardVerificationCode, 10 Double accountBalance) { 11 public BankResponse toResponse() { 12 return new BankResponse(accountHolderName, accountNumber, cardVerificationCode, accountBalance); 13 } 14 15 public BankAccountEntity toEntity() { 16 return new BankAccountEntity(accountHolderName, accountNumber, cardVerificationCode, accountBalance); 17 } 18 }
Em seguida, no mesmo pacote de domínio , a BancoAccountPort:
1 package com.mongodb.bankaccount.domain; 2 3 import java.util.List; 4 5 public interface BankAccountPort { 6 String insert(BankAccount bankAccount); 7 List<BankAccount> find(); 8 List<BankAccount> findByBalanceGreaterThan(double value); 9 BankAccount findByAccountNumber(String accountNumber); 10 }
E, finalmente, o serviço deconta bancária:
1 package com.mongodb.bankaccount.domain; 2 3 import org.springframework.stereotype.Service; 4 import java.util.List; 5 import java.util.Objects; 6 7 8 public class BankAccountService { 9 10 BankAccountPort bankAccountPort; 11 12 BankAccountService(BankAccountPort bankAccountPort) { 13 this.bankAccountPort = bankAccountPort; 14 } 15 16 public String insert(BankAccount bankAccount) { 17 Objects.requireNonNull(bankAccount, "Bank account must not be null"); 18 return bankAccountPort.insert(bankAccount); 19 } 20 21 public List<BankAccount> find() { 22 return bankAccountPort.find(); 23 } 24 25 public List<BankAccount> findByBalanceGreaterThan(double value) { 26 return bankAccountPort.findByBalanceGreaterThan(value); 27 } 28 29 public BankAccount findByAccountNumber(String accountNumber) { 30 return bankAccountPort.findByAccountNumber(accountNumber); 31 } 32 33 }
Nesta etapa final, forneceremos os endpoints do nosso controlador. Para fazer isso, crie um pacote chamado aplicação e dentro dele, crie a primeira classe BancoController:
1 package com.mongodb.bankaccount.application.web; 2 3 import com.mongodb.bankaccount.domain.BankAccount; 4 import com.mongodb.bankaccount.domain.BankAccountService; 5 import org.springframework.http.ResponseEntity; 6 import org.springframework.web.bind.annotation.*; 7 import java.util.List; 8 9 10 11 public class BankController { 12 13 BankAccountService bankAccountService; 14 15 BankController(BankAccountService bankAccountService) { 16 this.bankAccountService = bankAccountService; 17 } 18 19 20 ResponseEntity<String> create( BankRequest bankRequest) { 21 return ResponseEntity.ok(bankAccountService.insert(bankRequest.toDomain())); 22 } 23 24 25 ResponseEntity<List<BankResponse>> getAllAccounts() { 26 return ResponseEntity.ok(bankAccountService.find().stream().map(BankAccount::toResponse).toList()); 27 } 28 29 30 ResponseEntity<List<BankResponse>> findByBalanceGreaterThan( Double value) { 31 return ResponseEntity.ok(bankAccountService.findByBalanceGreaterThan(value).stream().map(BankAccount::toResponse).toList()); 32 } 33 34 35 ResponseEntity<BankResponse> getAccountByNumber( String accountNumber) { 36 BankAccount bankAccount = bankAccountService.findByAccountNumber(accountNumber); 37 if (bankAccount != null) { 38 return ResponseEntity.ok(bankAccount.toResponse()); 39 } else { 40 return ResponseEntity.notFound().build(); 41 } 42 } 43 }
Agora, vamos criar os dois registros ausentes e ajustar as importações necessárias. Ainda dentro do pacote .web do aplicação , crie o registroBancoRequest:
1 package com.mongodb.bankaccount.application.web; 2 3 import com.mongodb.bankaccount.domain.BankAccount; 4 5 record BankRequest( 6 String accountHolderName, 7 String accountNumber, 8 int cardVerificationCode, 9 Double accountBalance) { 10 public BankAccount toDomain() { 11 return new BankAccount(accountHolderName, accountNumber, cardVerificationCode, accountBalance); 12 } 13 }
E resposta do banco:
1 package com.mongodb.bankaccount.application.web; 2 3 public record BankResponse(String accountHolderName, 4 String accountNumber, 5 int cardVerificationCode, 6 Double accountBalance) { 7 }
Ideal! Agora, basta corrigir as importações nas classes que utilizam nossos registros e tudo estará definido. Nossa estrutura ficará assim:
Nesta etapa final, basta executar o seguinte comando para executar o aplicação:
1 ./mvnw spring-boot:run
Se o aplicação iniciar corretamente, abra o banco de dados de dados ao qual estamos nos conectando e você notará que os bancos de dadosdigitalBank e de criptografia, juntamente com as contas e coleções__keyVault, foram criados. Esta etapa acontece uma vez quando configuramos os arquivos na pasta recursos/config.
Para criar um novo documento, basta acessar o endpoint@POST descrito no BancoAccountController. Abaixo está um comando curl para inserir um novo documento:
1 curl --location 'http://localhost:8080/bank' \ 2 --header 'Content-Type: application/json' \ 3 --data '{ 4 "accountHolderName": "Ricardo Mello", 5 "accountNumber": "4527876391233218", 6 "cardVerificationCode": "761", 7 "accountBalance": 5000.2 8 }'
Após executar este comando curl, você verá que o documento foi criado com os campos accountNumber, cardVerificationCodee accountBalance criptografados conforme definido na classeEncryptionFieldConfig, conforme mostrado na imagem abaixo:
Observação: você verá que um camposafeContent campo criado. Este campo estará presente em todos os documentos criptografados e é crucial que esses dados não sejam modificados ou excluídos. Para mais informações, consulte encryption-collection-management.
Para explorar o tipo de igualdade de pesquisa criptografada, você pode pesquisar documentos por accountNumber. Para fazer isso, basta executar o seguinte comando curl:
1 curl --location 'http://localhost:8080/bank/accountNumber/{accountNumber}
Resultado:
Para explorar queries de intervalo (por exemplo, maiorTan), use o seguinte endpoint:
1 curl --location 'http://localhost:8080/bank/balance/greaterThan/{value}'
Resultado:
Nossa demonstração usando o aplicação Java "BankAccount" ilustra explicitamente como a Queryable Encryption do MongoDB pode ser implementada. Ao integrar a criptografia automática, o fluxo de trabalho é simplificado, permitindo que os desenvolvedores se concentrem nos principais recursos sem a necessidade de uma profunda experiência em segurança. A Queryable Encryption garante a execução segura de queries de igualdade e intervalo em dados criptografados, oferecendo um equilíbrio perfeita entre a forte proteção de dados e a capacidade de lidar com operações complexas.
Este tutorial ilustra uma abordagem para aproveitar o Queryable Encryption , mas é importante reconhecer que existem métodos e configurações alternativos. Incentivo você a explorar essas opções para identificar a solução mais adequada para suas necessidades específicas. Isso aumentará sua compreensão das práticas seguras de dados e aumentará sua capacidade de implementar estratégias eficazes de proteção de dados. Para ter uma visão completa do projeto Java , acesse o repositório em github-mongodb-developer.
Alguma pergunta? Junte-se a nós na MongoDB Developer Community
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.
Relacionado
Artigo
Mapa do Coronavírus e rastreador de dados ao vivo com o MongoDB Charts
Nov 15, 2023 | 3 min read
Tutorial
Como distribuir um aplicativo no Kubernetes com o MongoDB Atlas Operator
Aug 30, 2024 | 9 min read