Kafka용 Azure Event Hubs를 사용하여 암호 없는 연결을 사용하도록 애플리케이션 마이그레이션
이 문서에서는 기존 인증 방법에서 Kafka용 Azure Event Hubs를 사용하여 보다 안전하고 암호 없는 연결로 마이그레이션하는 방법을 설명합니다.
Kafka용 Azure Event Hubs에 대한 애플리케이션 요청은 인증되어야 합니다. Kafka용 Azure Event Hubs는 앱이 안전하게 연결하는 다양한 방법을 제공합니다. 방법 중 하나는 연결 문자열 사용하는 것입니다. 그러나 가능한 경우 애플리케이션에서 암호 없는 연결에 대한 우선 순위를 지정해야 합니다.
암호 없는 연결은 Spring Cloud Azure 4.3.0부터 지원됩니다. 이 문서는 Spring Cloud Stream Kafka 애플리케이션에서 자격 증명을 제거하기 위한 마이그레이션 가이드입니다.
인증 옵션 비교
애플리케이션이 Kafka용 Azure Event Hubs를 사용하여 인증하는 경우 Event Hubs 네임스페이스를 연결할 권한이 있는 엔터티를 제공합니다. Apache Kafka 프로토콜은 인증을 위한 여러 SASL(단순 인증 및 보안 계층) 메커니즘을 제공합니다. SASL 메커니즘에 따라 보안 리소스에 대한 액세스 권한을 부여하는 데 사용할 수 있는 두 가지 인증 옵션인 Microsoft Entra 인증 및 SAS(공유 액세스 서명) 인증이 있습니다.
Microsoft Entra 인증
Microsoft Entra 인증은 Microsoft Entra ID에 정의된 ID를 사용하여 Kafka용 Azure Event Hubs에 연결하는 메커니즘입니다. Microsoft Entra 인증을 사용하면 중앙 위치에서 서비스 주체 ID 및 기타 Microsoft 서비스 관리할 수 있으므로 권한 관리가 간소화됩니다.
인증에 Microsoft Entra ID를 사용하면 다음과 같은 이점이 제공됩니다.
- Azure 서비스 간 사용자 인증을 단일 방식으로 수행할 수 있습니다.
- 암호 정책 및 암호 순환을 단일 장소에서 관리할 수 있습니다.
- Microsoft Entra ID에서 지원하는 여러 형태의 인증으로 암호를 저장할 필요가 없습니다.
- 고객은 외부(Microsoft Entra ID) 그룹을 사용하여 Event Hubs 권한을 관리할 수 있습니다.
- Kafka용 Azure Event Hubs에 연결하는 애플리케이션에 대한 토큰 기반 인증을 지원합니다.
SAS 인증
Event Hubs는 Kafka 리소스용 Event Hubs에 대한 위임된 액세스를 위해 SAS(공유 액세스 서명)도 제공합니다.
SAS를 사용하여 Kafka용 Azure Event Hubs에 연결할 수 있지만 주의해서 사용해야 합니다. 안전하지 않은 위치에 연결 문자열 노출하지 않도록 부지런해야 합니다. 연결 문자열 대한 액세스 권한을 얻는 사람은 누구나 인증할 수 있습니다. 예를 들어 연결 문자열 실수로 소스 제어에 체크 인되거나, 안전하지 않은 전자 메일을 통해 전송되거나, 잘못된 채팅에 붙여넣거나, 권한이 없어야 하는 사람이 볼 경우 악의적인 사용자가 애플리케이션에 액세스할 수 있는 위험이 있습니다. 대신 OAuth 2.0 토큰 기반 메커니즘을 사용하여 액세스 권한을 부여하면 SAS를 통해 뛰어난 보안 및 사용 편의성을 제공합니다. 암호 없는 연결을 사용하도록 애플리케이션을 업데이트하는 것이 좋습니다.
암호 없는 연결 소개
암호 없는 연결을 사용하면 애플리케이션 코드, 구성 파일 또는 환경 변수에 자격 증명을 저장하지 않고도 Azure 서비스에 연결할 수 있습니다.
많은 Azure 서비스는 예를 들어 Azure 관리 ID를 통해 암호 없는 연결을 지원합니다. 이러한 기술은 Azure ID 클라이언트 라이브러리에서 DefaultAzureCredential을 사용하여 구현할 수 있는 강력한 보안 기능을 제공합니다. 이 자습서에서는 연결 문자열 같은 대안 대신 사용할 DefaultAzureCredential
기존 애플리케이션을 업데이트하는 방법을 알아봅니다.
DefaultAzureCredential
은 여러 인증 방법을 지원하고 런타임에 사용해야 하는 방법을 자동으로 결정합니다. 이 방법을 사용하면 앱에서 환경별 코드를 구현하지 않고도 다양한 환경(로컬 개발 및 프로덕션)에서 다양한 인증 방법을 사용할 수 있습니다.
자격 증명을 검색하는 DefaultAzureCredential
순서 및 위치는 Azure ID 라이브러리 개요에서 찾을 수 있습니다. 예를 들어 로컬 DefaultAzureCredential
로 작업하는 경우 일반적으로 개발자가 Visual Studio에 로그인하는 데 사용한 계정을 사용하여 인증합니다. 앱이 Azure에 배포되면 DefaultAzureCredential
에서 자동으로 관리 ID를 사용하도록 전환합니다. 이 전환에서는 코드를 변경할 필요가 없습니다.
연결이 암호 없는지 확인하려면 로컬 개발 및 프로덕션 환경을 모두 고려해야 합니다. 어느 곳에서든 연결 문자열 필요한 경우 애플리케이션은 암호가 없습니다.
로컬 개발 환경에서는 Azure CLI, Azure PowerShell, Visual Studio 또는 Visual Studio Code 또는 IntelliJ용 Azure 플러그 인으로 인증할 수 있습니다. 이 경우 속성을 구성하는 대신 애플리케이션에서 해당 자격 증명을 사용할 수 있습니다.
가상 머신과 같은 Azure 호스팅 환경에 애플리케이션을 배포할 때 해당 환경에서 관리 ID를 할당할 수 있습니다. 그런 다음 Azure 서비스에 연결하기 위해 자격 증명을 제공할 필요가 없습니다.
참고 항목
관리 ID는 앱 또는 서비스를 나타내는 보안 ID를 제공합니다. ID는 Azure 플랫폼에서 관리하며 비밀을 프로비전하거나 회전할 필요가 없습니다. 관리 ID는 개요 설명서에서 자세히 알아볼 수 있습니다.
암호 없는 연결을 사용하도록 기존 애플리케이션 마이그레이션
다음 단계에서는 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 Event Hubs 네임스페이스의 이름입니다.<YOUR_EVENTHUB_NAME>
: 사용할 이벤트 허브의 이름입니다.
1) Azure Event Hubs에 대한 권한 부여
Microsoft Entra 인증을 사용하여 이 샘플을 로컬로 실행하려면 사용자 계정이 IntelliJ용 Azure 도구 키트, Visual Studio Code Azure 계정 플러그 인 또는 Azure CLI를 통해 인증되었는지 확인합니다. 또한 계정에 충분한 권한이 부여되었는지 확인합니다.
Azure Portal에서 기본 검색 표시줄 또는 왼쪽 탐색을 사용하여 Event Hubs 네임스페이스를 찾습니다.
Event Hubs 개요 페이지의 왼쪽 메뉴에서 액세스 제어(IAM)를 선택합니다.
액세스 제어(IAM) 페이지에서 역할 할당 탭을 선택합니다.
위쪽 메뉴에서 추가를 선택한 다음 결과 드롭다운 메뉴에서 역할 할당 추가를 선택합니다.
검색 상자를 사용하여 결과를 원하는 역할로 필터링합니다. 이 예제에서는 Azure Event Hubs Data Sender 및 Azure Event Hubs 데이터 수신기를 검색하고 일치하는 결과를 선택한 다음, 다음을 선택합니다.
액세스 권한 할당에서 사용자, 그룹 또는 서비스 주체를 선택한 다음 멤버 선택을 선택합니다.
대화 상자에서 Microsoft Entra 사용자 이름(일반적으로 user@domain 이메일 주소)을 검색한 다음, 대화 상자 하단에서 선택을 선택합니다.
검토 + 할당을 선택하여 최종 페이지로 이동한 다음, 검토 + 할당을 다시 선택하여 프로세스를 완료합니다.
액세스 역할 부여에 대한 자세한 내용은 Microsoft Entra ID를 사용하여 Event Hubs 리소스에 대한 액세스 권한 부여를 참조하세요.
2) 암호 없는 연결을 사용하도록 앱 코드 로그인 및 마이그레이션
로컬 개발의 경우 Event Hubs에서 역할을 할당한 것과 동일한 Microsoft Entra 계정으로 인증되었는지 확인합니다. Azure CLI, Visual Studio, Azure PowerShell 또는 기타 도구(예: IntelliJ)를 통해 인증할 수 있습니다.
다음 명령을 사용하여 Azure CLI를 통해 Azure에 로그인합니다.
az login
다음으로, 다음 단계를 사용하여 암호 없는 연결을 사용하도록 Spring Kafka 애플리케이션을 업데이트합니다. 개념적으로 유사하지만 각 프레임워크는 서로 다른 구현 세부 정보를 사용합니다.
프로젝트 내에서 pom.xml 파일을 열고 다음 참조를 추가합니다.
<dependency> <groupId>com.azure</groupId> <artifactId>azure-identity</artifactId> <version>1.6.0</version> </dependency>
마이그레이션 후 다음 예제와 같이 OAuth2 인증을 위해 프로젝트에서 AuthenticateCallbackHandler 및 OAuthBearerToken을 구현합니다.
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(); } }
Kafka 생산자 또는 소비자를 만들 때 SASL/OAUTHBEARER 메커니즘을 지원하는 데 필요한 구성을 추가합니다. 다음 예제에서는 마이그레이션 전후 코드의 모양을 보여 줍니다. 두 예제에서 자리 표시자를 Event Hubs 네임스페이스의 이름으로 바꿉
<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);
마이그레이션 후 코드는 다음 예제와 같이 표시됩니다. 이 예제에서는 자리 표시자를 구현된
KafkaOAuth2AuthenticateCallbackHandler
클래스의 전체 클래스 이름으로 바꿉<path-to-your-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 서비스에 인증할 수 있습니다. 예를 들어 관리 ID가 할당된 Azure Spring Apps 인스턴스에 배포된 애플리케이션은 Kafka용 Azure Event Hubs에 연결할 수 있습니다.
이 섹션에서는 두 단계를 실행하여 암호 없는 방식으로 Azure 호스팅 환경에서 애플리케이션을 실행할 수 있도록 합니다.
- Azure 호스팅 환경에 대한 관리 ID를 할당합니다.
- 관리 ID에 역할을 할당합니다.
참고 항목
또한 Azure는 Event Hubs와 호스팅 서비스를 연결하는 데 도움이 되는 서비스 커넥터를 제공합니다. 서비스 커넥터를 사용하여 호스팅 환경을 구성하면 Service Connector에서 관리 ID에 역할을 할당하는 단계를 생략할 수 있습니다. 다음 섹션에서는 두 가지 방법으로 Azure 호스팅 환경을 구성하는 방법을 설명합니다. 하나는 Service Connector를 통해, 다른 하나는 각 호스팅 환경을 직접 구성하여 구성합니다.
Important
서비스 커넥터의 명령에는 Azure CLI 2.41.0 이상이 필요합니다.
Azure 호스팅 환경에 대한 관리 ID 할당
다음 단계에서는 다양한 웹 호스팅 서비스에 시스템 할당 관리 ID를 할당하는 방법을 보여 줍니다. 관리 ID는 이전에 설정한 앱 구성을 사용하여 다른 Azure 서비스에 안전하게 연결할 수 있습니다.
Azure 앱 Service 인스턴스의 주 개요 페이지에서 탐색 창에서 ID를 선택합니다.
시스템 할당 탭에서 상태 필드를 켜야 합니다. 시스템이 할당한 ID는 내부적으로 Azure에서 관리되며 관리 작업을 처리합니다. ID의 세부 정보 및 ID는 코드에서 공개되지 않습니다.
Azure CLI를 사용하여 Azure 호스팅 환경에서 관리 ID를 할당할 수도 있습니다.
다음 예제와 같이 az webapp identity assign 명령을 사용하여 관리 ID를 Azure 앱 Service 인스턴스에 할당할 수 있습니다.
export AZURE_MANAGED_IDENTITY_ID=$(az webapp identity assign \
--resource-group $AZ_RESOURCE_GROUP \
--name <app-service-name> \
--query principalId \
--output tsv)
관리 ID에 역할 할당
다음으로, Event Hubs 네임스페이스에 액세스하기 위해 만든 관리 ID에 권한을 부여합니다. 로컬 개발 사용자와 마찬가지로 관리 ID에 역할을 할당하여 권한을 부여할 수 있습니다.
서비스 커넥터를 사용하여 서비스를 연결한 경우 이 단계를 완료할 필요가 없습니다. 다음과 같은 필수 구성이 처리되었습니다.
연결을 만들 때 관리 ID를 선택한 경우 앱에 대해 시스템 할당 관리 ID가 생성되고 Event Hubs에 Azure Event Hubs 데이터 발신자 및 Azure Event Hubs 데이터 수신기 역할이 할당되었습니다.
연결 문자열 사용하도록 선택한 경우 연결 문자열 앱 환경 변수로 추가되었습니다.
앱 테스트
이러한 코드가 변경되면 브라우저에서 호스트된 애플리케이션으로 이동합니다. 앱이 Kafka용 Azure Event Hubs에 성공적으로 연결할 수 있어야 합니다. 역할 할당이 Azure 환경을 통해 전파되는 데 몇 분 정도 걸릴 수 있습니다. 이제 개발자가 애플리케이션 자체에서 비밀을 관리하지 않고도 애플리케이션이 로컬 및 프로덕션 환경 모두에서 실행되도록 구성되었습니다.
다음 단계
이 자습서에서는 애플리케이션을 암호 없는 연결로 마이그레이션하는 방법을 알아보았습니다.
다음 리소스를 참조하여 이 문서에서 설명하는 개념을 자세히 살펴볼 수 있습니다.