Introdução
Por que autenticação é importante no Kafka?
- Produzir eventos falsos em qualquer tópico: um serviço comprometido pode injetar pedidos fraudulentos, acionar workflows indevidos ou corromper o estado de outros sistemas
- Consumir dados sensíveis de tópicos que não deveria acessar: um container com vulnerabilidade pode virar um ponto de exfiltração de dados
- Fazer spoofing de producers: enviar mensagens se passando por outro serviço, quebrando rastreabilidade e trilhas de auditoria
O que é OAuth 2.0?
- A aplicação (producer ou consumer) se autentica no servidor OAuth usando client_id e client_secret
- O servidor OAuth retorna um JWT (JSON Web Token) assinado com sua chave privada
- A aplicação apresenta esse JWT ao Kafka como prova de identidade
- Identidades centralizadas: um único servidor de identidade para todos os serviços, e rotacionar credenciais de um serviço não exige reconfigurar o broker
- Tokens com expiração curta: se um token for capturado, ele expira automaticamente
- Sem reinicialização do broker: novas credenciais de clientes não exigem reconfiguração no broker
- Integração com IAM corporativo: Keycloak, Auth0, Okta. O Kafka vira mais um cliente do seu provedor de identidade existente
Como funciona o SASL/OAUTHBEARER no Kafka
- Assinatura: usando a chave pública JWKS em cache, o que garante que o token não foi adulterado
- aud: deve conter kafka-broker, o que impede que tokens emitidos para outros serviços funcionem aqui
- iss: verifica que o token veio do issuer esperado e rejeita tokens de outros realms ou servidores
- exp: rejeita tokens expirados
Configurando o ambiente
1. Subindo o Keycloak
docker compose up keycloak -d2. Configurando o Keycloak
2.1 Criando o realm kafka-example
- Realm name: kafka-example
- Clique em Create
2.2 Criando o client kafka-broker
- Client type: OpenID Connect
- Client ID: kafka-broker
- Clique em Next
- Client authentication: ON
- Desmarque Standard flow
- Marque Service accounts roles
- Clique em Next → Save
2.3 Adicionando o audience mapper ao kafka-broker
- Name: kafka-broker-audience
- Included Custom Audience: kafka-broker
- Clique em Save
2.4 Criando o client kafka-client
- Client ID: kafka-client
2.5 Adicionando o audience mapper ao kafka-client
- Name: kafka-broker-audience
- Included Custom Audience: kafka-broker
- Clique em Save
2.6 Verificando a configuração
terminal
curl -s -X POST "http://localhost:8082/realms/kafka-example/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=kafka-client&client_secret=<secret>"access_token
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2UzVFTVF0TE56ZlFCMEZYYWk4ckdpbHZWaXZQb2ltbDRQMkd5NWk4cWtrIn0.eyJleHAiOjE3NzIzMTU4MTQsImlhdCI6MTc3MjMxNTUxNCwianRpIjoidHJydGNjOmNlYzBhNWMwLTFkMWYtNmZmYS1kYTI5LTdmZDk4ZDE1YzQ3OCIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4Mi9yZWFsbXMva2Fma2EtZXhhbXBsZSIsImF1ZCI6WyJrYWZrYS1icm9rZXIiLCJhY2NvdW50Il0sInN1YiI6IjFjN2FhZTMwLTAwMTMtNDY3Zi05ZmJkLWZhNTAwZjc5NGE2ZSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImthZmthLWNsaWVudCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMta2Fma2EtZXhhbXBsZSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJjbGllbnRIb3N0IjoiMTcyLjIzLjAuMSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LWthZmthLWNsaWVudCIsImNsaWVudEFkZHJlc3MiOiIxNzIuMjMuMC4xIiwiY2xpZW50X2lkIjoia2Fma2EtY2xpZW50In0.Ttd5Vm28ETSCo0VIob4jHCAcvCW77LgtX0klg6DcODyX3XaCylxdhexZBpAc0QMk9zbBs0zScEQVb9zA0PzeY10RHtXFo-hlghBnARlpajC733IJ69vGL6M5fjQMV8tKXJ-DG-zQZkyLIh0Xo5decSwRzbJYQExQf_LD5QcqkKbhZxFAIk-TRnI9IYXilTEnavnK16REoDnMjrJeVKvE3m0p1Huh5rwEB0ZHdhHqNmZCA1JBAzPrzJvPTvUvmhNHwER2joJ1A-V5xvHYrj63mEEou0xYoPPJyzjdIDpl7k4XWKxpqyRsaxxqD7i2E2bIm3mfGGskw8S5h3uHoR3OgQJWT decodificado
// Header
{
"alg": "RS256",
"typ": "JWT",
"kid": "6S5EMQtLNzfQB0FXai8rGilvVivPoiml4P2Gy5i8qkk"
}
// Payload
{
"iss": "http://localhost:8082/realms/kafka-example", // issuer — quem emitiu o token
"aud": ["kafka-broker", "account"], // audience — para quem o token é válido
"exp": 1772315814, // expiration — quando o token expira
"iat": 1772315514, // issued at — quando foi emitido (exp - iat = 300s)
"sub": "1c7aae30-0013-467f-9fbd-fa500f794a6e", // subject — identidade única do client
"azp": "kafka-client", // authorized party — client que requisitou o token
"preferred_username": "service-account-kafka-client"
}3. Configurando e subindo o broker
docker-compose.yaml
broker:
image: confluentinc/cp-kafka:latest
environment:
# ... demais configurações ...
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: >-
CONTROLLER:PLAINTEXT,EXTERNAL:SASL_PLAINTEXT,PLAINTEXT_EXTERNAL:PLAINTEXT
KAFKA_LISTENER_NAME_EXTERNAL_SASL_ENABLED_MECHANISMS: "OAUTHBEARER"
KAFKA_LISTENER_NAME_EXTERNAL_SASL_OAUTHBEARER_EXPECTED_AUDIENCE: "kafka-broker"
KAFKA_LISTENER_NAME_EXTERNAL_SASL_OAUTHBEARER_EXPECTED_ISSUER: "http://localhost:8082/realms/kafka-example"
KAFKA_LISTENER_NAME_EXTERNAL_SASL_OAUTHBEARER_JWKS_ENDPOINT_URL: "http://keycloak:8080/realms/kafka-example/protocol/openid-connect/certs"
KAFKA_LISTENER_NAME_EXTERNAL_SASL_OAUTHBEARER_TOKEN_ENDPOINT_URL: "http://keycloak:8080/realms/kafka-example/protocol/openid-connect/token"
KAFKA_LISTENER_NAME_EXTERNAL_OAUTHBEARER_SASL_LOGIN_CALLBACK_HANDLER_CLASS: "org.apache.kafka.common.security.oauthbearer.secured.OAuthBearerLoginCallbackHandler"
KAFKA_LISTENER_NAME_EXTERNAL_OAUTHBEARER_SASL_SERVER_CALLBACK_HANDLER_CLASS: "org.apache.kafka.common.security.oauthbearer.secured.OAuthBearerValidatorCallbackHandler"
KAFKA_BROKER_CLIENT_SECRET: "<secret do kafka-broker>"Define OAUTHBEARER como o mecanismo SASL habilitado no listener EXTERNAL. KAFKA_LISTENER_NAME_EXTERNAL_SASL_OAUTHBEARER_EXPECTED_AUDIENCE
O broker rejeita tokens cujo campo aud não contenha kafka-broker. Isso garante que tokens emitidos para outros serviços não funcionem no Kafka. KAFKA_LISTENER_NAME_EXTERNAL_SASL_OAUTHBEARER_EXPECTED_ISSUER
O issuer (iss) esperado nos tokens. Tokens emitidos por outros realms ou servidores OAuth são rejeitados. KAFKA_LISTENER_NAME_EXTERNAL_SASL_OAUTHBEARER_JWKS_ENDPOINT_URL
Endpoint de onde o broker busca as chaves públicas para validar assinaturas JWT. Note que a URL usa keycloak:8080 (rede interna do Docker), pois o broker acessa o Keycloak de dentro do container. KAFKA_LISTENER_NAME_EXTERNAL_SASL_OAUTHBEARER_TOKEN_ENDPOINT_URL
Endpoint de onde o broker busca seu próprio token para autenticação interna com o Keycloak. KAFKA_LISTENER_NAME_EXTERNAL_OAUTHBEARER_SASL_LOGIN_CALLBACK_HANDLER_CLASS
Handler responsável por buscar o token JWT no servidor OAuth durante o login do cliente. KAFKA_LISTENER_NAME_EXTERNAL_OAUTHBEARER_SASL_SERVER_CALLBACK_HANDLER_CLASS
Handler do broker responsável por validar o JWT recebido durante o handshake SASL. Com as variáveis preenchidas, suba o broker:
docker compose up broker -d4. Configurando os clientes Kotlin
Kotlin
// MessageProducer.kt / MessageConsumer.kt
properties["bootstrap.servers"] = "localhost:9092"
properties["security.protocol"] = "SASL_PLAINTEXT"
properties["sasl.mechanism"] = "OAUTHBEARER"
properties["sasl.jaas.config"] =
"org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required" +
" clientId="kafka-client"" +
" clientSecret="$clientSecret";"
properties["sasl.login.callback.handler.class"] =
"org.apache.kafka.common.security.oauthbearer.secured.OAuthBearerLoginCallbackHandler"
properties["sasl.oauthbearer.token.endpoint.url"] =
"http://localhost:8082/realms/kafka-example/protocol/openid-connect/token"Instrui o cliente a usar SASL sem TLS. Em produção, o ideal é combinar os dois e usar SASL_SSL. sasl.mechanism
Define OAUTHBEARER como o mecanismo SASL a ser usado na conexão. sasl.jaas.config
Configuração JAAS com clientId e clientSecret do client kafka-client no Keycloak. Essas credenciais são usadas para buscar o JWT antes de cada conexão. sasl.login.callback.handler.class
Implementação do handler que executa o Client Credentials Flow: chama o endpoint /token do Keycloak com as credenciais do sasl.jaas.config e obtém o JWT. sasl.oauthbearer.token.endpoint.url
URL do endpoint de token do Keycloak. Os clientes usam localhost:8082 para acessar de fora do Docker, diferente do broker que usa a rede interna.
5. Compilando e executando
terminal
# Compila os dois fat JARs
./gradlew clean build
# Com autenticação OAuth — conecta ao listener SASL_PLAINTEXT na porta 9092
java -jar build/libs/producer-1.0-SNAPSHOT.jar --client-secret=<secret do kafka-client>
java -jar build/libs/consumer-1.0-SNAPSHOT.jar --client-secret=<secret do kafka-client>
# Sem autenticação — conecta ao listener PLAINTEXT na porta 9094
java -jar build/libs/producer-1.0-SNAPSHOT.jar --no-auth
java -jar build/libs/consumer-1.0-SNAPSHOT.jar --no-auth| Listener | Porta | Protocolo | Uso |
|---|---|---|---|
| EXTERNAL | 9092 | SASL_PLAINTEXT | Conexão com autenticação OAuth |
| PLAINTEXT_EXTERNAL | 9094 | PLAINTEXT | Conexão sem autenticação |












