Share via


將應用程式移轉至使用適用於 Kafka Azure 事件中樞 的無密碼連線

本文說明如何從傳統驗證方法移轉至更安全、無密碼的連線,以及 Kafka Azure 事件中樞。

必須驗證對 Kafka Azure 事件中樞 的應用程式要求。 適用於 Kafka 的 Azure 事件中樞 提供不同方式讓應用程式安全地連線。 其中一種方式是使用 連接字串。 不過,您應該盡可能排定應用程式中無密碼連線的優先順序。

Spring Cloud Azure 4.3.0 之後支持無密碼連線。 本文是從 Spring Cloud Stream Kafka 應用程式移除認證的移轉指南。

比較驗證選項

當應用程式向 Kafka 的 Azure 事件中樞 進行驗證時,它會提供連線事件中樞命名空間的授權實體。 Apache Kafka 通訊協定提供多個簡單驗證和安全性層 (SASL) 機制來進行驗證。 根據 SASL 機制,您可以使用兩個驗證選項來授權存取您的安全資源:Microsoft Entra 驗證和共用存取簽章 (SAS) 驗證。

Microsoft Entra 驗證

Microsoft Entra 驗證是使用 Microsoft Entra 識別碼中定義的身分識別連線到 Kafka Azure 事件中樞 的機制。 透過 Microsoft Entra 驗證,您可以在集中位置管理服務主體身分識別和其他 Microsoft 服務,以簡化許可權管理。

使用 Microsoft Entra ID 進行驗證可提供下列優點:

  • 以統一的方式驗證跨 Azure 服務的使用者。
  • 在單一位置管理密碼原則和密碼輪替。
  • Microsoft Entra ID 支援的多種驗證形式,這可以消除儲存密碼的需求。
  • 客戶可以使用外部 (Microsoft Entra ID) 群組來管理事件中樞許可權。
  • 針對連線至 Kafka Azure 事件中樞 的應用程式,支援令牌型驗證。

SAS 驗證

事件中樞也提供共用存取簽章(SAS),以委派存取 Kafka 資源的事件中樞。

雖然可以使用 SAS 連線到 Kafka Azure 事件中樞,但請務必謹慎使用。 您必須勤奮地不要在不安全的位置公開 連接字串。 任何獲得 連接字串 存取權的人都能夠進行驗證。 例如,如果不小心將 連接字串 簽入原始檔控制、透過不安全的電子郵件傳送、貼入錯誤的聊天,或由不應該擁有許可權的人員檢視,惡意使用者可能會存取應用程式。 相反地,使用 OAuth 2.0 令牌型機制來授權存取,可提供優於 SAS 的安全性和易於使用。 請考慮更新您的應用程式以使用無密碼連線。

無密碼連線簡介

透過無密碼連線,您可以連線到 Azure 服務,而不需將任何認證儲存在應用程式程式代碼、其組態檔或環境變數中。

許多 Azure 服務都支援無密碼連線,例如透過 Azure 受控識別。 這些技術提供強固的安全性功能,您可以從 Azure 身分識別用戶端連結庫使用 DefaultAzureCredential 來實作。 在本教學課程中,您將瞭解如何更新現有的應用程式以使用DefaultAzureCredential,而不是 連接字串 之類的替代方案。

DefaultAzureCredential 支援多個驗證方法,並自動判斷應該在運行時間使用哪些方法。 這種方法可讓您的應用程式在不同的環境中使用不同的驗證方法(本機開發與生產環境),而不實作環境特定的程序代碼。

您可以在 Azure 身分識別連結庫概觀中找到搜尋認證的順序和位置DefaultAzureCredential。 例如,在本機工作時, DefaultAzureCredential 通常會使用開發人員用來登入Visual Studio的帳戶進行驗證。 當應用程式部署至 Azure 時, DefaultAzureCredential 會自動切換為使用 受控識別。 此轉換不需要變更程序代碼。

若要確保連線是無密碼的,您必須同時考慮本機開發和生產環境。 如果任一位置都需要 連接字串,則應用程式不是無密碼的。

在本機開發環境中,您可以使用適用於 Visual Studio Code 或 IntelliJ 的 Azure CLI、Azure PowerShell、Visual Studio 或 Azure 外掛程式進行驗證。 在此情況下,您可以在應用程式中使用該認證,而不是設定屬性。

當您將應用程式部署至 Azure 主控環境,例如虛擬機時,您可以在該環境中指派受控識別。 然後,您不需要提供認證來連線到 Azure 服務。

注意

受控識別提供安全性身分識別來代表應用程式或服務。 身分識別由 Azure 平台負責管理,因此您不需要佈建或輪替任何密碼。 您可以在概觀檔中深入瞭解受控識別

移轉現有的應用程式以使用無密碼連線

下列步驟說明如何將現有的應用程式移轉至使用無密碼連線,而不是 SAS 解決方案。

0) 準備工作環境以進行本機開發驗證

首先,使用下列命令來設定一些環境變數。

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

將佔位元取代為下列值,此值會在整個文章中使用:

  • <YOUR_RESOURCE_GROUP>:您將使用之資源組名。
  • <YOUR_EVENTHUBS_NAMESPACE_NAME>:您將使用的 Azure 事件中樞 命名空間名稱。
  • <YOUR_EVENTHUB_NAME>:您將使用的事件中樞名稱。

1) 授與 Azure 事件中樞 的許可權

如果您想要使用 Microsoft Entra 驗證在本機執行此範例,請確定您的使用者帳戶已透過適用於 IntelliJ 的 Azure 工具組、Visual Studio Code Azure 帳戶外掛程式或 Azure CLI 進行驗證。 此外,請確定帳戶已獲得足夠的許可權。

  1. 在 Azure 入口網站 中,使用主要搜尋列或左側導覽來尋找事件中樞命名空間。

  2. 在 [事件中樞概觀] 頁面上,從左側功能表中選取 [訪問控制][IAM ]。

  3. 在 [ 訪問控制 (IAM)] 頁面上,選取 [ 角色指派] 索引標籤

  4. 從頂端功能表選取 [新增 ],然後 從產生的下拉功能表中新增角色指派

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

  5. 使用搜尋方塊將結果篩選為所需的角色。 在此範例中,搜尋 Azure 事件中樞 數據傳送者和 Azure 事件中樞 數據接收者,然後選取相符的結果,然後選擇 [下一步]。

  6. 在 [指派存取權] 底下,選取 [使用者、群組或服務主體],然後選擇 [選取成員]。

  7. 在對話框中,搜尋您的 Microsoft Entra 使用者名稱(通常是您的 user@domain 電子郵件位址),然後選擇對話方塊底部的 [ 選取 ]。

  8. 選取 [ 檢閱 + 指派 ] 以移至最終頁面,然後 再次檢閱 + 指派 以完成程式。

如需授與存取角色的詳細資訊,請參閱 使用 Microsoft Entra ID 授權事件中樞資源的存取權。

2) 登入並移轉應用程式程式代碼以使用無密碼連線

針對本機開發,請確定您已使用您在事件中樞上指派角色的相同 Microsoft Entra 帳戶進行驗證。 您可以透過 Azure CLI、Visual Studio、Azure PowerShell 或其他工具進行驗證,例如 IntelliJ。

使用下列命令透過 Azure CLI 登入 Azure:

az login

接下來,使用下列步驟來更新 Spring Kafka 應用程式以使用無密碼連線。 雖然在概念上很類似,但每個架構都會使用不同的實作詳細數據。

  1. 在您的專案中,開啟 pom.xml 檔案並新增下列參考:

    <dependency>
       <groupId>com.azure</groupId>
       <artifactId>azure-identity</artifactId>
       <version>1.6.0</version>
    </dependency>
    
  2. 移轉之後,請在您的專案中實 作 AuthenticationCallbackHandlerOAuthBearerToken 以進行 OAuth2 驗證,如下列範例所示。

    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. 當您建立 Kafka 產生者或取用者時,請新增支援 SASL/OAUTHBEARER 機制所需的設定。 下列範例顯示您的程式代碼在移轉前後的外觀。 在這兩個範例中,以事件中 <eventhubs-namespace> 樞命名空間的名稱取代 佔位符。

    在移轉之前,您的程式代碼看起來應該像下列範例:

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

    移轉之後,您的程式代碼看起來應該像下列範例。 在此範例中,請將 <path-to-your-KafkaOAuth2AuthenticateCallbackHandler> 佔位元取代為您實 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);
    

在本機執行應用程式

進行這些程式代碼變更之後,請在本機執行您的應用程式。 假設您已登入相容的 IDE 或命令行工具,例如 Azure CLI、Visual Studio 或 IntelliJ,新的組態應該會挑選您的本機認證。 您在 Azure 中指派給本機開發人員使用者的角色,可讓您的應用程式在本機連線到 Azure 服務。

3) 設定 Azure 裝載環境

將應用程式設定為使用無密碼連線並在本機執行之後,相同的程式代碼可以在部署至 Azure 之後向 Azure 服務進行驗證。 例如,部署至已指派受控識別的 Azure Spring Apps 實例的應用程式可以連線到 Kafka 的 Azure 事件中樞。

在本節中,您將執行兩個步驟,讓應用程式以無密碼的方式在 Azure 主控環境中執行:

  • 為您的 Azure 裝載環境指派受控識別。
  • 將角色指派給受控識別。

注意

Azure 也提供 Service 連線 or,可協助您將主控服務與事件中樞連線。 使用 Service 連線 or 來設定裝載環境,您可以省略將角色指派給受控識別的步驟,因為服務 連線 or 會為您執行此動作。 下一節說明如何以兩種方式設定 Azure 主控環境:一種是透過服務 連線 或另一種方式,直接設定每個裝載環境。

重要

服務 連線 或命令需要 Azure CLI 2.41.0 或更高版本。

為您的 Azure 裝載環境指派受控識別

下列步驟說明如何為各種 Web 主機服務指派系統指派的受控識別。 受控識別可以使用您先前設定的應用程式設定,安全地連線到其他 Azure 服務。

  1. 在 Azure App 服務 實例的主要概觀頁面上,從瀏覽窗格中選取 [身分識別]。

  2. 在 [ 系統指派] 索引 標籤上,請務必將 [ 狀態 ] 字段設定為 [開啟]。 系統指派的身分識別是由 Azure 內部管理,並為您處理系統管理工作。 身分識別的詳細數據和標識符永遠不會在您的程式代碼中公開。

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

您也可以使用 Azure CLI 在 Azure 主控環境中指派受控識別。

您可以使用 az webapp identity assign 命令,將受控識別指派給 Azure App 服務 實例,如下列範例所示。

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

將角色指派給受控識別

接下來,將許可權授與您建立的受控識別,以存取事件中樞命名空間。 您可以將角色指派給受控識別來授與許可權,就像您和本機開發用戶一樣。

如果您使用 Service 連線 or 連線服務,則不需要完成此步驟。 您已為您處理下列必要的群組態:

  • 如果您在建立連線時選取受控識別,則會為您的應用程式建立系統指派的受控識別,並在事件中樞上指派 Azure 事件中樞 數據傳送者和Azure 事件中樞 數據接收者角色。

  • 如果您選擇使用 連接字串,連接字串 會新增為應用程式環境變數。

測試應用程式

進行這些程式代碼變更之後,請在瀏覽器中瀏覽至您裝載的應用程式。 您的應用程式應該能夠順利連線到 Kafka 的 Azure 事件中樞。 請記住,角色指派可能需要幾分鐘的時間,才能透過 Azure 環境傳播。 您的應用程式現在已設定為在本機和生產環境中執行,而開發人員不需要在應用程式本身管理秘密。

下一步

在本教學課程中,您已瞭解如何將應用程式遷移至無密碼連線。

您可以閱讀下列資源,以更深入地探索本文中討論的概念: