BAEL-6046 - MongoDB - Field Level Encryption (#13266)
This commit is contained in:
parent
f6f63673a9
commit
1f987c4bb3
|
@ -24,6 +24,11 @@
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mongodb</groupId>
|
||||||
|
<artifactId>mongodb-crypt</artifactId>
|
||||||
|
<version>${mongodb-crypt.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>de.flapdoodle.embed</groupId>
|
<groupId>de.flapdoodle.embed</groupId>
|
||||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||||
|
@ -31,4 +36,8 @@
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<mongodb-crypt.version>1.6.1</mongodb-crypt.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.baeldung.boot.csfle;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class MongoDbCsfleApplication {
|
||||||
|
|
||||||
|
public static void main(String... args) {
|
||||||
|
SpringApplication.run(MongoDbCsfleApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package com.baeldung.boot.csfle.config;
|
||||||
|
|
||||||
|
import org.bson.BsonBinary;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import com.mongodb.client.vault.ClientEncryption;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class EncryptionConfig {
|
||||||
|
|
||||||
|
@Value("${com.baeldung.csfle.master-key-path}")
|
||||||
|
private String masterKeyPath;
|
||||||
|
|
||||||
|
@Value("${com.baeldung.csfle.key-vault.namespace}")
|
||||||
|
private String keyVaultNamespace;
|
||||||
|
|
||||||
|
@Value("${com.baeldung.csfle.key-vault.alias}")
|
||||||
|
private String keyVaultAlias;
|
||||||
|
|
||||||
|
@Value("${com.baeldung.csfle.auto-decryption:false}")
|
||||||
|
private Boolean autoDecryption;
|
||||||
|
|
||||||
|
private ClientEncryption encryption;
|
||||||
|
|
||||||
|
private BsonBinary dataKeyId;
|
||||||
|
|
||||||
|
public void setEncryption(ClientEncryption encryption) {
|
||||||
|
this.encryption = encryption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientEncryption getEncryption() {
|
||||||
|
return encryption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataKeyId(BsonBinary dataKeyId) {
|
||||||
|
this.dataKeyId = dataKeyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BsonBinary getDataKeyId() {
|
||||||
|
return dataKeyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKeyVaultNamespace() {
|
||||||
|
return keyVaultNamespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKeyVaultAlias() {
|
||||||
|
return keyVaultAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMasterKeyPath() {
|
||||||
|
return masterKeyPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getAutoDecryption() {
|
||||||
|
return autoDecryption;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.baeldung.boot.csfle.config;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class LocalKmsUtils {
|
||||||
|
|
||||||
|
private static final int KEY_SIZE = 96;
|
||||||
|
|
||||||
|
private LocalKmsUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] createMasterKey(String path) throws FileNotFoundException, IOException {
|
||||||
|
byte[] masterKey = new byte[KEY_SIZE];
|
||||||
|
new SecureRandom().nextBytes(masterKey);
|
||||||
|
|
||||||
|
try (FileOutputStream stream = new FileOutputStream(path)) {
|
||||||
|
stream.write(masterKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return masterKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] readMasterKey(String path) throws FileNotFoundException, IOException {
|
||||||
|
byte[] masterKey = new byte[KEY_SIZE];
|
||||||
|
|
||||||
|
try (FileInputStream stream = new FileInputStream(path)) {
|
||||||
|
stream.read(masterKey, 0, KEY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return masterKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Map<String, Object>> providersMap(String masterKeyPath) throws FileNotFoundException, IOException {
|
||||||
|
if (masterKeyPath == null)
|
||||||
|
throw new IllegalArgumentException("master key path cannot be null");
|
||||||
|
|
||||||
|
File masterKeyFile = new File(masterKeyPath);
|
||||||
|
byte[] masterKey = masterKeyFile.isFile()
|
||||||
|
? readMasterKey(masterKeyPath)
|
||||||
|
: createMasterKey(masterKeyPath);
|
||||||
|
|
||||||
|
Map<String, Object> masterKeyMap = new HashMap<>();
|
||||||
|
masterKeyMap.put("key", masterKey);
|
||||||
|
Map<String, Map<String, Object>> providersMap = new HashMap<>();
|
||||||
|
providersMap.put("local", masterKeyMap);
|
||||||
|
return providersMap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package com.baeldung.boot.csfle.config;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.bson.BsonBinary;
|
||||||
|
import org.bson.BsonDocument;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.bson.conversions.Bson;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
|
||||||
|
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
||||||
|
|
||||||
|
import com.baeldung.boot.csfle.config.converter.IntegerConverter;
|
||||||
|
import com.baeldung.boot.csfle.config.converter.StringConverter;
|
||||||
|
import com.mongodb.AutoEncryptionSettings;
|
||||||
|
import com.mongodb.ClientEncryptionSettings;
|
||||||
|
import com.mongodb.ConnectionString;
|
||||||
|
import com.mongodb.MongoClientSettings;
|
||||||
|
import com.mongodb.MongoClientSettings.Builder;
|
||||||
|
import com.mongodb.MongoNamespace;
|
||||||
|
import com.mongodb.client.MongoClient;
|
||||||
|
import com.mongodb.client.MongoClients;
|
||||||
|
import com.mongodb.client.MongoCollection;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import com.mongodb.client.model.IndexOptions;
|
||||||
|
import com.mongodb.client.model.Indexes;
|
||||||
|
import com.mongodb.client.model.vault.DataKeyOptions;
|
||||||
|
import com.mongodb.client.vault.ClientEncryption;
|
||||||
|
import com.mongodb.client.vault.ClientEncryptions;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class MongoClientConfig extends AbstractMongoClientConfiguration {
|
||||||
|
|
||||||
|
@Value("${spring.data.mongodb.uri}")
|
||||||
|
private String uri;
|
||||||
|
|
||||||
|
@Value("${spring.data.mongodb.database}")
|
||||||
|
private String db;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EncryptionConfig encryptionConfig;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDatabaseName() {
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MongoCustomConversions customConversions() {
|
||||||
|
return new MongoCustomConversions(Arrays.asList(new StringConverter(encryptionConfig), new IntegerConverter(encryptionConfig)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MongoClient mongoClient() {
|
||||||
|
MongoClient client;
|
||||||
|
try {
|
||||||
|
client = MongoClients.create(clientSettings());
|
||||||
|
|
||||||
|
ClientEncryption encryption = createClientEncryption();
|
||||||
|
encryptionConfig.setDataKeyId(createOrRetrieveDataKey(client, encryption));
|
||||||
|
|
||||||
|
return client;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("unable to create client", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BsonBinary createOrRetrieveDataKey(MongoClient client, ClientEncryption encryption) {
|
||||||
|
MongoNamespace namespace = new MongoNamespace(encryptionConfig.getKeyVaultNamespace());
|
||||||
|
MongoCollection<Document> keyVault = client.getDatabase(namespace.getDatabaseName())
|
||||||
|
.getCollection(namespace.getCollectionName());
|
||||||
|
|
||||||
|
Bson query = Filters.in("keyAltNames", encryptionConfig.getKeyVaultAlias());
|
||||||
|
BsonDocument key = keyVault.withDocumentClass(BsonDocument.class)
|
||||||
|
.find(query)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (key == null) {
|
||||||
|
keyVault.createIndex(Indexes.ascending("keyAltNames"), new IndexOptions().unique(true)
|
||||||
|
.partialFilterExpression(Filters.exists("keyAltNames")));
|
||||||
|
|
||||||
|
DataKeyOptions options = new DataKeyOptions();
|
||||||
|
options.keyAltNames(Arrays.asList(encryptionConfig.getKeyVaultAlias()));
|
||||||
|
return encryption.createDataKey("local", options);
|
||||||
|
} else {
|
||||||
|
return (BsonBinary) key.get("_id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientEncryption createClientEncryption() throws FileNotFoundException, IOException {
|
||||||
|
Map<String, Map<String, Object>> kmsProviders = LocalKmsUtils.providersMap(encryptionConfig.getMasterKeyPath());
|
||||||
|
|
||||||
|
ClientEncryptionSettings encryptionSettings = ClientEncryptionSettings.builder()
|
||||||
|
.keyVaultMongoClientSettings(clientSettings())
|
||||||
|
.keyVaultNamespace(encryptionConfig.getKeyVaultNamespace())
|
||||||
|
.kmsProviders(kmsProviders)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
encryptionConfig.setEncryption(ClientEncryptions.create(encryptionSettings));
|
||||||
|
return encryptionConfig.getEncryption();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MongoClientSettings clientSettings() throws FileNotFoundException, IOException {
|
||||||
|
Builder settings = MongoClientSettings.builder()
|
||||||
|
.applyConnectionString(new ConnectionString(uri));
|
||||||
|
|
||||||
|
if (encryptionConfig.getAutoDecryption()) {
|
||||||
|
settings.autoEncryptionSettings(AutoEncryptionSettings.builder()
|
||||||
|
.keyVaultNamespace(encryptionConfig.getKeyVaultNamespace())
|
||||||
|
.kmsProviders(LocalKmsUtils.providersMap(encryptionConfig.getMasterKeyPath()))
|
||||||
|
.bypassAutoEncryption(true)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.baeldung.boot.csfle.config.converter;
|
||||||
|
|
||||||
|
import org.bson.BsonBinary;
|
||||||
|
import org.bson.BsonValue;
|
||||||
|
import org.bson.types.Binary;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
|
||||||
|
import com.baeldung.boot.csfle.config.EncryptionConfig;
|
||||||
|
|
||||||
|
public class IntegerConverter implements Converter<Binary, Integer> {
|
||||||
|
|
||||||
|
private EncryptionConfig encryptionConfig;
|
||||||
|
|
||||||
|
public IntegerConverter(EncryptionConfig config) {
|
||||||
|
this.encryptionConfig = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer convert(Binary source) {
|
||||||
|
BsonBinary bin = new BsonBinary(source.getType(), source.getData());
|
||||||
|
BsonValue value = encryptionConfig.getEncryption()
|
||||||
|
.decrypt(bin);
|
||||||
|
|
||||||
|
return value.asInt32()
|
||||||
|
.getValue();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.baeldung.boot.csfle.config.converter;
|
||||||
|
|
||||||
|
import org.bson.BsonBinary;
|
||||||
|
import org.bson.BsonValue;
|
||||||
|
import org.bson.types.Binary;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
|
||||||
|
import com.baeldung.boot.csfle.config.EncryptionConfig;
|
||||||
|
|
||||||
|
public class StringConverter implements Converter<Binary, String> {
|
||||||
|
|
||||||
|
private EncryptionConfig encryptionConfig;
|
||||||
|
|
||||||
|
public StringConverter(EncryptionConfig config) {
|
||||||
|
this.encryptionConfig = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String convert(Binary source) {
|
||||||
|
BsonBinary bin = new BsonBinary(source.getType(), source.getData());
|
||||||
|
BsonValue value = encryptionConfig.getEncryption()
|
||||||
|
.decrypt(bin);
|
||||||
|
|
||||||
|
return value.asString()
|
||||||
|
.getValue();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.baeldung.boot.csfle.data;
|
||||||
|
|
||||||
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
|
@Document("citizens")
|
||||||
|
public class Citizen {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String email;
|
||||||
|
private Integer birthYear;
|
||||||
|
|
||||||
|
public Citizen() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Citizen(EncryptedCitizen encryptedCitizen) {
|
||||||
|
this.name = encryptedCitizen.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getBirthYear() {
|
||||||
|
return birthYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBirthYear(Integer birthYear) {
|
||||||
|
this.birthYear = birthYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Citizen [name=" + name + ", email=" + email + ", birthYear=" + birthYear + "]";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.baeldung.boot.csfle.data;
|
||||||
|
|
||||||
|
import org.bson.BsonBinary;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
|
@Document("citizens")
|
||||||
|
public class EncryptedCitizen {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private BsonBinary email;
|
||||||
|
private BsonBinary birthYear;
|
||||||
|
|
||||||
|
public EncryptedCitizen() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public EncryptedCitizen(Citizen citizen) {
|
||||||
|
this.name = citizen.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BsonBinary getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(BsonBinary email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BsonBinary getBirthYear() {
|
||||||
|
return birthYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBirthYear(BsonBinary birthYear) {
|
||||||
|
this.birthYear = birthYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Citizen [name=" + name + ", email=" + email + ", birthYear=" + birthYear + "]";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.baeldung.boot.csfle.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.bson.BsonBinary;
|
||||||
|
import org.bson.BsonInt32;
|
||||||
|
import org.bson.BsonString;
|
||||||
|
import org.bson.BsonValue;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||||
|
import org.springframework.data.mongodb.core.query.Criteria;
|
||||||
|
import org.springframework.data.mongodb.core.query.Query;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.baeldung.boot.csfle.config.EncryptionConfig;
|
||||||
|
import com.baeldung.boot.csfle.data.Citizen;
|
||||||
|
import com.baeldung.boot.csfle.data.EncryptedCitizen;
|
||||||
|
import com.mongodb.client.model.vault.EncryptOptions;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CitizenService {
|
||||||
|
|
||||||
|
public static final String DETERMINISTIC_ALGORITHM = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic";
|
||||||
|
public static final String RANDOM_ALGORITHM = "AEAD_AES_256_CBC_HMAC_SHA_512-Random";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MongoTemplate mongo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EncryptionConfig encryptionConfig;
|
||||||
|
|
||||||
|
public EncryptedCitizen save(Citizen citizen) {
|
||||||
|
EncryptedCitizen encryptedCitizen = new EncryptedCitizen(citizen);
|
||||||
|
encryptedCitizen.setEmail(encrypt(citizen.getEmail(), DETERMINISTIC_ALGORITHM));
|
||||||
|
encryptedCitizen.setBirthYear(encrypt(citizen.getBirthYear(), RANDOM_ALGORITHM));
|
||||||
|
|
||||||
|
return mongo.save(encryptedCitizen);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Citizen> findAll() {
|
||||||
|
return mongo.findAll(Citizen.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Citizen findByEmail(String email) {
|
||||||
|
Query byEmail = new Query(Criteria.where("email")
|
||||||
|
.is(encrypt(email, DETERMINISTIC_ALGORITHM)));
|
||||||
|
return mongo.findOne(byEmail, Citizen.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BsonBinary encrypt(Object value, String algorithm) {
|
||||||
|
if (value == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
BsonValue bsonValue = value instanceof Integer
|
||||||
|
? new BsonInt32((Integer) value)
|
||||||
|
: new BsonString(value.toString());
|
||||||
|
|
||||||
|
EncryptOptions options = new EncryptOptions(algorithm);
|
||||||
|
options.keyId(encryptionConfig.getDataKeyId());
|
||||||
|
return encryptionConfig.getEncryption()
|
||||||
|
.encrypt(bsonValue, options);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.baeldung.boot.csfle.web;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.baeldung.boot.csfle.data.Citizen;
|
||||||
|
import com.baeldung.boot.csfle.data.EncryptedCitizen;
|
||||||
|
import com.baeldung.boot.csfle.service.CitizenService;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/citizen")
|
||||||
|
public class CitizenController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CitizenService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<Citizen> get() {
|
||||||
|
return service.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("by")
|
||||||
|
public Citizen getBy(@RequestParam String email) {
|
||||||
|
return service.findByEmail(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public EncryptedCitizen post(@RequestBody Citizen citizen) {
|
||||||
|
return service.save(citizen);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.baeldung.boot.csfle;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.bson.BsonBinary;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||||
|
import org.springframework.data.mongodb.core.query.Criteria;
|
||||||
|
import org.springframework.data.mongodb.core.query.Query;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import com.baeldung.boot.csfle.data.Citizen;
|
||||||
|
import com.baeldung.boot.csfle.data.EncryptedCitizen;
|
||||||
|
import com.baeldung.boot.csfle.service.CitizenService;
|
||||||
|
|
||||||
|
@DirtiesContext
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@TestPropertySource("/embedded.properties")
|
||||||
|
@SpringBootTest(classes = MongoDbCsfleApplication.class)
|
||||||
|
public class CitizenServiceLiveTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MongoTemplate mongo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CitizenService service;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenCitizen_whenEncryptingEmail_thenEncryptedCitizenEmailMatches() {
|
||||||
|
final Citizen citizen = new Citizen();
|
||||||
|
citizen.setName("Foo");
|
||||||
|
citizen.setEmail("foo@citizen.com");
|
||||||
|
|
||||||
|
BsonBinary encryptedEmail = service.encrypt(citizen.getEmail(), CitizenService.DETERMINISTIC_ALGORITHM);
|
||||||
|
|
||||||
|
EncryptedCitizen saved = service.save(citizen);
|
||||||
|
assertEquals(encryptedEmail, saved.getEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenRandomEncryptedField_whenFilteringByField_thenDocumentNotFound() {
|
||||||
|
Citizen john = new Citizen();
|
||||||
|
john.setName("Jane Doe");
|
||||||
|
john.setEmail("jane.doe@citizen.com");
|
||||||
|
john.setBirthYear(1852);
|
||||||
|
|
||||||
|
service.save(john);
|
||||||
|
|
||||||
|
Query byBirthYear = new Query(Criteria.where("birthYear")
|
||||||
|
.is(service.encrypt(john.getBirthYear(), CitizenService.RANDOM_ALGORITHM)));
|
||||||
|
Citizen result = mongo.findOne(byBirthYear, Citizen.class);
|
||||||
|
|
||||||
|
assertNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenDeterministicallyEncryptedField_whenFilteringByField_thenDocumentFound() {
|
||||||
|
Citizen jane = new Citizen();
|
||||||
|
jane.setName("Jane Doe");
|
||||||
|
jane.setEmail("jane.doe@citizen.com");
|
||||||
|
jane.setBirthYear(1952);
|
||||||
|
|
||||||
|
service.save(jane);
|
||||||
|
Citizen result = service.findByEmail(jane.getEmail());
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,10 @@
|
||||||
spring.mongodb.embedded.version=4.4.9
|
spring.mongodb.embedded.version=4.4.9
|
||||||
|
|
||||||
|
spring.data.mongodb.uri=changeit
|
||||||
|
spring.data.mongodb.database=changeit
|
||||||
|
|
||||||
|
com.baeldung.csfle.kms-provider=local
|
||||||
|
com.baeldung.csfle.key-vault.namespace=encryption._keyVault
|
||||||
|
com.baeldung.csfle.key-vault.alias=master.key
|
||||||
|
com.baeldung.csfle.master-key-path=/tmp/master.key
|
||||||
|
com.baeldung.csfle.auto-decryption=false
|
||||||
|
|
Loading…
Reference in New Issue