Dela via


Självstudie: Autentisera användare till ditt WPF-skrivbordsprogram

Gäller för: vit cirkel med en grå X-symbol. arbetskraftsaktörer grön cirkel med en vit bockmarkeringssymbol. externa klienter (läs mer)

Den här självstudien visar hur du skapar en WPF-skrivbordsapp (Windows Presentation Form) och förbereder den för autentisering med hjälp av administrationscentret för Microsoft Entra.

I den här handledningen kommer du att:

  • Konfigurera en WPF-skrivbordsapp så att den använder appregistreringsinformation.
  • Skapa en skrivbordsapp som loggar in en användare och hämtar en token åt användaren.

Förutsättningar

Skapa ett WPF-skrivbordsprogram

  1. Öppna terminalen och gå till mappen där du vill att projektet ska finnas.

  2. Initiera en WPF-skrivbordsapp och navigera till rotmappen.

    dotnet new wpf --language "C#" --name sign-in-dotnet-wpf
    cd sign-in-dotnet-wpf
    

Installera paket

Installera konfigurationsleverantörer som hjälper programmet att läsa konfigurationsdata från nyckel/värde-par i programinställningsfilen. Dessa konfigurationsabstraktioner ger möjlighet att binda konfigurationsvärden till instanser av .NET-objekt.

dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Configuration.Binder

Installera Microsoft Authentication Library (MSAL) som innehåller alla viktiga komponenter som du behöver för att hämta en token. Du installerar också MSAL-koordinatorbiblioteket som hanterar interaktioner med autentiseringskoordinatorer för skrivbord.

dotnet add package Microsoft.Identity.Client
dotnet add package Microsoft.Identity.Client.Broker

Skapa appsettings.json fil och lägg till registreringskonfigurationer

  1. Skapa appsettings.json fil i appens rotmapp.

  2. Lägg till appregistreringsinformation i appsettings.json-filen.

    {
        "AzureAd": {
            "Authority": "https://<Enter_the_Tenant_Subdomain_Here>.ciamlogin.com/",
            "ClientId": "<Enter_the_Application_Id_Here>"
        }
    }
    
    • Ersätt Enter_the_Tenant_Subdomain_Here med katalogens (klientorganisationens) underdomän.
    • Ersätt Enter_the_Application_Id_Here med program-ID:t (klient) för den app som du registrerade tidigare.
  3. När du har skapat appinställningsfilen skapar vi en annan fil med namnet AzureAdConfig.cs som hjälper dig att läsa konfigurationerna från appinställningsfilen. Skapa AzureAdConfig.cs-filen i appens rotmapp.

  4. I filen AzureAdConfig.js definierar du getters och setters för ClientId egenskaperna och Authority . Lägg till följande kod:

    namespace sign_in_dotnet_wpf
    {
        public class AzureAdConfig
        {
            public string Authority { get; set; }
            public string ClientId { get; set; }
        }
    }
    

Använda anpassad URL-domän (valfritt)

Använd en anpassad domän för att helt märka autentiserings-URL:en. Från ett användarperspektiv finns användarna kvar på din domän under autentiseringsprocessen, i stället för att omdirigeras till ciamlogin.com domännamn.

Följ dessa steg för att använda en anpassad domän:

  1. Använd stegen i Aktivera anpassade URL-domäner för appar i externa klienter för att aktivera anpassad URL-domän för din externa klientorganisation.

  2. Öppna appsettings.json fil:

    1. Uppdatera värdet för Authority egenskapen till https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Here. Ersätt Enter_the_Custom_Domain_Here med din anpassade URL-domän och Enter_the_Tenant_ID_Here med ditt klient-ID. Om du inte har ditt klientorganisations-ID kan du lära dig hur du läser dina klientorganisationsuppgifter.
    2. Lägg till egenskapen knownAuthorities med värdet [Enter_the_Custom_Domain_Here].

När du har gjort ändringarna i din appsettings.json-fil , om din anpassade URL-domän är login.contoso.com och klientorganisations-ID:t är aaaabbbb-0000-cccc-1111-dddd222eeee, bör filen se ut ungefär så här:

{
    "AzureAd": {
        "Authority": "https://login.contoso.com/aaaabbbb-0000-cccc-1111-dddd2222eeee",
        "ClientId": "Enter_the_Application_Id_Here",
        "KnownAuthorities": ["login.contoso.com"]
    }
}

Ändra projektfilen

  1. Navigera till filen sign-in-dotnet-wpf.csproj i appens rotmapp.

  2. I den här filen utför du följande två steg:

    1. Ändra filen sign-in-dotnet-wpf.csproj för att instruera din app att kopiera appsettings.json-filen till utdatakatalogen när projektet kompileras. Lägg till följande kod i filen sign-in-dotnet-wpf.csproj :
    2. Ange målramverket till windows10.0.19041.0 för att underlätta läsning av cachelagrade tokens från tokencachen, vilket du ser i hjälpklassen för tokencache.
    <Project Sdk="Microsoft.NET.Sdk">
    
        ...
    
        <!-- Set target framework to target windows10.0.19041.0 build -->
        <PropertyGroup>
            <OutputType>WinExe</OutputType>
            <TargetFramework>net7.0-windows10.0.19041.0</TargetFramework> <!-- target framework -->
            <RootNamespace>sign_in_dotnet_wpf</RootNamespace>
            <Nullable>enable</Nullable>
            <UseWPF>true</UseWPF>
        </PropertyGroup>
    
        <!-- Copy appsettings.json file to output folder. -->
        <ItemGroup>
            <None Remove="appsettings.json" />
        </ItemGroup>
    
        <ItemGroup>
            <EmbeddedResource Include="appsettings.json">
                <CopyToOutputDirectory>Always</CopyToOutputDirectory>
            </EmbeddedResource>
        </ItemGroup>
    </Project>
    

Skapa en token-cache-hjälpklass

Skapa en hjälparklass för tokencache som initierar ett token cache. Programmet försöker läsa token från cacheminnet innan det försöker hämta en ny token. Om token inte hittas i cacheminnet hämtar programmet en ny token. Vid utloggning rensas cacheminnet från alla konton och alla motsvarande åtkomsttoken.

  1. Skapa en TokenCacheHelper.cs fil i appens rotmapp.

  2. Öppna filen TokenCacheHelper.cs . Lägg till paketen och namnrymderna i filen. I följande steg fyller du i den här filen med kodlogik genom att lägga till relevant logik i TokenCacheHelper klassen.

    using System.IO;
    using System.Security.Cryptography;
    using Microsoft.Identity.Client;
    
    namespace sign_in_dotnet_wpf
    {
        static class TokenCacheHelper{}
    }
    
  3. Lägg till konstruktor i klassen TokenCacheHelper som definierar cachefilens sökväg. För paketerade skrivbordsappar (MSIX-paket, även kallade desktop bridge) är körmappen skrivskyddad. I så fall måste vi använda Windows.Storage.ApplicationData.Current.LocalCacheFolder.Path + "\msalcache.bin" som är en läs-/skrivmapp per app för paketerade appar.

    namespace sign_in_dotnet_wpf
    {
        static class TokenCacheHelper
        {
            static TokenCacheHelper()
            {
                try
                {
                    CacheFilePath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalCacheFolder.Path, ".msalcache.bin3");
                }
                catch (System.InvalidOperationException)
                {
                    CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalcache.bin3";
                }
            }
            public static string CacheFilePath { get; private set; }
            private static readonly object FileLock = new object();
        }
    }
    
    
  4. Lägg till kod för att hantera tokencachens serialisering. Gränssnittet ITokenCache implementerar offentlig åtkomst till cacheåtgärder. ITokenCache gränssnittet innehåller metoderna för att prenumerera på cache-serialiseringshändelserna, medan gränssnittet ITokenCacheSerializer exponerar de metoder som du behöver använda i cache-serialiseringshändelserna för att serialisera/deserialisera cachen. TokenCacheNotificationArgs innehåller parametrar som används avMicrosoft.Identity.Client (MSAL) anrop till cachen. ITokenCacheSerializer-gränssnittet är tillgängligt i TokenCacheNotificationArgs callback-funktionen.

    Lägg till följande kod i TokenCacheHelper klassen:

        static class TokenCacheHelper
        {
            static TokenCacheHelper()
            {...}
            public static string CacheFilePath { get; private set; }
            private static readonly object FileLock = new object();
    
            public static void BeforeAccessNotification(TokenCacheNotificationArgs args)
            {
                lock (FileLock)
                {
                    args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath)
                            ? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),
                                                     null,
                                                     DataProtectionScope.CurrentUser)
                            : null);
                }
            }
    
            public static void AfterAccessNotification(TokenCacheNotificationArgs args)
            {
                if (args.HasStateChanged)
                {
                    lock (FileLock)
                    {
                        File.WriteAllBytes(CacheFilePath,
                                           ProtectedData.Protect(args.TokenCache.SerializeMsalV3(),
                                                                 null,
                                                                 DataProtectionScope.CurrentUser)
                                          );
                    }
                }
            }
        }
    
        internal static void EnableSerialization(ITokenCache tokenCache)
        {
            tokenCache.SetBeforeAccess(BeforeAccessNotification);
            tokenCache.SetAfterAccess(AfterAccessNotification);
        }
    

    BeforeAccessNotification I metoden läser du cachen från filsystemet och om cacheminnet inte är tomt deserialiserar du det och läser in det. Metoden AfterAccessNotification anropas efter att Microsoft.Identity.Client (MSAL) har fått tillgång till cacheminnet. Om cacheminnet har ändrats serialiserar du det och sparar ändringarna i cacheminnet.

    Den EnableSerialization innehåller metoderna ITokenCache.SetBeforeAccess() och ITokenCache.SetAfterAccess().

    • ITokenCache.SetBeforeAccess() anger att ett ombud ska meddelas innan någon biblioteksmetod kommer åt cachen. Detta ger ombudet möjlighet att deserialisera en cachepost för programmet och kontona som anges i TokenCacheNotificationArgs.
    • ITokenCache.SetAfterAccess() anger att ett ombud ska meddelas när någon biblioteksmetod har åtkomst till cacheminnet. Detta ger ombudet möjlighet att serialisera ett cacheinlägg för applikationen och kontona som anges i TokenCacheNotificationArgs.

Skapa WPF-skrivbordsappens användargränssnitt

Ändra filen MainWindow.xaml för att lägga till användargränssnittselementen för appen. Öppna filen MainWindow.xaml i appens rotmapp och lägg till följande kod med kontrollavsnittet<Grid></Grid>.

    <StackPanel Background="Azure">
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
            <Button x:Name="SignInButton" Content="Sign-In" HorizontalAlignment="Right" Padding="5" Click="SignInButton_Click" Margin="5" FontFamily="Segoe Ui"/>
            <Button x:Name="SignOutButton" Content="Sign-Out" HorizontalAlignment="Right" Padding="5" Click="SignOutButton_Click" Margin="5" Visibility="Collapsed" FontFamily="Segoe Ui"/>
        </StackPanel>
        <Label Content="Authentication Result" Margin="0,0,0,-5" FontFamily="Segoe Ui" />
        <TextBox x:Name="ResultText" TextWrapping="Wrap" MinHeight="120" Margin="5" FontFamily="Segoe Ui"/>
        <Label Content="Token Info" Margin="0,0,0,-5" FontFamily="Segoe Ui" />
        <TextBox x:Name="TokenInfoText" TextWrapping="Wrap" MinHeight="70" Margin="5" FontFamily="Segoe Ui"/>
    </StackPanel>

Den här koden lägger till viktiga gränssnittselement. De metoder och objekt som hanterar funktionerna i användargränssnittselementen definieras i den MainWindow.xaml.cs fil som vi skapar i nästa steg.

  • En knapp som loggar in användaren. SignInButton_Click -metoden anropas när användaren väljer den här knappen.
  • En knapp som loggar ut användaren. SignOutButton_Click -metoden anropas när användaren väljer den här knappen.
  • En textruta som visar information om autentiseringsresultatet när användaren försöker logga in. Information som visas här returneras av ResultText objektet.
  • En textruta som visar tokeninformationen när användaren har loggat in. Information som visas här returneras av TokenInfoText objektet.

Lägga till kod i MainWindow.xaml.cs-filen

Filen MainWindow.xaml.cs innehåller den kod som tillhandahåller körningslogiken för beteendet hos användargränssnittselementen i filen MainWindow.xaml.

  1. Öppna filen MainWindow.xaml.cs i appens rotmapp.

  2. Lägg till följande kod i filen för att importera paketen och definiera platshållare för de metoder vi skapar.

    using Microsoft.Identity.Client;
    using System;
    using System.Linq;
    using System.Windows;
    using System.Windows.Interop;
    
    namespace sign_in_dotnet_wpf
    {
        public partial class MainWindow : Window
        {
            string[] scopes = new string[] { };
    
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private async void SignInButton_Click(object sender, RoutedEventArgs e){...}
    
            private async void SignOutButton_Click(object sender, RoutedEventArgs e){...}
    
            private void DisplayBasicTokenInfo(AuthenticationResult authResult){...}
        }
    }
    
  3. Lägg till följande kod i metoden SignInButton_Click. Den här metoden anropas när användaren väljer knappen Logga in .

    private async void SignInButton_Click(object sender, RoutedEventArgs e)
    {
        AuthenticationResult authResult = null;
        var app = App.PublicClientApp;
    
        ResultText.Text = string.Empty;
        TokenInfoText.Text = string.Empty;
    
        IAccount firstAccount;
    
        var accounts = await app.GetAccountsAsync();
        firstAccount = accounts.FirstOrDefault();
    
        try
        {
            authResult = await app.AcquireTokenSilent(scopes, firstAccount)
                    .ExecuteAsync();
        }
        catch (MsalUiRequiredException ex)
        {
            try
            {
                authResult = await app.AcquireTokenInteractive(scopes)
                    .WithAccount(firstAccount)
                    .WithParentActivityOrWindow(new WindowInteropHelper(this).Handle) 
                    .WithPrompt(Prompt.SelectAccount)
                    .ExecuteAsync();
            }
            catch (MsalException msalex)
            {
                ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
            }
            catch (Exception ex)
            {
                ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
                return;
            }
    
            if (authResult != null)
            {
                ResultText.Text = "Sign in was successful.";
                DisplayBasicTokenInfo(authResult);
                this.SignInButton.Visibility = Visibility.Collapsed;
                this.SignOutButton.Visibility = Visibility.Visible;
            }
        }
    }
    

    GetAccountsAsync() returnerar alla tillgängliga konton i appens cacheminne för användartoken. Gränssnittet IAccount representerar information om ett enda konto.

    För att hämta token försöker appen hämta token tyst med hjälp av AcquireTokenSilent metoden för att kontrollera om en acceptabel token finns i cacheminnet. Metoden AcquireTokenSilent kan till exempel misslyckas eftersom användaren loggade ut. När MSAL upptäcker att problemet kan lösas genom att kräva en interaktiv åtgärd utlöser det ett MsalUiRequiredException undantag. Det här undantaget gör att appen hämtar en token interaktivt.

    AcquireTokenInteractive Att anropa metoden resulterar i ett fönster som uppmanar användarna att logga in. Appar kräver vanligtvis att användarna loggar in interaktivt första gången de behöver autentisera. De kan också behöva logga in vid en tyst operation för att hämta en token. När AcquireTokenInteractive har körts för första gången AcquireTokenSilent blir den vanliga metoden att använda för att hämta token

  4. Lägg till följande kod i metoden SignOutButton_Click. Den här metoden anropas när användaren väljer knappen Logga ut .

    private async void SignOutButton_Click(object sender, RoutedEventArgs e)
    {
        var accounts = await App.PublicClientApp.GetAccountsAsync();
        if (accounts.Any())
        {
            try
            {
                await App.PublicClientApp.RemoveAsync(accounts.FirstOrDefault());
                this.ResultText.Text = "User has signed-out";
                this.TokenInfoText.Text = string.Empty;
                this.SignInButton.Visibility = Visibility.Visible;
                this.SignOutButton.Visibility = Visibility.Collapsed;
            }
            catch (MsalException ex)
            {
                ResultText.Text = $"Error signing-out user: {ex.Message}";
            }
        }
    }
    

    Metoden SignOutButton_Click rensar cachen för alla konton och alla motsvarande åtkomsttoken. Nästa gång användaren försöker logga in måste de göra det interaktivt.

  5. Lägg till följande kod i metoden DisplayBasicTokenInfo. Den här metoden visar grundläggande information om token.

    private void DisplayBasicTokenInfo(AuthenticationResult authResult)
    {
        TokenInfoText.Text = "";
        if (authResult != null)
        {
            TokenInfoText.Text += $"Username: {authResult.Account.Username}" + Environment.NewLine;
            TokenInfoText.Text += $"{authResult.Account.HomeAccountId}" + Environment.NewLine;
        }
    }
    

Lägga till kod i App.xaml.cs-filen

App.xaml är där du deklarerar resurser som används i hela appen. Det är startpunkten för din app. App.xaml.cs är koden bakom filen för App.xaml. App.xaml.cs definierar även startfönstret för ditt program.

Öppna filen App.xaml.cs i appens rotmapp och lägg sedan till följande kod i den.

using System.Windows;
using System.Reflection;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;

namespace sign_in_dotnet_wpf
{
    public partial class App : Application
    {
        static App()
        {
            CreateApplication();
        }

        public static void CreateApplication()
        {
            var assembly = Assembly.GetExecutingAssembly();
            using var stream = assembly.GetManifestResourceStream("sign_in_dotnet_wpf.appsettings.json");
            AppConfiguration = new ConfigurationBuilder()
                .AddJsonStream(stream)
                .Build();

            AzureAdConfig azureADConfig = AppConfiguration.GetSection("AzureAd").Get<AzureAdConfig>();

            var builder = PublicClientApplicationBuilder.Create(azureADConfig.ClientId)
                .WithAuthority(azureADConfig.Authority)
                .WithDefaultRedirectUri();

            _clientApp = builder.Build();
            TokenCacheHelper.EnableSerialization(_clientApp.UserTokenCache);
        }

        private static IPublicClientApplication _clientApp;
        private static IConfiguration AppConfiguration;
        public static IPublicClientApplication PublicClientApp { get { return _clientApp; } }
    }
}

I det här steget läser du in filen appsettings.json . Konfigurationsverktyget hjälper dig att läsa appkonfigurationerna som definierats i filen appsettings.json . Du definierar även WPF-appen som en offentlig klientapp eftersom det är en skrivbordsapp. Metoden TokenCacheHelper.EnableSerialization aktiverar tokencachens serialisering.

Kör appen

Kör appen och logga in för att testa programmet

  1. I terminalen går du till rotmappen för WPF-appen och kör appen genom att köra kommandot dotnet run i terminalen.

  2. När du har lanserat exemplet bör du se ett fönster med en inloggningsknapp . Välj knappen Logga in.

    Skärmbild av inloggningsskärmen för ett WPF-skrivbordsprogram.

  3. På inloggningssidan anger du ditt kontos e-postadress. Om du inte har något konto väljer du Inget konto? Skapa en, som startar registreringsflödet. Följ det här flödet för att skapa ett nytt konto och logga in.

  4. När du har loggat in visas en skärm som visar lyckad inloggning och grundläggande information om ditt användarkonto som lagras i den hämtade token. Grundläggande information visas i avsnittet Tokeninformation på inloggningsskärmen

Se även