Microsoft Entra ID란?
Microsoft Entra ID(이전의 Azure Active Directory)는 Azure의 액세스 관리 서비스이다. AWS의 IAM과 유사하게, Azure에서의 인증과 권한 관리를 다루는 서비스이다. 이를 통해 사용자와 애플리케이션에 대한 보안을 관리할 수 있다.
Azure 환경에서 프로젝트를 진행하면서, 다양한 Azure Managed Service에 대한 인증 관리를 Entra ID를 통해 진행하고 있다. Entra ID는 Azure에서 가장 권장하고 있는 인증 방식이며, 오늘은 Spring Boot에서 Entra ID를 통해 인증을 받고, 토큰을 발급받는 과정에 대해 다뤄보려 한다.
Spring Boot에서 Entra ID 인증 구현하기
Azure Cache for Redis를 예시로 하여, 인증 토큰을 발급받는 과정을 살펴보자. (Azure의 다른 서비스인 Blob Storage, MySQL 등에서도 토큰 발급 프로세스는 유사하게 진행된다.) 나는 AzureTokenProvider
를 구현하여, 토큰 발급 기능을 통합한 클래스를 개발하였다. 이를 통해 다양한 서비스에 대한 인증 관리를 해당 클래스에서 일관되게 수행할 수 있도록 하였다.
현재 진행 중인 프로젝트는 Azure Kubernetes Service(AKS)에서 애플리케이션을 실행하고 있으며, AKS의 Agent Pool Identity에 Azure Redis에 대한 작업 권한을 부여하였다. 만약 VM에서 애플리케이션을 실행한다면, VM의 Managed Identity에 Redis 접근 권한을 부여해야 한다. 즉, Manged Identity를 통해 인증을 진행하기 위해서는 애플리케이션이 Azure 리소스에서 실행되어야 한다.
Azure 공식 문서를 살펴보면, 자격 증명을 빌드하는 방법에는 다음과 같이 여러 가지가 있다.
DefaultTokenCredential
ntSecretCredential
ClientCertificateCredential
UsernamePasswordCredential
ManagedIdentityCredential
DefaultTokenCredential
은 여러 자격 증명 체계를 통합하여, 가장 적합한 방법으로 Azure 서비스에 인증하도록 해주는 것으로, 여러 인증 방법 중에서 가장 적합한 것을 자동으로 선택하여 사용한다고 한다. 그리고 대부분의 공식 샘플 코드에서 DefaultTokenCredential
을 통해 인증을 진행하고 있다. 나도 마소 엔지니어에게 DefaultTokenCredential
을 통해 인증을 구현하는 예시를 안내받았다.
Java에서 Redis를 사용하기 위한 Sample로는 Jedis, Lettuce, Redisson 3가지를 제공하고 있다. 우리는 LettuceConnectionFactory
를 사용하여, RedisTemplate
으로 개발할 예정이었기 때문에, 다음의 Lettuce Sample을 참조하였다.
Sample에서는 DefaultTokenCredential
를 사용하고 있어 이 방식으로 시도해보았지만, 어째서인지 인증이 잘 되지 않았다. (아직도 이유를 모르겠다) 몇 번의 시도 후에 ManagedIdentityCredential
로 방식을 변경하였더니 인증이 정상적으로 작동하였다.
ManagedIdentityCredential
공식 문서에서는 azure-core
와 azure-identity
라이브러리가 필요하다고 나와있었는데, 종속성 때문에 이것저것 추가하다보니 다음과 같이 늘어나게 되었다. (Blob 등 다른 서비스들의 Util을 구현하면서 추가된 라이브러리들도 포함되어 있을 수 있다)
- Gradle 기준
implementation 'com.azure:azure-core:1.51.0'
implementation 'com.azure:azure-core-http-netty:1.15.3'
implementation 'com.azure:azure-identity:1.13.0'
implementation 'com.azure:azure-json:1.2.0'
implementation 'com.azure:azure-xml:1.1.0'
implementation 'com.microsoft.azure:msal4j:1.17.1'
...
TokenCredential
인터페이스는 Azure SDK에서 인증을 처리하는 핵심 구성 요소로, 다양한 인증 방식을 지원하며, 실제로 인증을 수행하는 클래스의 인스턴스를 저장한다. ManagedIdentityCredentialBuilder
는 ManagedIdentityCredential
객체를 생성하기 위한 빌더 클래스로, 다음과 같이 작성할 수 있다.
TokenCredential tokenCredential = new ManagedIdentityCredentialBuilder().build();
Redis 인증을 위한 객체를 생성할 것이기 때문에, 다음과 같이 Client ID를 추가한다. 이때 이 Client ID는 (Reids 액세스에 대한 권한이 부여된) AKS Agent Pool Identity의 Client ID이다.
TokenCredential tokenCredential = new ManagedIdentityCredentialBuilder().clientId(redisClientId).build();
그리고 TokenRequestContext
를 통해 Azure에 토큰 발급을 요청한다. TokenRequestContext
는 Azure SDK에서 액세스 토큰 요청을 구성하는 데 사용되는 클래스로, 어떤 스코프(scope)로 토큰을 요청할 지를 정의한다.
TokenRequestContext tokenRequestContext = new TokenRequestContext().addScopes("https://redis.azure.com/.default");
마지막으로 TokenCredential
의 getToken
메서드를 호출하여 토큰을 발급받을 수 있다.
tokenCredential.getToken(tokenRequestContext).block();
통합된 AzureTokenProvider
의 최종 코드는 다음과 같다.
@Component
@Slf4j
public class AzureTokenProvider {
/* Azure Token Scope */
@Value("${azure.redis.scope}")
private String redisScope;
...
/* 관리 ID(Azure UserAssigned Identity) */
@Value("${azure.redis.client.id}")
private String redisClientId;
...
private TokenCredential tokenCredential;
/**
* @Method getRedisAccessToken
* @Description Redis Access Token 발급
* @return AccessToken
*/
public AccessToken getRedisAccessToken() {
tokenCredential = new ManagedIdentityCredentialBuilder().clientId(redisClientId).build();
TokenRequestContext tokenRequestContext = new TokenRequestContext().addScopes(redisScope);
log.info("AzureTokenProvider : Get Redis Access Token !!");
return getAccessToken(tokenRequestContext);
}
...
/**
* @Method getAccessToken
* @Description AccessToken 토큰 발급
* @return AccessToken
*/
private AccessToken getAccessToken(TokenRequestContext tokenRequestContext) {
return tokenCredential.getToken(tokenRequestContext).block();
}
}
토큰 발급은 모두 AzureTokenProvider
에서 관리하고 제공하며, 각 서비스에 대한 직접적인 기능은 Util을 통해 개발하고 있다.
짧은 주저리
Spring 애플리케이션에서 Entra ID 인증을 처음 구현할 때, 많은 어려움을 겪었다. 일단 레퍼런스가 너무 부족했는데, 액세스 키 방식을 사용하는 예시는 몇 가지 찾아볼 수 있었지만, Entra ID 인증에 대한 구체적인 예시는 거의 없었다. 제공되는 Sample로 시도해봤지만, 토큰 발급이 계속 안되었다…. 같은 어려움을 겪고 있는 사람들에게 조금이나마 도움이 되고자, Entra ID를 통해 인증을 구현했던 프로세스를 정리해보았다.
그리고 Entra ID 인증 방식을 사용할 때 치명적인 불편함이 있는데 바로 디버깅이 어렵다는 것이다. 예를 들어, 내가 진행 중인 프로젝트의 경우 AKS의 Identity를 통해 인증을 구현하기 때문에, AKS에서 실행 중일 때는 인증이 잘 진행되지만, 로컬에서 실행할 경우 인증이 실패하여 애플리케이션 실행이 안된다. 그래서 임시로 Bastion VM을 생성하여 로컬 프로퍼티에 VM의 identity 를 설정한 후에 디버깅을 진행하거나, 혹은 디버깅할 때만 Azure 관련 리소스를 빼고 하거나...하는 등의 방법을 시도해볼 수는 있다. 아직은 설계 단계라 이렇게 하고 있지만, 추후에 서버 접근 제어가 적용되면, 로컬 PC 들의 IP 대역에 대한 권한을 추가하거나 다른 방안을 고민하고 있다.
물론 오늘 정리한 내용이 정해진 답은 아니고, 하나의 방법이라고 봐주면 좋을 것 같다. 다른 사람들은 어떻게 인증을 구현했는지 궁금하고, 더 좋은 방법이 있다면, 공유해주면 너무 좋을 것 같다 :D