관리 ID를 지원하는 Azure 리소스는 항상 Microsoft Entra 인증을 지원하는 Azure 리소스에 연결하기 위해 관리 ID를 지정하는 옵션을 제공합니다. 관리 ID 지원을 사용하면 개발자가 코드에서 자격 증명을 관리할 필요가 없습니다. 관리 ID는 이를 지원하는 Azure 리소스를 사용할 때 권장되는 인증 옵션입니다. 관리 ID 개요를 참조하세요.
이 페이지에서는 Azure Key Vault, Azure Storage 및 Microsoft SQL Server에 연결할 수 있도록 App Service를 구성하는 방법을 보여 줍니다. 관리 ID를 지원하고 Microsoft Entra 인증을 지원하는 리소스에 연결되는 모든 Azure 리소스에 동일한 원칙을 사용할 수 있습니다.
코드 샘플에서는 연결에 사용되는 액세스 토큰 획득을 포함하여 많은 단계를 자동으로 처리하므로 권장되는 방법인 Azure ID 클라이언트 라이브러리를 사용합니다.
관리 ID에서 연결할 수 있는 리소스는 무엇인가요?
관리 ID는 Microsoft Entra 인증을 지원하는 모든 리소스에 연결할 수 있습니다. 일반적으로 리소스에는 관리 ID에서 연결할 수 있도록 하는 데 필요한 특별한 지원이 없습니다.
일부 리소스는 Microsoft Entra 인증을 지원하지 않거나 해당 클라이언트 라이브러리가 토큰을 사용한 인증을 지원하지 않습니다. 코드 또는 애플리케이션 구성에 저장할 필요 없이 관리 ID를 사용하여 자격 증명에 안전하게 액세스하는 방법에 대한 지침을 보려면 계속 읽어보세요.
관리 ID 만들기
관리 ID에는 시스템 할당 및 사용자 할당의 두 가지 유형이 있습니다. 시스템이 할당한 ID는 단일 Azure 리소스에 직접 연결됩니다. Azure 리소스가 삭제되면 ID도 삭제됩니다. 사용자가 할당한 관리 ID는 여러 Azure 리소스와 연결할 수 있으며 해당 수명 주기는 이러한 리소스와 관련이 없습니다.
대부분의 시나리오에서는 사용자가 할당한 관리 ID를 사용하는 것이 좋습니다. 사용하는 원본 리소스에서 사용자가 할당한 관리 ID를 지원하지 않는 경우 해당 리소스 공급자의 설명서를 참조하여 시스템이 할당한 관리 ID를 갖도록 구성하는 방법을 알아보세요.
Important
관리 ID를 만드는 데 사용되는 계정에는 새 사용자 할당 관리 ID를 만들려면 "관리 ID 기여자"와 같은 역할이 필요합니다.
사용자가 할당한 관리 ID를 만든 후 관리 ID를 만들 때 반환되는 clientId 및 principalId 값을 기록해 둡니다. 권한을 추가하는 동안에는 principalId를 사용하고, 애플리케이션 코드에서는 clientId를 사용합니다.
사용자가 할당한 관리 ID를 사용한 App Service 구성
코드에서 관리 ID를 사용하려면 해당 ID를 사용할 App Service에 할당해야 합니다. 사용자가 할당한 관리 ID를 사용하도록 App Service를 구성하는 프로세스에서는 앱 구성에서 관리 ID의 리소스 식별자를 지정해야 합니다.
ID에 권한 추가
사용자가 할당한 관리 ID를 사용하도록 App Service를 구성한 후에는 ID에 필요한 권한을 부여합니다. 이 시나리오에서는 이 ID를 사용하여 Azure Storage와 상호 작용하므로 Azure RBAC(역할 기반 액세스 제어) 시스템을 사용하여 리소스에 사용자 할당 관리 ID 권한을 부여해야 합니다.
Important
역할 할당을 추가하려면 대상 리소스에 대해 "사용자 액세스 관리자" 또는 "소유자"와 같은 역할이 필요합니다. 애플리케이션을 실행하는 데 필요한 최소 권한을 부여해야 합니다.
액세스하려는 모든 리소스에는 ID 권한을 부여해야 합니다. 예를 들어 Key Vault에 액세스하기 위한 토큰을 요청하는 경우 앱 또는 함수의 관리 ID를 포함하는 액세스 정책도 추가해야 합니다. 그렇지 않으면 유효한 토큰을 사용하는 경우에도 Key Vault에 대한 호출이 거부됩니다. Azure SQL Database에서도 마찬가지입니다. Microsoft Entra 토큰을 지원하는 리소스에 대한 자세한 내용은 Microsoft Entra 인증을 지원하는 Azure 서비스를 참조하세요.
코드에서 관리 ID 사용
위에서 설명한 단계를 완료하면 App Service에 Azure 리소스에 대한 권한이 있는 관리 ID가 있습니다. 코드에서 자격 증명을 저장하는 대신, 관리되는 ID를 사용하면 코드에서 Azure 리소스와 상호 작용하는 데 사용할 수 있는 액세스 토큰을 얻을 수 있습니다.
기본 설정 프로그래밍 언어에 적합한 Azure ID 라이브러리를 사용하는 것을 권장합니다. 라이브러리는 액세스 토큰을 획득하므로 대상 리소스에 쉽게 연결할 수 있습니다.
Azure ID 라이브러리는 각각 DefaultAzureCredential 형식을 제공합니다. DefaultAzureCredential은 환경 변수 또는 대화형 로그인을 포함한 여러 메커니즘을 통해 자동으로 인증을 시도합니다. 자격 증명 유형은 사용자 고유의 자격 증명을 사용하여 개발 환경에서 사용할 수 있습니다. 관리 ID를 사용하여 프로덕션 Azure 환경에서도 사용할 수 있습니다. 애플리케이션을 배포할 때 코드를 변경할 필요가 없습니다.
사용자가 할당한 관리 ID를 사용하는 경우 ID의 클라이언트 ID를 매개 변수로 전달하여 인증하려는 사용자가 할당한 관리 ID도 명시적으로 지정해야 합니다. 클라이언트 ID는 Azuew Portal에서 ID로 이동하여 검색할 수 있습니다.
using Azure.Identity;
using Azure.Storage.Blobs;
// code omitted for brevity
// Specify the Client ID if using user-assigned managed identities
var clientID = Environment.GetEnvironmentVariable("Managed_Identity_Client_ID");
var credentialOptions = new DefaultAzureCredentialOptions
{
ManagedIdentityClientId = clientID
};
var credential = new DefaultAzureCredential(credentialOptions);
var blobServiceClient1 = new BlobServiceClient(new Uri("<URI of Storage account>"), credential);
BlobContainerClient containerClient1 = blobServiceClient1.GetBlobContainerClient("<name of blob>");
BlobClient blobClient1 = containerClient1.GetBlobClient("<name of file>");
if (blobClient1.Exists())
{
var downloadedBlob = blobClient1.Download();
string blobContents = downloadedBlob.Value.Content.ToString();
}
import com.azure.identity.DefaultAzureCredential;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.storage.blob.BlobClient;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
// read the Client ID from your environment variables
String clientID = System.getProperty("Client_ID");
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder()
.managedIdentityClientId(clientID)
.build();
BlobServiceClient blobStorageClient = new BlobServiceClientBuilder()
.endpoint("<URI of Storage account>")
.credential(credential)
.buildClient();
BlobContainerClient blobContainerClient = blobStorageClient.getBlobContainerClient("<name of blob container>");
BlobClient blobClient = blobContainerClient.getBlobClient("<name of blob/file>");
if (blobClient.exists()) {
String blobContent = blobClient.downloadContent().toString();
}
import { DefaultAzureCredential } from "@azure/identity";
import { BlobServiceClient } from "@azure/storage-blob";
// Specify the Client ID if using user-assigned managed identities
const clientID = process.env.Managed_Identity_Client_ID;
const credential = new DefaultAzureCredential({
managedIdentityClientId: clientID
});
const blobServiceClient = new BlobServiceClient("<URI of Storage account>", credential);
const containerClient = blobServiceClient.getContainerClient("<name of blob>");
const blobClient = containerClient.getBlobClient("<name of file>");
async function downloadBlob() {
if (await blobClient.exists()) {
const downloadBlockBlobResponse = await blobClient.download();
const downloadedBlob = await streamToString(downloadBlockBlobResponse.readableStreamBody);
console.log("Downloaded blob content:", downloadedBlob);
}
}
async function streamToString(readableStream) {
return new Promise((resolve, reject) => {
const chunks = [];
readableStream.on("data", (data) => {
chunks.push(data.toString());
});
readableStream.on("end", () => {
resolve(chunks.join(""));
});
readableStream.on("error", reject);
});
}
downloadBlob().catch(console.error);
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient
import os
# Specify the Client ID if using user-assigned managed identities
client_id = os.getenv("Managed_Identity_Client_ID")
credential = DefaultAzureCredential(managed_identity_client_id=client_id)
blob_service_client = BlobServiceClient(account_url="<URI of Storage account>", credential=credential)
container_client = blob_service_client.get_container_client("<name of blob>")
blob_client = container_client.get_blob_client("<name of file>")
def download_blob():
if blob_client.exists():
download_stream = blob_client.download_blob()
blob_contents = download_stream.readall().decode('utf-8')
print("Downloaded blob content:", blob_contents)
download_blob()
package main
import (
"context"
"fmt"
"io"
"os"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
)
func main() {
// The client ID for the user-assigned managed identity is read from the AZURE_CLIENT_ID env var
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
fmt.Printf("failed to obtain a credential: %v\n", err)
return
}
accountURL := "<URI of Storage account>"
containerName := "<name of blob>"
blobName := "<name of file>"
serviceClient, err := azblob.NewServiceClient(accountURL, cred, nil)
if err != nil {
fmt.Printf("failed to create service client: %v\n", err)
return
}
containerClient := serviceClient.NewContainerClient(containerName)
blobClient := containerClient.NewBlobClient(blobName)
// Check if the blob exists
_, err = blobClient.GetProperties(context.Background(), nil)
if err != nil {
fmt.Printf("failed to get blob properties: %v\n", err)
return
}
// Download the blob
downloadResponse, err := blobClient.Download(context.Background(), nil)
if err != nil {
fmt.Printf("failed to download blob: %v\n", err)
return
}
// Read the blob content
blobData := downloadResponse.Body(nil)
defer blobData.Close()
blobContents := new(strings.Builder)
_, err = io.Copy(blobContents, blobData)
if err != nil {
fmt.Printf("failed to read blob data: %v\n", err)
return
}
fmt.Println("Downloaded blob content:", blobContents.String())
}
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Azure.Core;
// code omitted for brevity
// Specify the Client ID if using user-assigned managed identities
var clientID = Environment.GetEnvironmentVariable("Managed_Identity_Client_ID");
var credentialOptions = new DefaultAzureCredentialOptions
{
ManagedIdentityClientId = clientID
};
var credential = new DefaultAzureCredential(credentialOptions);
var client = new SecretClient(
new Uri("https://<your-unique-key-vault-name>.vault.azure.net/"),
credential);
KeyVaultSecret secret = client.GetSecret("<my secret>");
string secretValue = secret.Value;
using Azure.Identity;
using Microsoft.Data.SqlClient;
// code omitted for brevity
// Specify the Client ID if using user-assigned managed identities
var clientID = Environment.GetEnvironmentVariable("Managed_Identity_Client_ID");
var credentialOptions = new DefaultAzureCredentialOptions
{
ManagedIdentityClientId = clientID
};
AccessToken accessToken = await new DefaultAzureCredential(credentialOptions).GetTokenAsync(
new TokenRequestContext(new string[] { "https://database.windows.net//.default" }));
using var connection = new SqlConnection("Server=<DB Server>; Database=<DB Name>;")
{
AccessToken = accessToken.Token
};
var cmd = new SqlCommand("select top 1 ColumnName from TableName", connection);
await connection.OpenAsync();
SqlDataReader dr = cmd.ExecuteReader();
while(dr.Read())
{
Console.WriteLine(dr.GetValue(0).ToString());
}
dr.Close();
Azure Spring Apps를 사용하는 경우 코드를 변경할 필요 없이 관리 ID를 사용하여 Azure SQL Database에 연결할 수 있습니다.
src/main/resources/application.properties 파일을 열고 다음 줄 끝에 Authentication=ActiveDirectoryMSI;를 추가합니다. 올바른 값을 $AZ_DATABASE_NAME 변수에 사용해야 합니다.
import { DefaultAzureCredential } from "@azure/identity";
import { Connection, Request } from "tedious";
// Specify the Client ID if using a user-assigned managed identity
const clientID = process.env.Managed_Identity_Client_ID;
const credential = new DefaultAzureCredential({
managedIdentityClientId: clientID
});
async function getAccessToken() {
const tokenResponse = await credential.getToken("https://database.windows.net//.default");
return tokenResponse.token;
}
async function queryDatabase() {
const accessToken = await getAccessToken();
const config = {
server: "<your-server-name>",
authentication: {
type: "azure-active-directory-access-token",
options: {
token: accessToken
}
},
options: {
database: "<your-database-name>",
encrypt: true
}
};
const connection = new Connection(config);
connection.on("connect", err => {
if (err) {
console.error("Connection failed:", err);
return;
}
const request = new Request("SELECT TOP 1 ColumnName FROM TableName", (err, rowCount, rows) => {
if (err) {
console.error("Query failed:", err);
return;
}
rows.forEach(row => {
console.log(row.value);
});
connection.close();
});
connection.execSql(request);
});
connection.connect();
}
queryDatabase().catch(err => console.error("Error:", err));
import os
from azure.identity import DefaultAzureCredential
from azure.core.credentials import AccessToken
import pyodbc
# Specify the Client ID if using a user-assigned managed identity
client_id = os.getenv("Managed_Identity_Client_ID")
credential = DefaultAzureCredential(managed_identity_client_id=client_id)
# Get the access token
token = credential.get_token("https://database.windows.net//.default")
access_token = token.token
# Set up the connection string
connection_string = "Driver={ODBC Driver 18 for SQL Server};Server=<your-server-name>;Database=<your-database-name>;"
# Connect to the database
connection = pyodbc.connect(connection_string, attrs_before={"AccessToken": access_token})
# Execute the query
cursor = connection.cursor()
cursor.execute("SELECT TOP 1 ColumnName FROM TableName")
# Fetch and print the result
row = cursor.fetchone()
while row:
print(row)
row = cursor.fetchone()
# Close the connection
cursor.close()
connection.close()
package main
import (
"context"
"database/sql"
"fmt"
"os"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/denisenkom/go-mssqldb"
)
func main() {
// The client ID for the user-assigned managed identity is read from the AZURE_CLIENT_ID env var
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
fmt.Printf("failed to obtain a credential: %v\n", err)
return
}
// Get the access token
token, err := credential.GetToken(context.TODO(), azidentity.TokenRequestOptions{
Scopes: []string{"https://database.windows.net//.default"},
})
if err != nil {
fmt.Printf("Failed to get token: %v\n", err)
return
}
// Set up the connection string
connString := fmt.Sprintf("sqlserver://<your-server-name>?database=<your-database-name>&access_token=%s", token.Token)
// Connect to the database
db, err := sql.Open("sqlserver", connString)
if err != nil {
fmt.Printf("Failed to connect to the database: %v\n", err)
return
}
defer db.Close()
// Execute the query
rows, err := db.QueryContext(context.TODO(), "SELECT TOP 1 ColumnName FROM TableName")
if err != nil {
fmt.Printf("Failed to execute query: %v\n", err)
return
}
defer rows.Close()
// Fetch and print the result
for rows.Next() {
var columnValue string
if err := rows.Scan(&columnValue); err != nil {
fmt.Printf("Failed to scan row: %v\n", err)
return
}
fmt.Println(columnValue)
}
}
라이브러리에서 Microsoft Entra ID 또는 토큰 기반 인증을 지원하지 않는 리소스에 연결
일부 Azure 리소스는 아직 Microsoft Entra 인증을 지원하지 않거나 해당 클라이언트 라이브러리가 토큰을 사용한 인증을 지원하지 않습니다. 일반적으로 이러한 리소스는 연결 문자열에서 사용자 이름 및 암호 또는 액세스 키가 필요한 오픈 소스 기술입니다.
자격 증명을 코드 또는 애플리케이션 구성에 저장하지 않으려면 자격 증명을 비밀로 Azure Key Vault에 저장할 수 있습니다. 위에 표시된 예제를 사용하면 관리 ID를 사용하여 Azure KeyVault에서 비밀을 검색하고 자격 증명을 연결 문자열에 전달할 수 있습니다. 이 방법은 코드 또는 환경에서 자격 증명을 직접 처리할 필요가 없음을 의미합니다.
토큰을 직접 처리하는 경우의 지침
일부 시나리오에서는 기본 제공 방법을 사용하여 대상 리소스에 연결하는 대신 관리 ID에 대한 토큰을 수동으로 획득할 수 있습니다. 이러한 시나리오에는 사용하는 프로그래밍 언어에 적합한 클라이언트 라이브러리, 연결하는 대상 리소스 또는 Azure에서 실행되지 않는 리소스에 연결하는 클라이언트 라이브러리가 포함되지 않습니다. 토큰을 수동으로 획득하는 경우 제공되는 지침은 다음과 같습니다.
획득한 토큰 캐시
성능과 안정성을 위해 애플리케이션에서 토큰을 로컬 메모리에 캐시하거나, 디스크에 저장하려는 경우 암호화하는 것이 좋습니다. 관리 ID 토큰은 24시간 동안 유효하므로 새 토큰을 정기적으로 요청하는 것은 이점이 없습니다.이는 캐시된 토큰이 토큰 발급 엔드포인트에서 반환되기 때문입니다. 요청 제한을 초과하면 속도가 제한되고 HTTP 429 오류가 발생합니다.
토큰을 획득하면 토큰이 생성될 때 반환되는 expires_on(또는 이에 상응하는 속성)보다 5분 전에 만료되도록 토큰 캐시를 설정할 수 있습니다.
토큰 검사
애플리케이션에서 토큰의 콘텐츠를 사용하면 안 됩니다. 토큰의 콘텐츠는 토큰을 요청하는 클라이언트가 아니라 액세스하는 대상(대상 리소스)만을 위한 것입니다. 토큰 콘텐츠는 나중에 변경되거나 암호화될 수 있습니다.
토큰을 공개하거나 이동하지 않음
토큰은 자격 증명처럼 처리해야 합니다. 사용자 또는 다른 서비스(예: 로깅/모니터링 솔루션)에 공개하지 마세요. 대상 리소스에 대해 인증하는 것 외에는 이를 사용하는 원본 리소스에서 이동하면 안 됩니다.