Connessione dall'applicazione alle risorse senza gestire le credenziali
Articolo
Le risorse di Azure con supporto delle identità gestite offrono sempre un'opzione per specificare un'identità gestita per connettersi alle risorse di Azure che supportano l'autenticazione di Microsoft Entra. Il supporto delle identità gestite rende superfluo agli sviluppatori la gestione delle credenziali nel codice. Le identità gestite sono l'opzione di autenticazione consigliata quando si lavora con le risorse di Azure che le supportano.
Leggere una panoramica delle identità gestite.
Questa pagina illustra come configurare un servizio app in modo che possa connettersi ad Azure Key Vault, Archiviazione di Azure e Microsoft SQL Server. Gli stessi principi possono essere usati per qualsiasi risorsa di Azure che supporta le identità gestite e che si connetterà alle risorse che supportano l'autenticazione di Microsoft Entra.
Gli esempi di codice usano la libreria client di Azure Identity, che è il metodo consigliato perché gestisce automaticamente molti dei passaggi necessari, inclusa l'acquisizione di un token di accesso usato nella connessione.
A quali risorse possono connettersi le identità gestite?
Un'identità gestita può connettersi a qualsiasi risorsa che supporti l'autenticazione di Microsoft Entra. In generale, non è necessario alcun supporto speciale per la risorsa per consentire alle identità gestite di connettersi.
Alcune risorse non supportano l'autenticazione di Microsoft Entra o la libreria client non supporta l'autenticazione con un token. Continuare a leggere per vedere le indicazioni su come usare un'identità gestita per accedere in modo sicuro alle credenziali senza dover archiviarle nel codice o nella configurazione dell'applicazione.
Creare un'identità gestita
Esistono due tipi di identità gestita: assegnata dal sistema e assegnata dall'utente. Le identità assegnate dal sistema sono collegate direttamente a una singola risorsa di Azure. Se la risorsa viene eliminata, verrà eliminata anche l'identità gestita. Un'identità gestita assegnata dall'utente può essere associata a più risorse di Azure e il relativo ciclo di vita è indipendente da tali risorse.
Per molti scenari è consigliabile usare un'identità gestita assegnata dall'utente. Se la risorsa di origine in uso non supporta le identità gestite assegnate dall'utente, è necessario fare riferimento alla documentazione del provider di risorse per informazioni su come configurarla per avere un'identità gestita assegnata dal sistema.
Importante
L'account usato per creare identità gestite richiede un ruolo come "Collaboratore identità gestita" per creare una nuova identità gestita assegnata dall'utente.
Dopo aver creato un'identità gestita assegnata dall'utente, prendere nota dei clientId valori e principalId restituiti quando viene creata l'identità gestita. Si usano principalId durante l'aggiunta di autorizzazioni e clientId nel codice dell'applicazione.
Configurare Servizio app con un'identità gestita assegnata dall'utente
Dopo aver configurato il servizio app per usare un'identità gestita assegnata dall'utente, concedere le autorizzazioni necessarie all'identità. In questo scenario si usa questa identità per interagire con Archiviazione di Azure, quindi è necessario usare il sistema RBAC (Role Based Controllo di accesso) di Azure per concedere all'identità gestita assegnata dall'utente le autorizzazioni per la risorsa.
Importante
Per aggiungere assegnazioni di ruolo, è necessario un ruolo come "Amministratore accesso utenti" o "Proprietario" per la risorsa di destinazione. Assicurarsi di concedere il privilegio minimo necessario per l'esecuzione dell'applicazione.
Per le risorse a cui si vuole accedere è necessario concedere le autorizzazioni di identità. Ad esempio, se si richiede un token di accesso a Key Vault, è necessario aggiungere anche un criterio di accesso che include l'identità dell'app o della funzione. In caso contrario, le chiamate a Key Vault verranno rifiutate, anche se si usa un token valido. Lo stesso vale per il database SQL di Azure. Per altre informazioni sulle risorse che supportano i token di Microsoft Entra, vedere Servizi di Azure che supportano l'autenticazione di Microsoft Entra.
Uso delle identità gestite nel codice
Dopo aver completato i passaggi descritti in precedenza, l'servizio app ha un'identità gestita con autorizzazioni per una risorsa di Azure. È possibile usare l'identità gestita per ottenere un token di accesso che il codice può usare per interagire con le risorse di Azure, anziché archiviare le credenziali nel codice.
È consigliabile usare le librerie client fornite per il linguaggio di programmazione preferito. Queste librerie acquisiscono automaticamente i token di accesso, semplificando l'autenticazione con Microsoft Entra ID. Per altre informazioni, vedere librerie client per l'autenticazione delle identità gestite.
Uso di una libreria di identità di Azure per accedere alle risorse di Azure
Ognuna delle librerie di identità di Azure fornisce un DefaultAzureCredential tipo.
DefaultAzureCredential tenta di autenticare automaticamente l'utente tramite flussi diversi, incluse le variabili di ambiente o un accesso interattivo. Il tipo di credenziale può essere usato in un ambiente di sviluppo con le tue credenziali. Può essere usato anche nell'ambiente di produzione di Azure usando un'identità gestita. Non sono necessarie modifiche al codice quando si distribuisce l'applicazione.
Se si usano identità gestite assegnate dall'utente, è anche necessario specificare in modo esplicito l'identità gestita assegnata dall'utente con cui si vuole eseguire l'autenticazione passando l'ID client dell'identità come parametro. È possibile recuperare l'ID client passando all'identità nel portale di Azure.
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();
Se si usa Azure Spring Apps, è possibile connettersi a database SQL di Azure usando un'identità gestita senza apportare modifiche al codice.
Aprire il file src/main/resources/application.properties e aggiungere Authentication=ActiveDirectoryMSI; alla fine della riga seguente. Assicurarsi di usare il valore corretto per $AZ_DATABASE_NAME la variabile.
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)
}
}
Uso di Microsoft Authentication Library (MSAL) per accedere alle risorse di Azure
Oltre alle librerie di identità di Azure, è anche possibile usare MSAL per accedere alle risorse di Azure usando identità gestite. I frammenti di codice seguenti illustrano come usare MSAL per accedere alle risorse di Azure in vari linguaggi di programmazione.
Per le identità gestite assegnate dal sistema, lo sviluppatore non deve passare informazioni aggiuntive. MSAL deduce automaticamente i metadati pertinenti sull'identità assegnata. Per le identità gestite assegnate dall'utente, lo sviluppatore deve passare l'ID client, l'identificatore completo della risorsa o l'ID oggetto dell'identità gestita.
È quindi possibile acquisire un token per accedere a una risorsa. Prima di usare le identità gestite, gli sviluppatori devono abilitarli per le risorse che vogliono usare.
using Microsoft.Identity.Client;
using System;
string resource = "https://vault.azure.net";
// Applies to system-assigned managed identities only
IManagedIdentityApplication mi = ManagedIdentityApplicationBuilder.Create(ManagedIdentityId.SystemAssigned)
.Build();
// Applies to user-assigned managed identities only
string userAssignedManagedIdentityClientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
IManagedIdentityApplication mi = ManagedIdentityApplicationBuilder.Create(ManagedIdentityId.WithUserAssignedClientId(userAssignedManagedIdentityClientId))
.Build();
// Acquire token
AuthenticationResult result = await mi.AcquireTokenForManagedIdentity(resource)
.ExecuteAsync()
.ConfigureAwait(false);
if (!string.IsNullOrEmpty(result.AccessToken))
{
Console.WriteLine(result.AccessToken);
}
import com.microsoft.aad.msal4j.IAuthenticationResult;
import com.microsoft.aad.msal4j.ManagedIdentityApplication;
import com.microsoft.aad.msal4j.ManagedIdentityId;
import com.microsoft.aad.msal4j.ManagedIdentityParameters;
String resource = "https://vault.azure.net";
// Use this for user-assigned managed identities
private static final String USER_ASSIGNED_MI_CLIENT_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
// Use this for system-assigned managed identities
ManagedIdentityApplication miApp = ManagedIdentityApplication
.builder(ManagedIdentityId.systemAssigned())
.build();
// Use this for user-assigned managed identities
ManagedIdentityApplication miApp = ManagedIdentityApplication
.builder(ManagedIdentityId.userAssignedClientId(USER_ASSIGNED_MI_CLIENT_ID))
.build();
// Acquire token
IAuthenticationResult result = miApp.acquireTokenForManagedIdentity(
ManagedIdentityParameters.builder(resource)
.build()).get();
System.out.println(result.accessToken());
import {
LogLevel,
LoggerOptions,
AuthenticationResult,
} from "@azure/msal-common";
import {
ManagedIdentityRequestParams,
ManagedIdentityConfiguration,
ManagedIdentityApplication,
ManagedIdentityIdParams,
NodeSystemOptions,
} from "@azure/msal-node";
// Define resource
const managedIdentityRequestParams: ManagedIdentityRequestParams = {
resource: "https://vault.azure.net",
};
// This section applies to user-assigned managed identities only
const userAssignedManagedIdentityIdParams: ManagedIdentityIdParams = {
userAssignedClientId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
};
const userAssignedManagedIdentityConfig: ManagedIdentityConfiguration = {
userAssignedManagedIdentityIdParams, // applicable to user-assigned managed identities only
// optional for logging
system: {
loggerOptions: {
logLevel: LogLevel.Verbose,
} as LoggerOptions,
} as NodeSystemOptions,
};
const userSystemAssignedManagedIdentityApplication: ManagedIdentityApplication =
new ManagedIdentityApplication(userAssignedManagedIdentityConfig);
// Acquire token: user-assigned managed identity
const response: AuthenticationResult =
await userAssignedManagedIdentityApplication.acquireToken(
managedIdentityRequestParams
);
// This section applies to system-assigned managed identities only
const systemAssignedManagedIdentityConfig: ManagedIdentityConfiguration = {
// optional for logging
system: {
loggerOptions: {
logLevel: LogLevel.Verbose,
} as LoggerOptions,
} as NodeSystemOptions,
};
const systemAssignedManagedIdentityApplication: ManagedIdentityApplication =
new ManagedIdentityApplication(systemAssignedManagedIdentityConfig);
// Acquire token: system-assigned managed identity
const response: AuthenticationResult =
await systemAssignedManagedIdentityApplication.acquireToken(
managedIdentityRequestParams
);
console.log(response);
import msal
import requests
# Use this for system-assigned managed identities
managed_identity = msal.SystemAssignedManagedIdentity()
# Use this for user-assigned managed identities
userAssignedClientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
managed_identity = msal.UserAssignedManagedIdentity(client_id=userAssignedClientId)
global_app = msal.ManagedIdentityClient(managed_identity, http_client=requests.Session())
result = global_app.acquire_token_for_client(resource='https://vault.azure.net')
if "access_token" in result:
print("Token obtained!")
MSAL Go non supporta ancora le identità gestite. È possibile usare la libreria di identità di Azure per acquisire i token per le identità gestite.
Connessione a risorse che non supportano l'autenticazione basata su token o ID Di Microsoft Entra nelle librerie
Alcune risorse di Azure non supportano ancora l'autenticazione di Microsoft Entra o le librerie client non supportano l'autenticazione con un token. In genere queste risorse sono tecnologie open source che prevedono un nome utente e una password o una chiave di accesso in un stringa di connessione.
Per evitare di archiviare le credenziali nel codice o nella configurazione dell'applicazione, è possibile archiviare le credenziali come segreto in Azure Key Vault. Usando l'esempio riportato sopra, è possibile recuperare il segreto da Azure KeyVault usando un'identità gestita e passare le credenziali all'stringa di connessione. Questo approccio significa che non è necessario gestire le credenziali direttamente nel codice o nell'ambiente. Per un esempio dettagliato, vedere Usare identità gestite per accedere ai certificati di Azure Key Vault. Per altre informazioni sull'autenticazione di Azure Key Vault, vedere autenticazione di Azure Key Vault.
Linee guida per la gestione diretta dei token
In alcuni scenari, è possibile acquisire manualmente i token per le identità gestite anziché usare un metodo predefinito per connettersi alla risorsa di destinazione. Questi scenari includono nessuna libreria client per il linguaggio di programmazione usato o la risorsa di destinazione a cui ci si connette o la connessione alle risorse che non sono in esecuzione in Azure. Quando si acquisiscono i token manualmente, vengono fornite le linee guida seguenti:
Memorizzare nella cache i token acquisiti
Per prestazioni e affidabilità, è consigliabile che l'applicazione memorizza nella cache i token nella memoria locale o crittografati se si desidera salvarli su disco. Poiché i token di identità gestita sono validi per 24 ore, non c'è alcun vantaggio nella richiesta di nuovi token regolarmente, perché ne verrà restituito uno memorizzato nella cache dall'endpoint emittente del token. Se si superano i limiti delle richieste, si otterrà un limite di frequenza e si riceverà un errore HTTP 429.
Quando si acquisisce un token, è possibile impostare la cache dei token per scadere 5 minuti prima della expires_on proprietà (o equivalente) che verrà restituita quando viene generato il token.
Ispezione dei token
L'applicazione non deve basarsi sul contenuto di un token. Il contenuto del token è destinato solo al gruppo di destinatari (risorsa di destinazione) a cui si accede, non al client che richiede il token. Il contenuto del token può cambiare o essere crittografato in futuro.
Non esporre o spostare token
I token devono essere considerati come credenziali. Non esporli agli utenti o ad altri servizi; ad esempio, soluzioni di registrazione/monitoraggio. Non devono essere spostati dalla risorsa di origine che li usa, ad eccezione dell'autenticazione nella risorsa di destinazione.