Share via


Migrar um aplicativo para usar conexões sem senha com os Hubs de Eventos do Azure para Kafka

Este artigo explica como migrar de métodos de autenticação tradicionais para conexões mais seguras e sem senha com os Hubs de Eventos do Azure para Kafka.

As solicitações de aplicativo para Hubs de Eventos do Azure para Kafka devem ser autenticadas. Os Hubs de Eventos do Azure para Kafka fornecem diferentes maneiras para os aplicativos se conectarem com segurança. Uma das maneiras é usar uma cadeia de conexão. No entanto, você deve priorizar conexões sem senha em seus aplicativos quando possível.

As conexões sem senha são suportadas desde o Spring Cloud Azure 4.3.0. Este artigo é um guia de migração para remover credenciais de aplicativos Spring Cloud Stream Kafka.

Comparar opções de autenticação

Quando o aplicativo é autenticado com Hubs de Eventos do Azure para Kafka, ele fornece uma entidade autorizada para conectar o namespace Hubs de Eventos. Os protocolos Apache Kafka fornecem vários mecanismos SASL (Simple Authentication and Security Layer) para autenticação. De acordo com os mecanismos SASL, há duas opções de autenticação que você pode usar para autorizar o acesso aos seus recursos seguros: autenticação Microsoft Entra e autenticação SAS (Assinatura de Acesso Compartilhado).

Autenticação do Microsoft Entra

A autenticação do Microsoft Entra é um mecanismo para se conectar aos Hubs de Eventos do Azure para Kafka usando identidades definidas na ID do Microsoft Entra. Com a autenticação do Microsoft Entra, você pode gerenciar identidades de entidade de serviço e outros serviços da Microsoft em um local central, o que simplifica o gerenciamento de permissões.

Usar o Microsoft Entra ID para autenticação oferece os seguintes benefícios:

  • Autenticação de usuários nos serviços do Azure de maneira uniforme.
  • Gerenciamento de políticas de senhas e rotação de senhas em um único lugar.
  • Várias formas de autenticação suportadas pelo Microsoft Entra ID, que podem eliminar a necessidade de armazenar senhas.
  • Os clientes podem gerenciar permissões de Hubs de Eventos usando grupos externos (Microsoft Entra ID).
  • Suporte para autenticação baseada em token para aplicativos que se conectam aos Hubs de Eventos do Azure para Kafka.

Autenticação SAS

Os Hubs de Eventos também fornecem Assinaturas de Acesso Compartilhado (SAS) para acesso delegado aos Hubs de Eventos para recursos do Kafka.

Embora seja possível conectar-se aos Hubs de Eventos do Azure para Kafka com SAS, ele deve ser usado com cuidado. Você deve ser diligente para nunca expor as cadeias de conexão em um local não seguro. Qualquer pessoa que obtenha acesso às cadeias de conexão é capaz de autenticar. Por exemplo, há um risco de que um usuário mal-intencionado possa acessar o aplicativo se uma cadeia de conexão for acidentalmente verificada no controle do código-fonte, enviada por meio de um e-mail não seguro, colada no bate-papo errado ou visualizada por alguém que não deveria ter permissão. Em vez disso, autorizar o acesso usando o mecanismo baseado em token OAuth 2.0 oferece segurança superior e facilidade de uso em relação ao SAS. Considere atualizar seu aplicativo para usar conexões sem senha.

Introdução a ligações sem palavra-passe

Com uma conexão sem senha, você pode se conectar aos serviços do Azure sem armazenar credenciais no código do aplicativo, em seus arquivos de configuração ou em variáveis de ambiente.

Muitos serviços do Azure suportam ligações sem palavra-passe, por exemplo, através da Identidade Gerida do Azure. Essas técnicas fornecem recursos de segurança robustos que você pode implementar usando DefaultAzureCredential das bibliotecas de cliente do Azure Identity. Neste tutorial, você aprenderá como atualizar um aplicativo existente para usar DefaultAzureCredential em vez de alternativas, como cadeias de conexão.

DefaultAzureCredential suporta vários métodos de autenticação e determina automaticamente quais devem ser usados em tempo de execução. Essa abordagem permite que seu aplicativo use métodos de autenticação diferentes em ambientes diferentes (desenvolvimento local versus produção) sem implementar código específico do ambiente.

A ordem e os locais em que DefaultAzureCredential as pesquisas de credenciais podem ser encontradas na visão geral da Biblioteca de Identidades do Azure. Por exemplo, ao trabalhar localmente, DefaultAzureCredential geralmente autenticará usando a conta que o desenvolvedor usou para entrar no Visual Studio. Quando o aplicativo for implantado no Azure, DefaultAzureCredential alternará automaticamente para usar uma identidade gerenciada. Não são necessárias alterações de código para esta transição.

Para garantir que as conexões não tenham senha, você deve levar em consideração o desenvolvimento local e o ambiente de produção. Se uma cadeia de conexão for necessária em qualquer lugar, o aplicativo não será sem senha.

Em seu ambiente de desenvolvimento local, você pode autenticar com a CLI do Azure, Azure PowerShell, Visual Studio ou plug-ins do Azure para Visual Studio Code ou IntelliJ. Nesse caso, você pode usar essa credencial em seu aplicativo em vez de configurar propriedades.

Ao implantar aplicativos em um ambiente de hospedagem do Azure, como uma máquina virtual, você pode atribuir identidade gerenciada nesse ambiente. Em seguida, você não precisará fornecer credenciais para se conectar aos serviços do Azure.

Nota

Uma identidade gerenciada fornece uma identidade de segurança para representar um aplicativo ou serviço. A identidade é gerida pela plataforma do Azure e não precisa de aprovisionar nem rodar segredos. Você pode ler mais sobre identidades gerenciadas na documentação de visão geral .

Migrar um aplicativo existente para usar conexões sem senha

As etapas a seguir explicam como migrar um aplicativo existente para usar conexões sem senha em vez de uma solução SAS.

0) Preparar o ambiente de trabalho para a autenticação do desenvolvimento local

Primeiro, use o seguinte comando para configurar algumas variáveis de ambiente.

export AZ_RESOURCE_GROUP=<YOUR_RESOURCE_GROUP>
export AZ_EVENTHUBS_NAMESPACE_NAME=<YOUR_EVENTHUBS_NAMESPACE_NAME>
export AZ_EVENTHUB_NAME=<YOUR_EVENTHUB_NAME>

Substitua os marcadores de posição pelos seguintes valores, que são utilizados ao longo deste artigo:

  • <YOUR_RESOURCE_GROUP>: O nome do grupo de recursos que você usará.
  • <YOUR_EVENTHUBS_NAMESPACE_NAME>: O nome do namespace dos Hubs de Eventos do Azure que você usará.
  • <YOUR_EVENTHUB_NAME>: O nome do hub de eventos que você usará.

1) Conceder permissão para Hubs de Eventos do Azure

Se quiser executar este exemplo localmente com a autenticação do Microsoft Entra, certifique-se de que a sua conta de utilizador foi autenticada através do Kit de Ferramentas do Azure para IntelliJ, do plug-in da Conta do Azure do Visual Studio Code ou da CLI do Azure. Além disso, certifique-se de que a conta recebeu permissões suficientes.

  1. No portal do Azure, localize seu namespace de Hubs de Eventos usando a barra de pesquisa principal ou a navegação à esquerda.

  2. Na página de visão geral dos Hubs de Eventos, selecione Controle de acesso (IAM) no menu à esquerda.

  3. Na página Controle de acesso (IAM), selecione a guia Atribuições de função.

  4. Selecione Adicionar no menu superior e, em seguida, Adicionar atribuição de função no menu suspenso resultante.

    Screenshot of Azure portal Access Control (IAM) page of Event Hubs Namespace resource with Add role assignment highlighted.

  5. Use a caixa de pesquisa para filtrar os resultados para a função desejada. Neste exemplo, procure Remetente de Dados dos Hubs de Eventos do Azure e Recetor de Dados dos Hubs de Eventos do Azure, selecione o resultado correspondente e escolha Avançar.

  6. Em Atribuir acesso a, selecione Utilizador, grupo ou entidade de serviço e, em seguida, selecione Selecionar membros.

  7. Na caixa de diálogo, procure seu nome de usuário do Microsoft Entra (geralmente seu endereço de e-mail user@domain ) e escolha Selecionar na parte inferior da caixa de diálogo.

  8. Selecione Rever + atribuir para ir para a página final e, em seguida, Rever + atribuir novamente para concluir o processo.

Para obter mais informações sobre como conceder funções de acesso, consulte Autorizar acesso a recursos de Hubs de Eventos usando a ID do Microsoft Entra.

2) Entre e migre o código do aplicativo para usar conexões sem senha

Para desenvolvimento local, certifique-se de que está autenticado com a mesma conta Microsoft Entra à qual atribuiu a função nos Hubs de Eventos. Você pode autenticar por meio da CLI do Azure, Visual Studio, Azure PowerShell ou outras ferramentas, como IntelliJ.

Entre no Azure por meio da CLI do Azure usando o seguinte comando:

az login

Em seguida, use as etapas a seguir para atualizar seu aplicativo Spring Kafka para usar conexões sem senha. Embora conceitualmente semelhante, cada estrutura usa detalhes de implementação diferentes.

  1. Dentro do seu projeto, abra o arquivo pom.xml e adicione a seguinte referência:

    <dependency>
       <groupId>com.azure</groupId>
       <artifactId>azure-identity</artifactId>
       <version>1.6.0</version>
    </dependency>
    
  2. Após a migração, implemente AuthenticateCallbackHandler e OAuthBearerToken em seu projeto para autenticação OAuth2, conforme mostrado no exemplo a seguir.

    public class KafkaOAuth2AuthenticateCallbackHandler implements AuthenticateCallbackHandler {
    
       private static final Duration ACCESS_TOKEN_REQUEST_BLOCK_TIME = Duration.ofSeconds(30);
       private static final String TOKEN_AUDIENCE_FORMAT = "%s://%s/.default";
    
       private Function<TokenCredential, Mono<OAuthBearerTokenImp>> resolveToken;
       private final TokenCredential credential = new DefaultAzureCredentialBuilder().build();
    
       @Override
       public void configure(Map<String, ?> configs, String mechanism, List<AppConfigurationEntry> jaasConfigEntries) {
          TokenRequestContext request = buildTokenRequestContext(configs);
          this.resolveToken = tokenCredential -> tokenCredential.getToken(request).map(OAuthBearerTokenImp::new);
       }
    
       private TokenRequestContext buildTokenRequestContext(Map<String, ?> configs) {
          URI uri = buildEventHubsServerUri(configs);
          String tokenAudience = buildTokenAudience(uri);
    
          TokenRequestContext request = new TokenRequestContext();
          request.addScopes(tokenAudience);
          return request;
       }
    
       @SuppressWarnings("unchecked")
       private URI buildEventHubsServerUri(Map<String, ?> configs) {
          String bootstrapServer = Arrays.asList(configs.get(BOOTSTRAP_SERVERS_CONFIG)).get(0).toString();
          bootstrapServer = bootstrapServer.replaceAll("\\[|\\]", "");
          URI uri = URI.create("https://" + bootstrapServer);
          return uri;
       }
    
       private String buildTokenAudience(URI uri) {
          return String.format(TOKEN_AUDIENCE_FORMAT, uri.getScheme(), uri.getHost());
       }
    
       @Override
       public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
          for (Callback callback : callbacks) {
             if (callback instanceof OAuthBearerTokenCallback) {
                OAuthBearerTokenCallback oauthCallback = (OAuthBearerTokenCallback) callback;
                this.resolveToken
                        .apply(credential)
                        .doOnNext(oauthCallback::token)
                        .doOnError(throwable -> oauthCallback.error("invalid_grant", throwable.getMessage(), null))
                        .block(ACCESS_TOKEN_REQUEST_BLOCK_TIME);
             } else {
                throw new UnsupportedCallbackException(callback);
             }
          }
       }
    
       @Override
       public void close() {
          // NOOP
       }
    }
    
    public class OAuthBearerTokenImp implements OAuthBearerToken {
        private final AccessToken accessToken;
        private final JWTClaimsSet claims;
    
        public OAuthBearerTokenImp(AccessToken accessToken) {
            this.accessToken = accessToken;
            try {
                claims = JWTParser.parse(accessToken.getToken()).getJWTClaimsSet();
            } catch (ParseException exception) {
                throw new SaslAuthenticationException("Unable to parse the access token", exception);
            }
        }
    
        @Override
        public String value() {
            return accessToken.getToken();
        }
    
        @Override
        public Long startTimeMs() {
            return claims.getIssueTime().getTime();
        }
    
        @Override
        public long lifetimeMs() {
            return claims.getExpirationTime().getTime();
        }
    
        @Override
        public Set<String> scope() {
            // Referring to https://docs.microsoft.com/azure/active-directory/develop/access-tokens#payload-claims, the scp
            // claim is a String, which is presented as a space separated list.
            return Optional.ofNullable(claims.getClaim("scp"))
                    .map(s -> Arrays.stream(((String) s)
                    .split(" "))
                    .collect(Collectors.toSet()))
                    .orElse(null);
        }
    
        @Override
        public String principalName() {
            return (String) claims.getClaim("upn");
        }
    
        public boolean isExpired() {
            return accessToken.isExpired();
        }
    }
    
  3. Ao criar seu produtor ou consumidor Kafka, adicione a configuração necessária para suportar o mecanismo SASL/OAUTHBEARER . Os exemplos a seguir mostram como seu código deve parecer antes e depois da migração. Em ambos os exemplos, substitua o espaço reservado <eventhubs-namespace> pelo nome do namespace Hubs de Eventos.

    Antes da migração, seu código deve ser semelhante ao exemplo a seguir:

    Properties properties = new Properties();
    properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, "<eventhubs-namespace>.servicebus.windows.net:9093");
    properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
    properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
    properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
    properties.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
    properties.put(SaslConfigs.SASL_JAAS_CONFIG,
            String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"$ConnectionString\" password=\"%s\";", connectionString));
    return new KafkaProducer<>(properties);
    

    Após a migração, seu código deve se parecer com o exemplo a seguir. Neste exemplo, substitua o espaço reservado <path-to-your-KafkaOAuth2AuthenticateCallbackHandler> pelo nome completo da classe implementada KafkaOAuth2AuthenticateCallbackHandler.

    Properties properties = new Properties();
    properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, "<eventhubs-namespace>.servicebus.windows.net:9093");
    properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
    properties.put(SaslConfigs.SASL_MECHANISM, "OAUTHBEARER");
    properties.put(SaslConfigs.SASL_JAAS_CONFIG, "org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required");
    properties.put(SaslConfigs.SASL_LOGIN_CALLBACK_HANDLER_CLASS, "<path-to-your-KafkaOAuth2AuthenticateCallbackHandler>");
    return new KafkaProducer<>(properties);
    

Executar a aplicação localmente

Depois de fazer essas alterações de código, execute seu aplicativo localmente. A nova configuração deve pegar suas credenciais locais, supondo que você esteja conectado a um IDE compatível ou ferramenta de linha de comando, como a CLI do Azure, Visual Studio ou IntelliJ. As funções que você atribuiu ao seu usuário de desenvolvimento local no Azure permitirão que seu aplicativo se conecte ao serviço do Azure localmente.

3) Configurar o ambiente de hospedagem do Azure

Depois que seu aplicativo é configurado para usar conexões sem senha e é executado localmente, o mesmo código pode se autenticar nos serviços do Azure depois de implantado no Azure. Por exemplo, um aplicativo implantado em uma instância do Azure Spring Apps que tenha uma identidade gerenciada atribuída pode se conectar aos Hubs de Eventos do Azure para Kafka.

Nesta seção, você executará duas etapas para permitir que seu aplicativo seja executado em um ambiente de hospedagem do Azure sem senha:

  • Atribua a identidade gerenciada para seu ambiente de hospedagem do Azure.
  • Atribua funções à identidade gerenciada.

Nota

O Azure também fornece o Service Connector, que pode ajudá-lo a conectar seu serviço de hospedagem com Hubs de Eventos. Com o Service Connector para configurar seu ambiente de hospedagem, você pode omitir a etapa de atribuir funções à sua identidade gerenciada porque o Service Connector fará isso por você. A seção a seguir descreve como configurar seu ambiente de hospedagem do Azure de duas maneiras: uma por meio do Service Connector e outra configurando cada ambiente de hospedagem diretamente.

Importante

Os comandos do Service Connector exigem a CLI do Azure 2.41.0 ou superior.

Atribuir a identidade gerenciada para seu ambiente de hospedagem do Azure

As etapas a seguir mostram como atribuir uma identidade gerenciada atribuída ao sistema para vários serviços de hospedagem na Web. A identidade gerenciada pode se conectar com segurança a outros Serviços do Azure usando as configurações de aplicativo que você configurou anteriormente.

  1. Na página de visão geral principal da sua instância do Serviço de Aplicativo do Azure, selecione Identidade no painel de navegação.

  2. Na guia Sistema atribuído, certifique-se de definir o campo Status como ativado. Uma identidade atribuída ao sistema é gerenciada pelo Azure internamente e lida com tarefas administrativas para você. Os detalhes e IDs da identidade nunca são expostos no seu código.

    Screenshot of Azure portal Identity page of App Service resource with System assigned tab showing and Status field highlighted.

Você também pode atribuir identidade gerenciada em um ambiente de hospedagem do Azure usando a CLI do Azure.

Você pode atribuir uma identidade gerenciada a uma instância do Serviço de Aplicativo do Azure com o comando az webapp identity assign , conforme mostrado no exemplo a seguir.

export AZURE_MANAGED_IDENTITY_ID=$(az webapp identity assign \
    --resource-group $AZ_RESOURCE_GROUP \
    --name <app-service-name> \
    --query principalId \
    --output tsv)

Atribuir funções à identidade gerenciada

Em seguida, conceda permissões à identidade gerenciada que você criou para acessar seu namespace de Hubs de Eventos. Você pode conceder permissões atribuindo uma função à identidade gerenciada, assim como fez com seu usuário de desenvolvimento local.

Se você conectou seus serviços usando o Service Connector, não precisará concluir esta etapa. As seguintes configurações necessárias foram tratadas para você:

  • Se você selecionou uma identidade gerenciada quando criou a conexão, uma identidade gerenciada atribuída ao sistema foi criada para seu aplicativo e atribuída as funções Remetente de Dados dos Hubs de Eventos do Azure e Recetor de Dados dos Hubs de Eventos do Azure nos Hubs de Eventos.

  • Se você optar por usar uma cadeia de conexão, a cadeia de conexão foi adicionada como uma variável de ambiente do aplicativo.

Testar a aplicação

Depois de fazer essas alterações de código, navegue até o aplicativo hospedado no navegador. Seu aplicativo deve ser capaz de se conectar aos Hubs de Eventos do Azure para Kafka com êxito. Lembre-se de que pode levar vários minutos para que as atribuições de função se propaguem pelo seu ambiente do Azure. Seu aplicativo agora está configurado para ser executado localmente e em um ambiente de produção sem que os desenvolvedores tenham que gerenciar segredos no próprio aplicativo.

Próximos passos

Neste tutorial, você aprendeu como migrar um aplicativo para conexões sem senha.

Você pode ler os seguintes recursos para explorar os conceitos discutidos neste artigo com mais profundidade: