1. 概述

在这个教程中,我们将回顾Spring Kafka的信任包功能。我们将探讨其背后的动机,以及如何使用它,并通过实际示例来说明。

2. 前置条件

通常,Spring Kafka模块允许用户为发送的POJO指定一些元数据,这通常以Kafka消息头的形式出现。例如,如果我们这样配置ProducerFactory

@Bean
public ProducerFactory<Object, SomeData> producerFactory() {
    JsonSerializer<SomeData> jsonSerializer = new JsonSerializer<>();
    jsonSerializer.setAddTypeInfo(true);
    return new DefaultKafkaProducerFactory<>(
      producerFactoryConfig(),
      new StringOrBytesSerializer(),
      jsonSerializer
    );
}

@Data
@AllArgsConstructor
static class SomeData {

    private String id;
    private String type;
    private String status;
    private Instant timestamp;
}

那么我们就可以使用配置了上述producerFactoryKafkaTemplate向某个主题生产新消息:

public void sendDataIntoKafka() {
    SomeData someData = new SomeData("1", "active", "sent", Instant.now());
    kafkaTemplate.send(new ProducerRecord<>("sourceTopic", null, someData));
}

在这种情况下,我们在Kafka消费者的控制台会看到以下消息:

CreateTime:1701021806470 __TypeId__:com.baeldung.example.SomeData null {"id":"1","type":"active","status":"sent","timestamp":1701021806.153965150}

如我们所见,消息中的POJO类型信息在消息头中。这是Spring Kafka独有的特性,从其他框架的角度来看,这些头仅仅是元数据。因此,我们可以假设消费者和生产者都使用Spring处理Kafka消息。

3. 信任包功能

说到这里,我们可以说,在某些情况下,这是一个相当有用的功能。当主题中的消息有不同的负载模式时,为消费者提供负载类型提示将非常有用。

然而,一般来说,我们知道主题中的消息在模式上的可能性。因此,限制消费者可能接受的负载模式是一个好主意,这就是Spring Kafka的信任包功能的核心。

4. 使用示例

Spring Kafka的信任包功能是在反序列化级别配置的。如果配置了信任包,Spring会在接收到的消息类型头中进行查找。然后,它会检查提供的所有类型(键和值)是否都在信任范围内。这意味着消息中的键和值对应的Java类必须位于信任的包中。如果一切正常,Spring将继续进行反序列化。如果头信息不存在,Spring将直接反序列化对象,而不检查信任包:

@Bean
public ConsumerFactory<String, SomeData> someDataConsumerFactory() {
    JsonDeserializer<SomeData> payloadJsonDeserializer = new JsonDeserializer<>();
    payloadJsonDeserializer.addTrustedPackages("com.baeldung.example");
    return new DefaultKafkaConsumerFactory<>(
      consumerConfigs(),
      new StringDeserializer(),
      payloadJsonDeserializer
    );
}

值得一提的是,如果我们用星号(*)替换具体的包,Spring将信任所有包:

JsonDeserializer<SomeData> payloadJsonDeserializer = new JsonDeserializer<>();
payloadJsonDeserializer.trustedPackages("*");

但在这种情况下,使用信任包没有任何效果,只会增加额外的开销。现在让我们来看看我们刚刚看到的功能背后的动机。

5.1. 第一个动机:一致性

这个功能之所以优秀,主要有两个原因。首先,如果集群中发生错误,我们可以快速失败。想象一下,某个生产者意外地在一个不应该发布的主题上发布消息,这可能会导致很多问题,特别是如果成功反序列化了接收到的消息,整个系统的行为可能会变得不确定。

因此,如果生产者在消息中包含类型信息,并且消费者知道它信任哪些类型,那么可以避免这种情况。当然,这假设生产者的消息类型与消费者期望的不同,但这个假设是合理的,因为这个生产者根本不应该向这个主题发布消息。

5.2. 第二个动机:安全性

但最重要的是安全方面的考虑。在我们的例子中,我们强调生产者无意中向主题发布了消息。但这也可能是一种故意的攻击。恶意生产者可能会故意向特定主题发布消息,以利用反序列化漏洞。因此,通过阻止不想要的消息反序列化,Spring提供了额外的安全措施,以减少安全风险。

这里真正重要的是理解,信任包功能并不是针对“头信息欺骗”攻击的解决方案。在这种情况下,攻击者操纵消息头,欺骗接收者相信消息是合法的,来自可信来源。通过提供正确的类型头,攻击者可能会欺骗Spring,后者将继续执行消息反序列化。但这个问题相当复杂,不在本文讨论范围之内。总的来说,Spring只是提供了一个额外的安全措施,以尽量减少黑客成功的风险。

6. 总结

在这篇文章中,我们探索了Spring Kafka的信任包功能。这个功能为我们的分布式消息系统提供了额外的一致性和安全性。但请记住,信任包仍然对头信息欺骗攻击开放。尽管如此,Spring Kafka在提供额外安全措施方面做得很好。

如往常一样,这篇文章的源代码可在GitHub上找到。