Το άρθρο εξετάζει τον διαφορετικό τρόπο με τον οποίο μπορεί να εφαρμοστεί και να ασφαλιστεί ένα πρόγραμμα-πελάτης εφαρμογής Microsoft Graph σε μια εφαρμογή ASP.NET Core ή μια εφαρμογή .NET. Αυτός ο τύπος πελάτη προορίζεται για εφαρμογές ή λογική εφαρμογών όπου δεν εμπλέκεται κανένας χρήστης.
Κώδικας: https://github.com/damienbod/MicrosoftGraphAppToAppSecurity
Η πρόσβαση στο Microsoft Graph μπορεί να προετοιμαστεί για ασφάλεια από εφαρμογή σε εφαρμογή (άδειες εφαρμογής) με τρεις διαφορετικούς τρόπους. Οι ροές μπορούν να χρησιμοποιηθούν μόνο σε έναν αξιόπιστο κεντρικό υπολογιστή. Οι διαφορετικοί τύποι υλοποίησης είναι οι εξής:
- Χρήση διαχειριζόμενων ταυτοτήτων
- Χρήση του Azure SDK και του Graph SDK απευθείας με τα διαπιστευτήρια πελάτη
- Χρήση του Microsoft.Identity.Client και του MSAL για την απόκτηση ενός διακριτικού πρόσβασης που μπορεί να χρησιμοποιηθεί απευθείας έναντι του Microsoft Graph ή χρησιμοποιώντας το GraphServiceClient με την κλάση DelegateAuthenticationProvider
Χρήση διαχειριζόμενων ταυτοτήτων
Η χρήση διαχειριζόμενων ταυτοτήτων για τις αναπτύξεις Azure είναι ο πιο ασφαλής από τους τρεις τρόπους υλοποίησης αυτού του προγράμματος-πελάτη. Αυτό οφείλεται στο γεγονός ότι κανένα μυστικό ή πιστοποιητικό δεν κοινοποιείται και επομένως δεν μπορεί να γίνει κατάχρηση και δεν υπάρχει ανάγκη για μυστική εναλλαγή.
Ρύθμιση
Χρησιμοποιούμε μια εφαρμογή Ιστού που έχει αναπτυχθεί σε μια υπηρεσία εφαρμογής Azure για να ρυθμίσουμε την ασφάλεια. Δημιουργείται μια διαχειριζόμενη ταυτότητα για αυτόν τον πόρο του Azure. Εάν διαγραφεί η υπηρεσία εφαρμογής Azure, το ίδιο ισχύει και για τη διαχειριζόμενη ταυτότητα και τους εκχωρημένους ρόλους του Γραφήματος. Μόνο αυτός ο πόρος Azure μπορεί να χρησιμοποιήσει τη διαχειριζόμενη ταυτότητα.

Μόλις δημιουργηθεί ο πόρος Azure, οι ρόλοι της εφαρμογής Graph μπορούν να αντιστοιχιστούν στη διαχειριζόμενη ταυτότητα.
Powershell scripting
Δημιούργησα το σενάριο Powershell χρησιμοποιώντας ένα ιστολόγιο από τη Microsoft. Αυτό το σενάριο powershell βρίσκει τη διαχειριζόμενη ταυτότητα και εκχωρεί το Χρήστης.Διαβάστε.Όλα άδεια εφαρμογής στη διαχειριζόμενη ταυτότητα.
$TenantID = "<your-tenant-id>"
$DisplayNameServicePrincpal ="<your-azure-app-registration-or-other-azure-resource>"
$GraphAppId = "00000003-0000-0000-c000-000000000000"
$PermissionName = "User.Read.All"
Connect-AzureAD -TenantId $TenantID
$sp = (Get-AzureADServicePrincipal -Filter "displayName eq '$DisplayNameServicePrincpal'")
Write-Host $sp
$GraphServicePrincipal = Get-AzureADServicePrincipal -Filter "appId eq '$GraphAppId'"
$AppRole = $GraphServicePrincipal.AppRoles | Where-Object {$_.Value -eq $PermissionName -and $_.AllowedMemberTypes -contains "Application"}
New-AzureAdServiceAppRoleAssignment -ObjectId $sp.ObjectId -PrincipalId $sp.ObjectId -ResourceId $GraphServicePrincipal.ObjectId -Id $AppRole.Id
Αυτό μπορεί να ελεγχθεί στην πύλη Azure χρησιμοποιώντας το blade εφαρμογών Enterprise και φιλτράρισμα για διαχειριζόμενες ταυτότητες.

Τα δικαιώματα περιέχουν το γράφημα Χρήστης.Διαβάστε.Όλα άδεια εφαρμογής.

Υλοποίηση του πελάτη
Ο πελάτης υλοποιείται χρησιμοποιώντας Azure.Ταυτότητα και Graph SDK. Έχουμε δύο ρυθμίσεις, μία για την παραγωγή και όλες τις άλλες αναπτύξεις Azure και μία για ανάπτυξη. Η διαχειριζόμενη ταυτότητα χρησιμοποιείται παντού εκτός από τις αναπτύξεις προγραμματιστών και μόνο αυτή μπορεί να χρησιμοποιηθεί. Ο τοπικός προγραμματιστής χρησιμοποιεί μια εγγραφή εφαρμογής Azure με τη ροή διαπιστευτηρίων πελάτη. ο GetGraphClientWithManagedIdentity μέθοδος επιστρέφει το GraphServiceClient Ρύθμιση προγράμματος-πελάτη SDK γραφήματος για τη σωστή ανάπτυξη. Το σωστό ChainedTokenCredential χρησιμοποιείται για την ασφάλεια του πελάτη. Είναι σημαντικό μόνο η σωστή διαχειριζόμενη ταυτότητα για τον ακριβή πόρο να μπορεί να χρησιμοποιηθεί στην παραγωγή. Δεν απαιτούνται μυστικά ή πιστοποιητικά για αυτήν τη λύση, η διαχειριζόμενη ταυτότητα και η Azure φροντίζει για αυτό. Το GraphServiceClient είναι για την εφαρμογή και χειρίζεται τη δημιουργία HttpClient, ώστε η υπηρεσία να δημιουργείται ως singleton.
using Azure.Identity;
using Microsoft.Graph;
namespace GraphManagedIdentity;
public class GraphApplicationClientService
{
private readonly IConfiguration _configuration;
private readonly IHostEnvironment _environment;
private GraphServiceClient? _graphServiceClient;
public GraphApplicationClientService(IConfiguration configuration, IHostEnvironment environment)
{
_configuration = configuration;
_environment = environment;
}
/// <summary>
/// gets a singleton instance of the GraphServiceClient
/// </summary>
/// <returns></returns>
public GraphServiceClient GetGraphClientWithManagedIdentityOrDevClient()
{
if (_graphServiceClient != null)
return _graphServiceClient;
string[] scopes = new[] { "https://graph.microsoft.com/.default" };
var chainedTokenCredential = GetChainedTokenCredentials();
_graphServiceClient = new GraphServiceClient(chainedTokenCredential, scopes);
return _graphServiceClient;
}
private ChainedTokenCredential GetChainedTokenCredentials()
{
if (!_environment.IsDevelopment())
{
return new ChainedTokenCredential(new ManagedIdentityCredential());
}
else // dev env
{
var tenantId = _configuration["AzureAd:TenantId"];
var clientId = _configuration.GetValue<string>("AzureAd:ClientId");
var clientSecret = _configuration.GetValue<string>("AzureAd:ClientSecret");
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// https://docs.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var devClientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
var chainedTokenCredential = new ChainedTokenCredential(devClientSecretCredential);
return chainedTokenCredential;
}
}
}
Η υπηρεσία προστίθεται στο IoC και μπορεί να χρησιμοποιηθεί οπουδήποτε στην εφαρμογή. Μόλις αναπτυχθεί, χρησιμοποιείται η διαχειριζόμενη ταυτότητα, διαφορετικά εκτελείται η ρύθμιση προγραμματιστή.
builder.Services.AddSingleton<GraphApplicationClientService>();
builder.Services.AddScoped<AadGraphSdkApplicationClient>();
Το χρησιμοποιώ σε μια υπηρεσία τότε:
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Graph;
using System.Security.Cryptography.X509Certificates;
namespace GraphClientCrendentials;
public class AadGraphSdkApplicationClient
{
private readonly IConfiguration _configuration;
private readonly GraphApplicationClientService _graphService;
public AadGraphSdkApplicationClient(IConfiguration configuration, GraphApplicationClientService graphService)
{
_configuration = configuration;
_graphService = graphService;
}
public async Task<int> GetUsersAsync()
{
var graphServiceClient = _graphService.GetGraphClientWithClientSecretCredential();
IGraphServiceUsersCollectionPage users = await graphServiceClient.Users
.Request()
.GetAsync();
return users.Count;
}
}
Ρύθμιση προγραμματιστή
Μια εγγραφή εφαρμογής Azure χρησιμοποιείται για την υλοποίηση της ροής διαπιστευτηρίων πελάτη OAuth και χρησιμοποιεί το πρόγραμμα-πελάτη Graph SDK στην ανάπτυξη. Η εφαρμογή Graph προστίθεται στην εγγραφή Azure App μεμονωμένου μισθωτή. Από αυτό δημιουργείται μια εταιρική εφαρμογή.

ο ChainedTokenCredential χρησιμοποιεί το app.settings και τα μυστικά χρήστη για τη διαμόρφωση του προγράμματος-πελάτη. Ο πελάτης χρησιμοποιεί τη ροή διαπιστευτηρίων πελάτη OAuth για να αποκτήσει ένα διακριτικό πρόσβασης. Συνήθως χρησιμοποιώ μυστικά για ανάπτυξη για λόγους απλότητας, αλλά εάν απαιτείται περισσότερη ασφάλεια, μπορεί να χρησιμοποιηθεί ένα πιστοποιητικό και το μυστικό/πιστοποιητικό μπορεί να χρησιμοποιηθεί απευθείας από ένα Azure KeyVault.
"AzureAd": {
"TenantId": "7ff95b15-dc21-4ba6-bc92-824856578fc1",
"ClientId": "3606b25d-f670-4bab-ab70-437460143d89"
//"ClientSecret": "add secret to the user secrets"
//"CertificateName": "[Or instead of client secret: Enter here the name of a certificate (from the user cert store) as registered with your application]",
//"Certificate": {
// "SourceType": "KeyVault",
// "KeyVaultUrl": "<VaultUri>",
// "KeyVaultCertificateName": "<CertificateName>"
//}
},
Απευθείας χρήση του Azure SDK και του Graph SDK
Ένα πρόγραμμα-πελάτη Microsoft Graph μπορεί να ρυθμιστεί ώστε να χρησιμοποιεί τη ροή διαπιστευτηρίων του προγράμματος-πελάτη για την προετοιμασία του Graph SDK GraphServiceClient. Αυτός είναι ένας καλός τρόπος δημιουργίας της ροής διαπιστευτηρίων πελάτη OAuth, εάν χρησιμοποιείται εκτός του μισθωτή Azure. Συνιστάται η χρήση πιστοποιητικού και αυτό συνήθως αποθηκεύεται σε ένα Azure Key Vault. Αυτό χρησιμοποιεί τη ροή διαπιστευτηρίων πελάτη OAuth και χρησιμοποιεί τους ισχυρισμούς του πελάτη για να αποκτήσει ένα νέο διακριτικό πρόσβασης.
Η ροή μπορεί να ρυθμιστεί ώστε να χρησιμοποιεί ένα μυστικό:
private GraphServiceClient GetGraphClientWithClientSecretCredential()
{
string[] scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = _configuration["AzureAd:TenantId"];
// Values from app registration
var clientId = _configuration.GetValue<string>("AzureAd:ClientId");
var clientSecret = _configuration.GetValue<string>("AzureAd:ClientSecret");
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// https://docs.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
return new GraphServiceClient(clientSecretCredential, scopes);
}
Ή ρυθμίστε για να χρησιμοποιήσετε ένα πιστοποιητικό:
private async Task<GraphServiceClient> GetGraphClientWithClientCertificateCredentialAsync()
{
string[] scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = _configuration["AzureAd:TenantId"];
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// Values from app registration
var clientId = _configuration.GetValue<string>("AzureAd:ClientId");
var certififacte = await GetCertificateAsync();
var clientCertificateCredential = new ClientCertificateCredential(
tenantId, clientId, certififacte, options);
// var clientCertificatePath = _configuration.GetValue<string>("AzureAd:CertificateName");
// https://learn.microsoft.com/en-us/dotnet/api/azure.identity.clientcertificatecredential?view=azure-dotnet
// var clientCertificateCredential = new ClientCertificateCredential(
// tenantId, clientId, clientCertificatePath, options);
return new GraphServiceClient(clientCertificateCredential, scopes);
}
private async Task<X509Certificate2> GetCertificateAsync()
{
var identifier = _configuration["AzureAd:ClientCertificates:0:KeyVaultCertificateName"];
if (identifier == null)
throw new ArgumentNullException(nameof(identifier));
var vaultBaseUrl = _configuration["AzureAd:ClientCertificates:0:KeyVaultUrl"];
if(vaultBaseUrl == null)
throw new ArgumentNullException(nameof(vaultBaseUrl));
var secretClient = new SecretClient(vaultUri: new Uri(vaultBaseUrl), credential: new DefaultAzureCredential());
// Create a new secret using the secret client.
var secretName = identifier;
//var secretVersion = "";
KeyVaultSecret secret = await secretClient.GetSecretAsync(secretName);
var privateKeyBytes = Convert.FromBase64String(secret.Value);
var certificateWithPrivateKey = new X509Certificate2(privateKeyBytes,
string.Empty, X509KeyStorageFlags.MachineKeySet);
return certificateWithPrivateKey;
}
Συνήθως χρησιμοποιώ ένα μυστικό για την ανάπτυξη και ένα πιστοποιητικό για την παραγωγή.
Χρήση Microsoft.Identity.Client και MSAL
Ένας τρίτος τρόπος υλοποίησης του προγράμματος-πελάτη Graph είναι να χρησιμοποιηθεί Microsoft.Identity.Client ή Microsoft.Identity.Web. Αυτό χρησιμοποιεί το ConfidentialClientApplicationBuilder για τη δημιουργία μιας νέας παρουσίας IconfidentialClientApplication και μπορεί να χρησιμοποιήσει ένα μυστικό ή ένα πιστοποιητικό για να αποκτήσει το διακριτικό πρόσβασης.
Microsoft.Identity.Client με μυστικό:
var app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority))
.Build();
app.AddInMemoryTokenCache();
ή με πιστοποιητικό και δηλώσεις πελάτη:
var app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithCertificate(certificate)
.WithAuthority(new Uri(config.Authority))
.Build();
app.AddInMemoryTokenCache();
ο GraphServiceClient μπορεί να δημιουργηθεί χρησιμοποιώντας το DelegateAuthenticationProvider. Όπως καταλαβαίνω θα πρέπει να αποφύγετε τη χρήση του DelegateAuthenticationProvider αν είναι δυνατόν.
GraphServiceClient graphServiceClient =
new GraphServiceClient("https://graph.microsoft.com/V1.0/",
new DelegateAuthenticationProvider(async (requestMessage) =>
{
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
AuthenticationResult result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
// Add the access token in the Authorization header of the API request.
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", result.AccessToken);
}));
}
Σημειώσεις
Υπάρχουν τρεις διαφορετικοί τρόποι δημιουργίας προγραμμάτων-πελατών εφαρμογών Microsoft Graph και μερικές φορές είναι δύσκολο να καταλάβουμε πότε πρέπει να χρησιμοποιήσετε ποιο. Αυτό δεν είναι για τους ανάδοχους πελάτες. Σε μια εφαρμογή ASP.NET Core που θα χρησιμοποιούσατε Microsoft.Identity.Web για έναν ανατεθέντα πελάτη που χρησιμοποιεί στη συνέχεια το Microsoft Graph για λογαριασμό του χρήστη. Οι διαχειριζόμενες ταυτότητες που έχουν εκχωρηθεί στο σύστημα δεν απαιτούν διαχείριση μυστικών ή πιστοποιητικών, αλλά μπορούν να χρησιμοποιηθούν μόνο στον ίδιο μισθωτή. Η ροή διαπιστευτηρίων πελάτη μπορεί να χρησιμοποιηθεί από οπουδήποτε. Η Microsoft συνιστά τη χρήση πιστοποιητικών κατά τη χρήση της ροής διαπιστευτηρίων πελάτη.
Συνδέσεις
https://learn.microsoft.com/en-us/azure/active-directory/develop/sample-v2-code#service–daemon
https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/tree/master/1-Call-MSGraph
https://oceanleaf.ch/azure-managed-identity/
https://learningbydoing.cloud/blog/stop-using-client-secrets-start-using-managed-identities/
https://github.com/Azure/azure-sdk-for-net
https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet
https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS