Overview
このガイドでは、 MongoDB クライアント側フィールド レベル暗号化(CSFLE)をSpring Data MongoDBと統合する方法について説明します。
注意
以前に CSFLE を使用したことがない場合は、 MongoDB Serverマニュアルの「CSFLE チュートリアル」ガイドを参照してください。
以前にspring Data MongoDBを使用したことがない場合は、spring Data MongoDB のドキュメントを参照してください。
GitHub リポジトリ
このガイドのソースコードを表示するには GitHub の mongodb-java-spring-boot-csfle リポジトリ を参照してください。
次のコマンドを実行して、GitHubリポジトリからソースコードを取得することもできます。
git clone git@github.com:mongodb-developer/mongodb-java-spring-boot-csfle.git
前提条件
このガイドを使用する前に、次の手順を実行してください。
MongoDB自動暗号化共有ライブラリ v7.0.2 以降をインストール
例コードの実行方法については、README.md を参照してください。
また、MongoDB v7.0.2 以降を実行しているMongoDBクラスターにもアクセスする必要があります。
サンプル機能
この例では、本番環境向けのテンプレート コードを提供しており、次の機能を含めています。
複数の暗号化されたコレクション
自動JSON Schema 生成
サーバーサイドJSON schema
データ暗号化キー(DEK)と暗号化されたコレクションの分離されたクラスター
自動 DEK 生成または取得
プロセス式言語(SpEL)の評価拡張機能
自動実装リポジトリ
OpenAPI 3.0.1ドキュメント
テンプレートは、コードの読みやすさと再利用を高めるために SOLID の原則に従います。SOLID の詳細については、SOLID の Wikipedia を参照してください。
プロジェクト図
次の図は、フィールドを自動的に暗号化および復号化できる CSFLE 対応の MongoClient を作成するために必要なコンポーネントを示しています。図の矢印は、コンポーネント間の依存関係と関係を示しています。

図の矢印は、コンポーネント間の依存関係と関係を示しています。
アプリケーションがMongoDBとの接続を確立すると、次の図に示すように、3 階層アーキテクチャを使用してREST API を公開し、 MongoDBデータベースとの通信を管理します。

キーヴォールトコレクションの作成
このセクションでは、キーヴォールトコレクションとそのユニークインデックスを作成する方法について説明します。
新しいMongoDBクラスターから起動する場合は、まず keyAltNamesフィールドにキーヴォールトコレクションとそのユニークインデックスを作成する必要があります。
キーヴォールトと DEK を設定する
KeyVaultAndDekSetupクラスは、 MongoDBへの標準接続を使用して、キーヴォールトコレクションとそのユニークインデックスを作成します。次に、各 暗号化コレクション内のドキュメントを暗号化するために必要な DEK を作成します。
次のコードは、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); } } }
重要
本番環境では、keyAltNamesフィールドにキーヴォールトコレクションとそのユニークインデックスを手動で作成できます。これらを作成した後、このコードを除くことができます。
このコードでは、標準(CSFLE が有効になっていない)と一時的な MongoClient 接続を使用して、try と リソース ブロックを使用して、 MongoDBクラスターにコレクションとインデックスを作成します。
キーヴォールトコレクションの作成
KeyVaultServiceImplクラスはキーヴォールトコレクションと keyAltNamesユニークインデックスを作成します。
次のコードは、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)); } }
キーヴォールトコレクションとインデックスを作成したら、標準のMongoDB接続を閉じることができます。
データ暗号化キーの作成
このセクションでは、ClientEncryption 接続を使用して DEK を作成する方法を説明します。
キーヴォールト クライアントの構成
MongoDBKeyVaultClientConfigurationクラスは、DataEncryptionKeyService が DEK の作成に使用する ClientEncryption Bearer を作成します。
次のコードは、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); } }
KMS(Key Management System)とそれを使用してDEKを生成することで、ClientEncryption bean を作成できます。暗号化されたコレクションごとに 1 つの DEK が必要です。
データ暗号化キーサービスの実装
DataEncryptionKeyServiceImplクラスはDEK を作成して保存します。エンティティ内の SPEL 式を評価してJSONスキーマを作成する場合は、DEK を検索する必要があります。
次のコードは、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; } }
注意
DEK はマップに保存されるため、 JSONスキーマに対して後で再度検索する必要はありません。
エンティティを定義する
spring Data MongoDB は、POJO 中心モデルを使用してリポジトリを実装し、ドキュメントをMongoDBコレクションにマッピングします。
人物エンティティの作成
次のコードは、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; } }
先行するクラスには、CSFLE を自動化するために必要なすべての情報が含まれています。クラスは次の方法で @Encrypted アノテーションを使用します。
@Encryptedクラスの注釈には SEL式#{mongocrypt.keyId(#target)}が含まれています。この式は、前のステップで生成または検索した DEK のkeyIdを指定します。CSFLE は、暗号化にこの DEK を使用します。ssnフィールドの@Encrypted注釈は、決定的なアルゴリズムが必要であることを示します。bloodTypeフィールドの@Encrypted注釈は、ランダムなアルゴリズムが必要であることを示します。
このエンティティに対してSpring Data MongoDBによって生成されるJSON Schema は次のとおりです。
{ "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" } } } }
SPEL 評価の構成
EntitySpelEvaluationExtensionクラスは、SpEL式の評価を可能にします。次のコードは、このクラスを示しています。
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; } }
注意
このクラスはDEK を検索し、target と照合します。この例では、target は PersonEntity です。
JSONスキーマの生成と MongoClient の構成
次のセクションでは、spring Data MongoDBプロジェクトでJSONスキーマを生成する方法を示します。
スキーマ生成プロセスの理解
JSONスキーマを生成するには、spring Data の自動構成によって作成される MappingContext エンティティが必要です。自動構成により、MongoClient 接続と MongoTemplate が作成されます。
ただし、自動暗号化を有効にして MongoClient を作成するには、 JSONスキーマが必要です。
解決策としては、MongoClientSettingsBuilderCustomizer beans をインスタンス化して、自動構成プロセスにJSON Schema の作成を挿入することです。
安全なMongoDBクライアントの構成
次のコードは、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
異なるクラスターに対して異なるバックアップ保持ポリシーを使用する場合は、各クラスターに 1 つの DEK を保存できます。これにより、クラスターとすべてのバックアップから DEK を完全に削除できます。これは、コンプライアンスに必要になる場合があります。
詳細については、CSFLE とJavaドライバー を参照してください。
スキーマ サービスを実装する
次のコードは、生成されたJSONスキーマをマップに保存する SchemaServiceImplクラスを示しています。
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; } }
このテンプレートはサーバー側のJSON スキーマ も実装しているため、アプリケーションはJSON スキーマ を保存します。これは CSFLE のベストプラクティスです。
暗号化されたコレクションの作成または更新
サーバーサイドJSONスキーマの理解
自動暗号化共有ライブラリには、クライアント側のJSON スキーマ のみが必要です。ただし、サーバー側のJSON スキーマ を使用しないと、別の誤ったクライアントまたはクラスターに直接接続された管理者が、フィールドを暗号化せずにドキュメントを挿入または更新することができます。
これを防ぐために、サーバー側のJSON Schemaを使用してドキュメントにフィールド型を強制する。
JSON Schema はアプリケーションのさまざまなバージョンによって変更されるため、アプリケーションを再起動する たびにJSON Schema を更新する必要があります。
暗号化されたコレクションの設定を実装する
次のコードは、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())); } }
複数のエンティティをサポートする
このガイドのテンプレートは、任意の数のエンティティをサポートできます。
別のタイプのエンティティを追加するには、前の手順で説明したように 3 階層アーキテクチャのコンポーネントを作成します。次に、次の例に示すように、エンティティを EncryptedCollectionsConfigurationクラスに追加します。
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")); }
エンティティクラスのフィールドに @Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic") アノテーションを追加したため、フレームワークはサーバー側のJSON Schema を使用して DEK を自動生成し、 暗号化コレクションを作成します。
暗号化されたフィールドによるクエリ
このテンプレートは findFirstBySsn(ssn) メソッドを実装します。このメソッドを使用すると、このフィールドが暗号化されている場合でも、ソーシャル セキュリティ 番号で個人のドキュメントを検索できます。
注意
このメソッドは、決定的な暗号化アルゴリズムが使用されているためにのみ機能します。
人のリポジトリを実装する
次のコードは、PersonRepository インターフェースを示しています。
public interface PersonRepository extends MongoRepository<PersonEntity, String> { PersonEntity findFirstBySsn(String ssn); }
追加リソース
質問がある場合は、mongodb-java-spring-boot-csfleリポジトリで問題を開くか、MongoDB Community フォーラムで質問してください。
CSFLE の詳細については、次のリソースを参照してください。
CSFLE with the Java Driver (without Spring Data)
spring Data MongoDBの詳細については、次のリソースを参照してください。
このガイドのバージョンは、 MongoDB YouTube チャンネル でビデオ形式で見ることができます。