此版本仍在开发中,尚未被视为稳定版。如需最新稳定版本,请使用 Spring Data MongoDB 5.0.4spring-doc.cadn.net.cn

加密

客户端加密(Client Side Encryption)是一项在您的应用程序中对数据进行加密的功能,然后再将数据发送到 MongoDB。 我们建议您先熟悉相关概念,理想情况下,请参阅MongoDB 官方文档,以深入了解其功能和限制,然后再通过 Spring Data 应用加密功能。spring-doc.cadn.net.cn

请务必设置驱动程序的 com.mongodb.AutoEncryptionSettings 以使用客户端加密。 MongoDB 并非对所有字段类型都支持加密。 某些特定数据类型需要使用确定性加密,以保留相等性比较功能。spring-doc.cadn.net.cn

客户端字段级加密 (CSFLE)

选择 CSFLE 可为您提供完全的灵活性,并允许为单个字段使用不同的密钥,例如在“每个租户一个密钥”的场景中。
请确保咨询MongoDB CSFLE 文档在继续阅读之前。spring-doc.cadn.net.cn

自动加密 (CSFLE)

MongoDB 通过其驱动程序内置支持客户端字段级加密(Client-Side Field Level Encryption),并提供自动加密功能。 自动加密需要一个JSON Schema,使得在执行加密的读写操作时无需显式提供加密/解密步骤。spring-doc.cadn.net.cn

有关定义包含加密信息的 JSON Schema 的更多信息,请参阅JSON Schema部分。spring-doc.cadn.net.cn

要使用 MongoJsonSchema,您必须将其与 AutoEncryptionSettings 一起提供,例如可以通过 MongoClientSettingsBuilderCustomizer 来实现。spring-doc.cadn.net.cn

@Bean
MongoClientSettingsBuilderCustomizer customizer(MappingContext mappingContext) {
    return (builder) -> {

        // ... keyVaultCollection, kmsProvider, ...

        MongoJsonSchemaCreator schemaCreator = MongoJsonSchemaCreator.create(mappingContext);
        MongoJsonSchema patientSchema = schemaCreator
            .filter(MongoJsonSchemaCreator.encryptedOnly())
            .createSchemaFor(Patient.class);

        AutoEncryptionSettings autoEncryptionSettings = AutoEncryptionSettings.builder()
            .keyVaultNamespace(keyVaultCollection)
            .kmsProviders(kmsProviders)
            .extraOptions(extraOpts)
            .schemaMap(Collections.singletonMap("db.patient", patientSchema.schemaDocument().toBsonDocument()))
            .build();

        builder.autoEncryptionSettings(autoEncryptionSettings);
    };
}

显式加密(CSFLE)

显式加密使用 MongoDB 驱动程序的加密库(org.mongodb:mongodb-crypt)来执行加密和解密任务。 @ExplicitEncrypted 注解是用于JSON Schema 创建mapping/mapping-schema.html#mongo.jsonSchema.encrypted-fields 注解与属性转换器(Property Converter)的组合。 换句话说,@ExplicitEncrypted 利用现有的构建模块,将它们组合起来以简化对显式加密的支持。spring-doc.cadn.net.cn

使用 @ExplicitEncrypted 注解的字段始终会作为整体进行加密。 请考虑以下示例:spring-doc.cadn.net.cn

@ExplicitEncrypted(…)
String simpleValue;        (1)

@ExplicitEncrypted(…)
Address address;           (2)

@ExplicitEncrypted(…)
List<...> list;            (3)

@ExplicitEncrypted(…)
Map<..., ...> mapOfString; (4)
1 如果值不为 String,则对简单类型(例如 null)的值进行加密。
2 将整个 Address 对象及其所有嵌套字段作为 Document 进行加密。 若仅需加密 Address 的部分内容(例如 Address#street),则需在 street 内的 Address 字段上添加 @ExplicitEncrypted 注解。
3 Collection 类型的字段将作为单个值进行加密,而不是对每个条目分别加密。
4 Map 类型的字段会被加密为单个值,而不是以键/值条目的形式进行加密。

客户端字段级加密允许您在确定性算法和随机化算法之间进行选择。根据所选算法的不同,可能支持不同的操作。 要指定某种算法,请使用@ExplicitEncrypted(algorithm),有关算法常量,请参见EncryptionAlgorithms。 有关算法及其用法的更多信息,请阅读加密类型手册。spring-doc.cadn.net.cn

为了执行实际的加密操作,我们需要一个数据加密密钥(DEK)。 有关如何设置密钥管理以及创建数据加密密钥的更多信息,请参阅MongoDB 文档。 可以通过 DEK 的 id 或其定义的替代名称直接引用该密钥。 @EncryptedField 注解仅允许通过替代名称引用 DEK。 可以为任意 DEK 提供一个 EncryptionKeyResolver,这将在后文讨论。spring-doc.cadn.net.cn

示例 1. 引用数据加密密钥
@EncryptedField(algorithm=…, altKeyName = "secret-key") (1)
String ssn;
@EncryptedField(algorithm=…, altKeyName = "/name")      (2)
String ssn;
1 使用以别名 secret-key 存储的 DEK。
2 使用一个字段引用,该引用将读取实际的字段值并将其用于键查找。 保存操作始终要求完整文档存在。 字段不能用于查询/聚合。

默认情况下,@ExplicitEncrypted(value=…) 注解引用一个 MongoEncryptionConverter。 通过提供相应的类型引用,可以更改默认实现,并将其替换为任意的 PropertyValueConverter 实现。 有关自定义 PropertyValueConverters 及其所需配置的更多信息,请参阅属性转换器 - 映射特定字段部分。spring-doc.cadn.net.cn

可查询加密 (QE)

选择 QE 可让您运行不同类型的查询,例如范围 or 平等,针对加密字段。
请确保咨询MongoDB QE 文档在继续阅读以了解 QE 的功能和限制之前。spring-doc.cadn.net.cn

集合设置

可查询加密要求在对加密字段执行实际查询之前,预先声明该查询中允许使用的某些方面。 这些信息包括所使用的算法、允许的查询类型及其属性,并且在创建集合时必须提供。spring-doc.cadn.net.cn

MongoOperations#createCollection(…​) 可用于为使用 QE 的集合进行初始设置。 通过 Spring Data 配置 QE 所使用的构建模块(JSON Schema 创建)与 CSFLE 相同,会将 schema/属性转换为 MongoDB 所需的配置格式。spring-doc.cadn.net.cn

您可以手动配置可查询加密,也可以采用派生的方式进行配置:spring-doc.cadn.net.cn

手动设置使您可以完全控制加密字段的声明方式以及集合的创建方式。 当您需要显式管理数据密钥、加密算法和字段映射时,这种方式非常有用。spring-doc.cadn.net.cn

派生配置依赖于您领域模型中的注解,并自动从中生成所需的加密字段配置。 这种方式更简单,推荐用于典型 Spring 应用程序,尤其是当您的数据模型已经带有注解时。spring-doc.cadn.net.cn

BsonBinary pinDK = clientEncryption.createDataKey("local", new com.mongodb.client.model.vault.DataKeyOptions());
BsonBinary ssnDK = clientEncryption.createDataKey("local", new com.mongodb.client.model.vault.DataKeyOptions());
BsonBinary ageDK = clientEncryption.createDataKey("local", new com.mongodb.client.model.vault.DataKeyOptions());
BsonBinary signDK = clientEncryption.createDataKey("local", new com.mongodb.client.model.vault.DataKeyOptions());

CollectionOptions collectionOptions = CollectionOptions.encryptedCollection(options -> options
    .encrypted(string("pin"), pinDK)
    .queryable(encrypted(string("ssn")).algorithm("Indexed").keyId(ssnDK.asUuid()), equality().contention(0))
    .queryable(encrypted(int32("age")).algorithm("Range").keyId(ageDK.asUuid()), range().contention(8).min(0).max(150))
    .queryable(encrypted(int64("address.sign")).algorithm("Range").keyId(signDK.asUuid()), range().contention(2).min(-10L).max(10L))
);

mongoTemplate.createCollection(Patient.class, collectionOptions); (1)
1 使用模板创建集合可能会导致无法捕获生成的 keyId。在这种情况下,请从选项中渲染 Document,并通过加密库使用 createEncryptedCollection(…​) 方法。
class Patient {

    @Id String id;        (1)

    Address address;      (1)

    @Encrypted(algorithm = "Unindexed")
    String pin;           (2)

    @Encrypted(algorithm = "Indexed")
    @Queryable(queryType = "equality", contentionFactor = 0)
    String ssn;           (3)

    @RangeEncrypted(contentionFactor = 8, rangeOptions = "{ 'min' : 0, 'max' : 150 }")
    Integer age;          (4)

    @RangeEncrypted(contentionFactor = 0L,
        rangeOptions = "{\"min\": {\"$numberDouble\": \"0.3\"}, \"max\": {\"$numberDouble\": \"2.5\"}, \"precision\": 2 }")
    double height;        (5)
}

MongoJsonSchema patientSchema = MongoJsonSchemaCreator.create(mappingContext)
    .filter(MongoJsonSchemaCreator.encryptedOnly())
    .createSchemaFor(Patient.class);

Document encryptedFields = CollectionOptions.encryptedCollection(patientSchema)
        .getEncryptedFieldsOptions()
        .map(CollectionOptions.EncryptedFieldsOptions::toDocument)
        .orElseThrow();

template.execute(db -> clientEncryption.createEncryptedCollection(db, template.getCollectionName(Patient.class), new CreateCollectionOptions()
        .encryptedFields(encryptedFields), new CreateEncryptedCollectionParams("local"))); (1)
1 idaddress 未被加密。 这些字段可以正常查询。
2 pin 是加密的,但不支持查询。
3 ssn 已加密,并支持相等性查询。
4 age 已加密,并允许在 0150 之间进行范围查询。
5 height 已加密,并允许在 0.32.5 之间进行范围查询。

Queryable 注解用于定义加密字段所允许的查询类型。 @RangeEncrypted@Encrypted@Queryable 的组合注解,用于支持 range(范围)查询的字段。 可以基于提供的注解创建自定义注解。spring-doc.cadn.net.cn

{
    name: 'patient',
    type: 'collection',
    options: {
      encryptedFields: {
        escCollection: 'enxcol_.test.esc',
        ecocCollection: 'enxcol_.test.ecoc',
        fields: [
          {
            keyId: ...,
            path: 'ssn',
            bsonType: 'string',
            queries: [ { queryType: 'equality', contention: Long('0') } ]
          },
          {
            keyId: ...,
            path: 'age',
            bsonType: 'int',
            queries: [ { queryType: 'range', contention: Long('8'), min: 0, max: 150 } ]
          },
          {
            keyId: ...,
            path: 'pin',
            bsonType: 'string'
          },
          {
            keyId: ...,
            path: 'address.sign',
            bsonType: 'long',
            queries: [ { queryType: 'range', contention: Long('2'), min: Long('-10'), max: Long('10') } ]
          }
        ]
      }
    }
}

自动加密 (QE)

MongoDB 通过其驱动程序内置支持可查询加密(Queryable Encryption),并提供自动加密(Automatic Encryption)功能。 自动加密需要一个JSON Schema,从而无需显式执行加密/解密步骤即可进行加密的读写操作。spring-doc.cadn.net.cn

您只需根据 MongoDB 文档创建集合即可。 您可以使用上文所述的技术来创建所需的配置。spring-doc.cadn.net.cn

显式加密 (QE)

显式加密使用 MongoDB 驱动程序的加密库(org.mongodb:mongodb-crypt),根据领域模型中注解所提供的元信息来执行加密和解密任务。spring-doc.cadn.net.cn

目前没有对使用显式可查询加密(Explicit Queryable Encryption)的官方支持。 敢于尝试的用户可以自行将 @Encrypted@Queryable@ValueConverter(MongoEncryptionConverter.class) 结合使用,但需自行承担相关风险。spring-doc.cadn.net.cn

MongoEncryptionConverter 设置

MongoEncryptionConverter 设置转换器需要几个步骤,因为涉及多个组件。 Bean 的配置包括以下内容:spring-doc.cadn.net.cn

  1. ClientEncryption 引擎spring-doc.cadn.net.cn

  2. 一个使用 MongoEncryptionConverterClientEncryption 配置的 EncryptionKeyResolver 实例。spring-doc.cadn.net.cn

  3. 一个使用已注册的 PropertyValueConverterFactory Bean 的 MongoEncryptionConverterspring-doc.cadn.net.cn

EncryptionKeyResolver 使用一个 EncryptionContext,该上下文提供对属性的访问,从而实现动态 DEK 解析。spring-doc.cadn.net.cn

示例2. MongoEncryptionConverter 配置示例
class Config extends AbstractMongoClientConfiguration {

    @Autowired ApplicationContext appContext;

    @Bean
    ClientEncryption clientEncryption() {                                                            (1)
        ClientEncryptionSettings encryptionSettings = ClientEncryptionSettings.builder();
        // …

        return ClientEncryptions.create(encryptionSettings);
    }

    @Bean
    MongoEncryptionConverter encryptingConverter(ClientEncryption clientEncryption) {

        Encryption<BsonValue, BsonBinary> encryption = MongoClientEncryption.just(clientEncryption);
        EncryptionKeyResolver keyResolver = EncryptionKeyResolver.annotated((ctx) -> …);             (2)

        return new MongoEncryptionConverter(encryption, keyResolver);                                (3)
    }

    @Override
    protected void configureConverters(MongoConverterConfigurationAdapter adapter) {

        adapter
            .registerPropertyValueConverterFactory(PropertyValueConverterFactory.beanFactoryAware(appContext)); (4)
    }
}
1 使用 Encryption 设置一个 com.mongodb.client.vault.ClientEncryption 引擎。 该实例是有状态的,使用后必须关闭。 Spring 会负责处理这一点,因为 ClientEncryption 实现了 Closeable 接口。
2 设置一个基于注解的 EncryptionKeyResolver,用于从注解中确定 EncryptionKey
3 创建 MongoEncryptionConverter
4 启用从 PropertyValueConverter 中查找 BeanFactory