Condividi tramite


Eseguire la migrazione di un'applicazione per usare connessioni senza password con Hub eventi di Azure per Kafka

Questo articolo illustra come eseguire la migrazione da metodi di autenticazione tradizionali a connessioni senza password più sicure con Hub eventi di Azure per Kafka.

Le richieste dell'applicazione a Hub eventi di Azure per Kafka devono essere autenticate. Hub eventi di Azure per Kafka offre diversi modi per connettersi in modo sicuro alle app. Uno dei modi consiste nell'usare un stringa di connessione. Tuttavia, è consigliabile assegnare priorità alle connessioni senza password nelle applicazioni, quando possibile.

Le connessioni senza password sono supportate a partire da Spring Cloud Azure 4.3.0. Questo articolo è una guida alla migrazione per la rimozione delle credenziali dalle applicazioni Kafka di Spring Cloud Stream.

Confrontare le opzioni di autenticazione

Quando l'applicazione esegue l'autenticazione con Hub eventi di Azure per Kafka, fornisce un'entità autorizzata per connettere lo spazio dei nomi di Hub eventi. I protocolli Apache Kafka forniscono più meccanismi sasl (Simple Authentication and Security Layer) per l'autenticazione. In base ai meccanismi SASL, sono disponibili due opzioni di autenticazione che è possibile usare per autorizzare l'accesso alle risorse protette: autenticazione di Microsoft Entra e autenticazione della firma di accesso condiviso .

Autenticazione Microsoft Entra

L'autenticazione di Microsoft Entra è un meccanismo per la connessione a Hub eventi di Azure per Kafka usando le identità definite in Microsoft Entra ID. Con l'autenticazione Microsoft Entra, è possibile gestire le identità dell'entità servizio e altre servizi Microsoft in una posizione centrale, semplificando la gestione delle autorizzazioni.

L'uso di Microsoft Entra ID per l'autenticazione offre i vantaggi seguenti:

  • Autenticazione degli utenti nei servizi di Azure in modo uniforme.
  • Gestione dei criteri password e della rotazione delle password in un'unica posizione.
  • Più forme di autenticazione supportate da Microsoft Entra ID, che possono eliminare la necessità di archiviare le password.
  • I clienti possono gestire le autorizzazioni di Hub eventi usando gruppi esterni (Microsoft Entra ID).
  • Supporto per l'autenticazione basata su token per le applicazioni che si connettono a Hub eventi di Azure per Kafka.

Autenticazione con firma di accesso condiviso

Hub eventi fornisce anche firme di accesso condiviso (SAS) per l'accesso delegato a Hub eventi per le risorse Kafka.

Anche se è possibile connettersi a Hub eventi di Azure per Kafka con firma di accesso condiviso, è consigliabile usarlo con cautela. È necessario essere diligenti per non esporre mai i stringa di connessione in una posizione non sicura. Chiunque possa accedere ai stringa di connessione è in grado di eseguire l'autenticazione. Ad esempio, esiste il rischio che un utente malintenzionato possa accedere all'applicazione se un stringa di connessione viene accidentalmente archiviato nel controllo del codice sorgente, inviato tramite un messaggio di posta elettronica non sicuro, incollato nella chat sbagliata o visualizzato da un utente che non deve avere l'autorizzazione. L'autorizzazione dell'accesso tramite il meccanismo basato su token OAuth 2.0 offre invece sicurezza e facilità di utilizzo superiori rispetto alla firma di accesso condiviso. È consigliabile aggiornare l'applicazione per usare connessioni senza password.

Introduzione alle connessioni senza password

Con una connessione senza password, è possibile connettersi ai servizi di Azure senza archiviare credenziali nel codice dell'applicazione, nei relativi file di configurazione o nelle variabili di ambiente.

Molti servizi di Azure supportano connessioni senza password, ad esempio tramite Identità gestita di Azure. Queste tecniche forniscono funzionalità di sicurezza affidabili che è possibile implementare usando DefaultAzureCredential dalle librerie client di Identità di Azure. In questa esercitazione si apprenderà come aggiornare un'applicazione esistente da usare DefaultAzureCredential invece di alternative, ad esempio stringa di connessione.

DefaultAzureCredential supporta più metodi di autenticazione e determina automaticamente quali devono essere usati in fase di esecuzione. Questo approccio consente all'app di usare metodi di autenticazione diversi in ambienti diversi (sviluppo locale e produzione) senza implementare codice specifico dell'ambiente.

L'ordine e le posizioni in cui DefaultAzureCredential cercare le credenziali sono disponibili nella panoramica della libreria di identità di Azure. Ad esempio, quando si lavora in locale, DefaultAzureCredential in genere si esegue l'autenticazione usando l'account usato dallo sviluppatore per accedere a Visual Studio. Quando l'app viene distribuita in Azure, DefaultAzureCredential passerà automaticamente all'uso di un'identità gestita. Per questa transizione non sono necessarie modifiche al codice.

Per garantire che le connessioni siano senza password, è necessario prendere in considerazione sia lo sviluppo locale che l'ambiente di produzione. Se una stringa di connessione è necessaria in entrambe le posizioni, l'applicazione non è senza password.

Nell'ambiente di sviluppo locale è possibile eseguire l'autenticazione con l'interfaccia della riga di comando di Azure, Azure PowerShell, Visual Studio o plug-in di Azure per Visual Studio Code o IntelliJ. In questo caso, è possibile usare tali credenziali nell'applicazione anziché configurare le proprietà.

Quando si distribuiscono applicazioni in un ambiente di hosting di Azure, ad esempio una macchina virtuale, è possibile assegnare un'identità gestita in tale ambiente. Non sarà quindi necessario fornire le credenziali per connettersi ai servizi di Azure.

Nota

Un'identità gestita fornisce un'identità di sicurezza per rappresentare un'app o un servizio. L'identità viene gestita dalla piattaforma Azure e non è necessario eseguire il provisioning o ruotare alcun segreto. Per altre informazioni sulle identità gestite, vedere la documentazione di panoramica .

Eseguire la migrazione di un'applicazione esistente per usare connessioni senza password

La procedura seguente illustra come eseguire la migrazione di un'applicazione esistente per usare connessioni senza password anziché una soluzione di firma di accesso condiviso.

0) Preparare l'ambiente di lavoro per l'autenticazione di sviluppo locale

Usare prima di tutto il comando seguente per configurare alcune variabili di ambiente.

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

Sostituire i segnaposto con i valori seguenti, che vengono usati nell'intero articolo:

  • <YOUR_RESOURCE_GROUP>: nome del gruppo di risorse che verrà usato.
  • <YOUR_EVENTHUBS_NAMESPACE_NAME>: nome dello spazio dei nomi Hub eventi di Azure che verrà usato.
  • <YOUR_EVENTHUB_NAME>: nome dell'hub eventi che verrà usato.

1) Concedere l'autorizzazione per Hub eventi di Azure

Se si vuole eseguire questo esempio in locale con l'autenticazione Microsoft Entra, assicurarsi che l'account utente sia stato autenticato tramite Azure Toolkit for IntelliJ, il plug-in dell'account azure di Visual Studio Code o l'interfaccia della riga di comando di Azure. Assicurarsi inoltre che all'account siano state concesse autorizzazioni sufficienti.

  1. Nella portale di Azure individuare lo spazio dei nomi di Hub eventi usando la barra di ricerca principale o lo spostamento a sinistra.

  2. Nella pagina di panoramica di Hub eventi selezionare Controllo di accesso (IAM) dal menu a sinistra.

  3. Nella pagina Controllo di accesso (IAM), selezionare la scheda Assegnazioni di ruolo.

  4. Selezionare Aggiungi dal menu in alto e quindi Aggiungi assegnazione di ruolo dal menu a discesa risultante.

    Screenshot della pagina portale di Azure Controllo di accesso (IAM) della risorsa Spazio dei nomi di Hub eventi con l'opzione Aggiungi assegnazione di ruolo evidenziata.

  5. Usare la casella di ricerca per filtrare i risultati in base al ruolo desiderato. Per questo esempio cercare Hub eventi di Azure Mittente dati e Hub eventi di Azure Ricevitore dati, selezionare il risultato corrispondente e quindi scegliere Avanti.

  6. In Assegna accesso a selezionare Utente, gruppo o entità servizio e quindi scegliere Seleziona membri.

  7. Nella finestra di dialogo cercare il nome utente di Microsoft Entra (in genere l'indirizzo di posta elettronica user@domain) e quindi scegliere Selezionare nella parte inferiore della finestra di dialogo.

  8. Selezionare Rivedi e assegna per passare alla pagina finale e quindi Rivedi e assegna di nuovo per completare il processo.

Per altre informazioni sulla concessione dei ruoli di accesso, vedere Autorizzare l'accesso alle risorse di Hub eventi usando Microsoft Entra ID.

2) Accedere ed eseguire la migrazione del codice dell'app per usare connessioni senza password

Per lo sviluppo locale, assicurarsi di essere autenticati con lo stesso account Microsoft Entra a cui è stato assegnato il ruolo in Hub eventi. È possibile eseguire l'autenticazione tramite l'interfaccia della riga di comando di Azure, Visual Studio, Azure PowerShell o altri strumenti come IntelliJ.

Accedere ad Azure tramite l'interfaccia della riga di comando di Azure usando il comando seguente:

az login

Usare quindi la procedura seguente per aggiornare l'applicazione Spring Kafka per usare connessioni senza password. Anche se concettualmente simile, ogni framework usa dettagli di implementazione diversi.

  1. All'interno del progetto aprire il file pom.xml e aggiungere il riferimento seguente:

    <dependency>
       <groupId>com.azure</groupId>
       <artifactId>azure-identity</artifactId>
       <version>1.6.0</version>
    </dependency>
    
  2. Dopo la migrazione, implementare AuthenticateCallbackHandler e OAuthBearerToken nel progetto per l'autenticazione OAuth2, come illustrato nell'esempio seguente.

    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. Quando si crea il producer o il consumer Kafka, aggiungere la configurazione necessaria per supportare il meccanismo SASL/OAUTHBEARER . Gli esempi seguenti illustrano l'aspetto del codice prima e dopo la migrazione. In entrambi gli esempi sostituire il <eventhubs-namespace> segnaposto con il nome dello spazio dei nomi di Hub eventi.

    Prima della migrazione, il codice dovrebbe essere simile all'esempio seguente:

    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);
    

    Dopo la migrazione, il codice dovrebbe essere simile all'esempio seguente. In questo esempio sostituire il <path-to-your-KafkaOAuth2AuthenticateCallbackHandler> segnaposto con il nome completo della classe per l'implementazione KafkaOAuth2AuthenticateCallbackHandlerdi .

    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);
    

Eseguire l'app in locale

Dopo aver apportato queste modifiche al codice, eseguire l'applicazione in locale. La nuova configurazione deve raccogliere le credenziali locali, presupponendo che si sia connessi a un IDE compatibile o a uno strumento da riga di comando, ad esempio l'interfaccia della riga di comando di Azure, Visual Studio o IntelliJ. I ruoli assegnati all'utente di sviluppo locale in Azure consentiranno all'app di connettersi al servizio di Azure in locale.

3) Configurare l'ambiente di hosting di Azure

Dopo che l'applicazione è configurata per l'uso di connessioni senza password e viene eseguita in locale, lo stesso codice può eseguire l'autenticazione ai servizi di Azure dopo la distribuzione in Azure. Ad esempio, un'applicazione distribuita in un'istanza di Azure Spring Apps con un'identità gestita assegnata può connettersi a Hub eventi di Azure per Kafka.

In questa sezione verranno eseguiti due passaggi per consentire l'esecuzione dell'applicazione in un ambiente di hosting di Azure in modo senza password:

  • Assegnare l'identità gestita per l'ambiente di hosting di Azure.
  • Assegnare ruoli all'identità gestita.

Nota

Azure offre anche Service Connector, che consente di connettere il servizio di hosting con Hub eventi. Con Service Connector per configurare l'ambiente di hosting, è possibile omettere il passaggio di assegnazione dei ruoli all'identità gestita perché Service Connector lo eseguirà automaticamente. La sezione seguente descrive come configurare l'ambiente di hosting di Azure in due modi: uno tramite Service Connector e l'altro configurando direttamente ogni ambiente di hosting.

Importante

I comandi di Service Connector richiedono l'interfaccia della riga di comando di Azure 2.41.0 o versione successiva.

Assegnare l'identità gestita per l'ambiente di hosting di Azure

I passaggi seguenti illustrano come assegnare un'identità gestita assegnata dal sistema per vari servizi di hosting Web. L'identità gestita può connettersi in modo sicuro ad altri servizi di Azure usando le configurazioni dell'app configurate in precedenza.

  1. Nella pagina di panoramica principale dell'istanza del servizio app Azure selezionare Identità nel riquadro di spostamento.

  2. Nella scheda Assegnata dal sistema assicurarsi di impostare il campo Stato su attivato. Un'identità assegnata dal sistema viene gestita internamente da Azure e gestisce automaticamente le attività amministrative. I dettagli e gli ID dell'identità non vengono mai esposti nel codice.

È anche possibile assegnare un'identità gestita in un ambiente di hosting di Azure usando l'interfaccia della riga di comando di Azure.

È possibile assegnare un'identità gestita a un'istanza del servizio app Azure con il comando az webapp identity assign, come illustrato nell'esempio seguente.

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

Assegnare ruoli all'identità gestita

Concedere quindi le autorizzazioni all'identità gestita creata per accedere allo spazio dei nomi di Hub eventi. È possibile concedere le autorizzazioni assegnando un ruolo all'identità gestita, proprio come è stato fatto con l'utente di sviluppo locale.

Se i servizi sono stati connessi tramite Service Connector, non è necessario completare questo passaggio. Le configurazioni necessarie seguenti sono state gestite automaticamente:

  • Se è stata selezionata un'identità gestita al momento della creazione della connessione, è stata creata un'identità gestita assegnata dal sistema per l'app e sono stati assegnati i ruoli Hub eventi di Azure Mittente dati e Hub eventi di Azure Ricevitore dati in Hub eventi.

  • Se si sceglie di usare un stringa di connessione, il stringa di connessione è stato aggiunto come variabile di ambiente dell'app.

Testare l'app

Dopo aver apportato queste modifiche al codice, passare all'applicazione ospitata nel browser. L'app dovrebbe essere in grado di connettersi correttamente al Hub eventi di Azure per Kafka. Tenere presente che la propagazione delle assegnazioni di ruolo nell'ambiente di Azure potrebbe richiedere alcuni minuti. L'applicazione è ora configurata per l'esecuzione sia in locale che in un ambiente di produzione senza che gli sviluppatori debbano gestire i segreti nell'applicazione stessa.

Passaggi successivi

In questa esercitazione si è appreso come eseguire la migrazione di un'applicazione a connessioni senza password.

È possibile leggere le risorse seguenti per esplorare i concetti illustrati in questo articolo in modo più approfondito: