共用方式為


在 Windows 應用程式之間共享憑證

除了使用者標識碼和密碼組合之外,需要安全驗證的 Windows 應用程式可以使用憑證進行驗證。 驗證使用者時,憑證驗證可提供高度的信任。 在某些情況下,服務群組需要驗證多個應用程式的使用者。 本文說明如何使用同一憑證驗證多個 Windows 應用程式,以及如何提供使用者將access的憑證匯入安全網路服務的方法。

應用程式可以使用憑證向 Web 服務進行驗證,而多個應用程式可以使用憑證儲存的單一憑證來驗證相同的使用者。 如果商店中不存在憑證,您可以將程式代碼新增至您的應用程式,以從 PFX 檔案匯入憑證。 這個快速入門中的客戶端應用程式是 WinUI 3 應用程式,而網頁服務則是 ASP.NET Core 網頁 API。

提示

Microsoft Copilot 是一個很棒的資源,當你對於開始開發 Windows 應用程式或 ASP.NET Core 網頁 API 感到疑惑時。 Copilot 可協助您撰寫程式代碼、尋找範例,以及深入瞭解建立安全應用程式的最佳做法。

必要條件

建立並發佈安全的 Web 服務

  1. 打開Microsoft Visual Studio,從開始畫面選擇建立一個新的project

  2. 在「建立新專案」對話框中,在「選擇專案類型」下拉清單中選擇「API」,以篩選可用的專案範本。

  3. 選擇 ASP.NET Core Web API 範本,並選擇 Next

  4. 將應用程式命名為 「FirstContosoBank」,然後選取 [ 下一步]。

  5. 選擇 .NET 8.0 或更新版本,作為 Framework,將 Authentication Type 設為 None,確保勾選 Configure for HTTPS,取消勾選 Enable OpenAPI support, 請檢查 請勿使用頂層語句 以及 使用 controllers,並選擇 Create

    Visual Studio 建立 ASP.NET Core Web API 專案新細節的截圖

  6. 以滑鼠右鍵按兩下 Controllers 資料夾中WeatherForecastController.cs檔案,然後選取 [重新命名]。 將名稱改為 BankController.cs,並讓Visual Studio重新命名類別及所有對該類別的引用。

  7. launchSettings.json 檔案中,將 “launchUrl” 的值從 “weatherforecast” 變更為 “bank”,並應用於所有使用該值的三個組態中。

  8. BankController.cs 檔案中,新增下列 「Login」 方法。

    [HttpGet]
    [Route("login")]
    public string Login()
    {
        // Return any value you like here.
        // The client is just looking for a 200 OK response.
        return "true";
    }
    
  9. 打開 NuGet 套件管理器,然後搜尋並安裝最新穩定版本的 Microsoft.AspNetCore.Authentication.Certificate 套件。 此套件提供 ASP.NET Core 憑證驗證的中介軟體。

  10. 在project中新增一個名為 SecureCertificateValidationService 的類別。 將下列程式代碼新增至 類別,以設定憑證驗證中間件。

    using System.Security.Cryptography.X509Certificates;
    
    public class SecureCertificateValidationService
    {
        public bool ValidateCertificate(X509Certificate2 clientCertificate)
        {
            // Values are hard-coded for this example.
            // You should load your valid thumbprints from a secure location.
            string[] allowedThumbprints = { "YOUR_CERTIFICATE_THUMBPRINT_1", "YOUR_CERTIFICATE_THUMBPRINT_2" };
            if (allowedThumbprints.Contains(clientCertificate.Thumbprint))
            {
                return true;
            }
        }
    }
    
  11. 開啟 Program.cs ,並以下列程式代碼取代 Main 方法中的程式代碼:

    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
    
        // Add our certificate validation service to the DI container.
        builder.Services.AddTransient<SecureCertificateValidationService>();
    
        builder.Services.Configure<KestrelServerOptions>(options =>
        {
            // Configure Kestrel to require a client certificate.
            options.ConfigureHttpsDefaults(options =>
            {
                options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
                options.AllowAnyClientCertificate();
            });
        });
    
        builder.Services.AddControllers();
    
        // Add certificate authentication middleware.
        builder.Services.AddAuthentication(
        CertificateAuthenticationDefaults.AuthenticationScheme)
           .AddCertificate(options =>
        {
            options.AllowedCertificateTypes = CertificateTypes.SelfSigned;
            options.Events = new CertificateAuthenticationEvents
            {
                // Validate the certificate with the validation service.
                OnCertificateValidated = context =>
                {
                    var validationService = context.HttpContext.RequestServices.GetService<SecureCertificateValidationService>();
    
                    if (validationService.ValidateCertificate(context.ClientCertificate))
                    {
                        context.Success();
                    }
                    else
                    {
                        context.Fail("Invalid certificate");
                    }
    
                    return Task.CompletedTask;
                },
                OnAuthenticationFailed = context =>
                {
                    context.Fail("Invalid certificate");
                    return Task.CompletedTask;
                }
            };
         });
    
         var app = builder.Build();
    
         // Add authentication/authorization middleware.
         app.UseHttpsRedirection();
         app.UseAuthentication();
         app.UseAuthorization();
    
         app.MapControllers();
         app.Run();
     }
    

    上述程式代碼會將 Kestrel 伺服器設定為需要用戶端憑證,並將憑證驗證中間件新增至應用程式。 中間件會使用 SecureCertificateValidationService 類別來驗證客戶端憑證。 當憑證經過驗證時會呼叫OnCertificateValidated事件。 如果憑證有效,事件會呼叫 Success 方法。 如果憑證無效,事件會使用錯誤訊息呼叫 Fail 方法,而錯誤訊息將會傳回給用戶端。

  12. 開始除錯專案以便啟動網路服務。 您可能會收到有關信任和安裝 SSL 憑證的訊息。 按Yes 以信任憑證並繼續偵錯專案。

    對話框的螢幕快照,詢問使用者是否想要信任憑證

    Windows 對話框的螢幕快照,詢問使用者是否要安裝憑證

  13. Web 服務將可用於 https://localhost:7072/bank。 您可以開啟網頁瀏覽器並輸入網址來測試Web服務。 您會看到產生的天氣預報數據格式化為 JSON。 建立用戶端應用程式時,讓 Web 服務保持執行。

欲了解更多關於使用 ASP.NET Core控制器網頁 API 的資訊,請參見 Create a web API with ASP.NET Core

建立使用憑證驗證的 WinUI 應用程式

現在您已經擁有一個或多個安全的 Web 服務,您的應用程式可以使用憑證對這些 Web 服務進行驗證。 當您使用來自 WinRT API 的 HttpClient 物件向已驗證的 Web 服務提出要求時,初始要求將不會包含客戶端憑證。 經過驗證的 Web 服務將回應用戶端驗證要求。 發生這種情況時,Windows 用戶端將自動查詢憑證儲存以取得可用的用戶端憑證。 您的使用者可以從這些憑證中進行選擇,以對 Web 服務進行驗證。 有些憑證受密碼保護,因此您需要為使用者提供輸入憑證密碼的方法。

注意

目前還沒有 Windows App SDK API 來管理憑證。 您必須使用 WinRT API 來管理應用程式中的憑證。 我們也會使用 WinRT storage API 從 PFX 檔案匯入憑證。 許多 WinRT API 可被任何具有套件身份的 Windows app 使用,包括 WinUI 應用程式。

我們將實作的 HTTP 用戶端程式碼使用 .NET 的 HttpClient。 WinRT API 中包含的 HttpClient 不支援客戶端憑證。

如果沒有可用的用戶端憑證,則使用者需要將憑證新增至憑證儲存。 您可以在應用程式中包含程式碼,使使用者能夠選擇包含用戶端憑證的 PFX 檔案,然後將該憑證匯入到用戶端憑證存放區中。

提示

您可以使用 PowerShell Cmdlet New-SelfSignedCertificateExport-PfxCertificate 來建立自我簽署憑證,並將其導出至 PFX 檔案以搭配本快速入門使用。 如需詳細資訊,請參閱 New-SelfSignedCertificateExport-PfxCertificate

請注意,產生憑證時,您應該儲存憑證的指紋,以用於Web服務進行驗證。

  1. 打開 Visual Studio,從開始頁面建立一個新的 WinUI project。 將新的專案命名為「FirstContosoBankApp」。 點擊 Create 建立新的project。

  2. MainWindow.xaml 檔案中,將下列 XAML 新增至 Grid 元素,並取代現有的 StackPanel 元素及其內容。 此XAML 包括一個用於瀏覽要匯入的PFX 檔案的按鈕、一個用於為受密碼保護的PFX 檔案輸入密碼的文字方塊、一個用於匯入所選PFX 檔案的按鈕、一個用於登入安全 Web 服務的按鈕,以及顯示目前動作狀態的文字區塊。

    <Button x:Name="Import" Content="Import Certificate (PFX file)" HorizontalAlignment="Left" Margin="352,305,0,0" VerticalAlignment="Top" Height="77" Width="260" Click="Import_Click" FontSize="16"/>
    <Button x:Name="Login" Content="Login" HorizontalAlignment="Left" Margin="611,305,0,0" VerticalAlignment="Top" Height="75" Width="240" Click="Login_Click" FontSize="16"/>
    <TextBlock x:Name="Result" HorizontalAlignment="Left" Margin="355,398,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="153" Width="560"/>
    <PasswordBox x:Name="PfxPassword" HorizontalAlignment="Left" Margin="483,271,0,0" VerticalAlignment="Top" Width="229"/>
    <TextBlock HorizontalAlignment="Left" Margin="355,271,0,0" TextWrapping="Wrap" Text="PFX password" VerticalAlignment="Top" FontSize="18" Height="32" Width="123"/>
    <Button x:Name="Browse" Content="Browse for PFX file" HorizontalAlignment="Left" Margin="352,189,0,0" VerticalAlignment="Top" Click="Browse_Click" Width="499" Height="68" FontSize="16"/>
    <TextBlock HorizontalAlignment="Left" Margin="717,271,0,0" TextWrapping="Wrap" Text="(Optional)" VerticalAlignment="Top" Height="32" Width="83" FontSize="16"/>
    
  3. 儲存 MainWindow 變更。

  4. 開啟 MainWindow.xaml.cs 檔案,然後新增下列 using 語句。

    using System;
    using System.Security.Cryptography.X509Certificates;
    using System.Diagnostics;
    using System.Net.Http;
    using System.Net;
    using System.Text;
    using Microsoft.UI.Xaml;
    using Windows.Security.Cryptography.Certificates;
    using Windows.Storage.Pickers;
    using Windows.Storage;
    using Windows.Storage.Streams;
    
  5. 在 MainWindow.xaml.cs 檔案中,將下列變數新增至 MainWindow 類別。 他們會指定 「FirstContosoBank」Web 服務之安全 登入 服務端點的位址,以及保存要匯入證書存儲之 PFX 憑證的全域變數。 將 <server-name> 更新為 localhost:7072 或你 API project launchSettings.json 檔案中「https」設定中指定的埠。

    private Uri requestUri = new Uri("https://<server-name>/bank/login");
    private string pfxCert = null;
    
  6. MainWindow.xaml.cs 檔案中,新增以下點擊事件處理程序,並加入方法以存取安全的網路服務。

    private void Login_Click(object sender, RoutedEventArgs e)
    {
        MakeHttpsCall();
    }
    
    private async void MakeHttpsCall()
    {
        var result = new StringBuilder("Login ");
    
        // Load the certificate
        var certificate = new X509Certificate2(Convert.FromBase64String(pfxCert),
                                               PfxPassword.Password);
    
        // Create HttpClientHandler and add the certificate
        var handler = new HttpClientHandler();
        handler.ClientCertificates.Add(certificate);
        handler.ClientCertificateOptions = ClientCertificateOption.Automatic;
    
        // Create HttpClient with the handler
        var client = new HttpClient(handler);
    
        try
        {
            // Make a request
            var response = await client.GetAsync(requestUri);
    
            if (response.StatusCode == HttpStatusCode.OK)
            {
                result.Append("successful");
            }
            else
            {
                result = result.Append("failed with ");
                result = result.Append(response.StatusCode);
            }
        }
        catch (Exception ex)
        {
            result = result.Append("failed with ");
            result = result.Append(ex.Message);
        }
    
        Result.Text = result.ToString();
    }
    
  7. 接下來,為按鈕新增以下點擊處理程式,以瀏覽 PFX 檔案,以及將選擇的 PFX 檔案匯入到憑證存儲區。

    private async void Import_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            Result.Text = "Importing selected certificate into user certificate store....";
            await CertificateEnrollmentManager.UserCertificateEnrollmentManager.ImportPfxDataAsync(
                  pfxCert,
                  PfxPassword.Password,
                  ExportOption.Exportable,
                  KeyProtectionLevel.NoConsent,
                  InstallOptions.DeleteExpired,
                  "Import Pfx");
    
            Result.Text = "Certificate import succeeded";
        }
        catch (Exception ex)
        {
            Result.Text = "Certificate import failed with " + ex.Message;
        }
    }
    
    private async void Browse_Click(object sender, RoutedEventArgs e)
    {
        var result = new StringBuilder("Pfx file selection ");
        var pfxFilePicker = new FileOpenPicker();
        IntPtr hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
        WinRT.Interop.InitializeWithWindow.Initialize(pfxFilePicker, hwnd);
        pfxFilePicker.FileTypeFilter.Add(".pfx");
        pfxFilePicker.CommitButtonText = "Open";
        try
        {
            StorageFile pfxFile = await pfxFilePicker.PickSingleFileAsync();
            if (pfxFile != null)
            {
                IBuffer buffer = await FileIO.ReadBufferAsync(pfxFile);
                using (DataReader dataReader = DataReader.FromBuffer(buffer))
                {
                    byte[] bytes = new byte[buffer.Length];
                    dataReader.ReadBytes(bytes);
                    pfxCert = System.Convert.ToBase64String(bytes);
                    PfxPassword.Password = string.Empty;
                    result.Append("succeeded");
                }
            }
            else
            {
                result.Append("failed");
            }
        }
        catch (Exception ex)
        {
            result.Append("failed with ");
            result.Append(ex.Message); ;
        }
    
        Result.Text = result.ToString();
    }
    
  8. 開啟 Package.appxmanifest 檔案,並將下列功能新增至 [功能] 索引標籤。

    • 企業認證
    • SharedUserCertificates
  9. 執行您的應用程式並登入您的安全 Web 服務,並將 PFX 檔案匯入本機憑證存放區。

    WinUI 應用程式的螢幕快照,其中包含用來流覽 PFX 檔案、匯入憑證,以及登入安全 Web 服務的按鈕

你可以利用這些步驟建立多個應用程式,使用相同的使用者憑證來access相同或不同的安全網路服務。

Windows Hello

安全性和身分識別

使用 ASP.NET Core 建立網頁 API