Windows 앱 간에 인증서 공유
사용자 ID 및 암호 조합 이외의 보안 인증이 필요한 Windows 앱은 인증에 인증서를 사용할 수 있습니다. 인증서 인증은 사용자를 인증할 때 높은 수준의 신뢰를 제공합니다. 경우에 따라, 서비스 그룹은 여러 앱에 대해 사용자를 인증하려고 합니다. 이 문서에서는 동일한 인증서를 사용하여 여러 Windows 앱을 인증하는 방법과 사용자가 보안 웹 서비스에 액세스하기 위해 제공된 인증서를 가져오는 방법을 제공하는 방법을 보여 줍니다.
앱은 인증서를 사용하여 웹 서비스에 인증할 수 있으며, 여러 앱이 인증서 저장소의 단일 인증서를 사용하여 동일한 사용자를 인증하는 것이 가능합니다. 인증서가 저장소에 없는 경우 PFX 파일에서 인증서를 불러오는 코드를 앱에 추가할 수 있습니다. 이 빠른 시작의 클라이언트 앱은 WinUI 앱이며 웹 서비스는 ASP.NET Core 웹 API입니다.
팁
Microsoft Copilot 는 Windows 앱 또는 ASP.NET Core 웹 API 작성을 시작하는 방법에 대한 질문이 있는 경우 유용한 리소스입니다. Copilot를 사용하면 코드를 작성하고, 예제를 찾고, 보안 앱을 만들기 위한 모범 사례에 대해 자세히 알아볼 수 있습니다.
필수 조건
- ASP.NET 및 웹 개발 및 Windows 애플리케이션 개발 워크로드가 설치된 Visual Studio.
- WinUI 앱에서 WinRT(Windows 런타임) API를 사용하는 최신 Windows SDK(소프트웨어 개발 키트)입니다.
- 자체 서명된 인증서를 사용하기 위한 PowerShell 입니다.
보안 웹 서비스를 생성하고 게시하세요
Microsoft Visual Studio를 열고 시작 화면에서 새 프로젝트 만들기를 선택합니다.
새 프로젝트 만들기 대화 상자의 프로젝트 유형 선택 드롭다운 목록에서 API 를 선택하여 사용 가능한 프로젝트 템플릿을 필터링합니다.
ASP.NET Core Web API 템플릿을 선택하고 다음을 선택합니다.
애플리케이션 이름을 "FirstContosoBank"로 지정하고 다음을 선택합니다.
.NET 8.0 이상을 프레임워크로 선택하고, 인증 유형을 없음으로 설정하고, HTTPS에 대한 구성을 선택 취소하고, OpenAPI 지원 사용 선택을 취소하고, 최상위 문 및 컨트롤러 사용 안 을 선택하고, 만들기를 선택합니다.
Controllers 폴더에서 WeatherForecastController.cs 파일을 마우스 오른쪽 단추로 클릭하고 이름 바꾸기를 선택합니다. 이름을 BankController.cs 변경하고 Visual Studio에서 클래스와 클래스에 대한 모든 참조의 이름을 바꾸도록 합니다.
launchSettings.json 파일에서 값을 사용하는 세 가지 구성 모두에 대해 "launchUrl"의 값을 "weatherforecast"에서 "bank"로 변경합니다.
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"; }
NuGet 패키지 관리자 열고 안정적인 최신 버전의 Microsoft.AspNetCore.Authentication.Certificate 패키지를 검색하여 설치합니다. 이 패키지는 ASP.NET Core에서 인증서 인증을 위한 미들웨어를 제공합니다.
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; } } }
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
반환되는 오류 메시지와 함께 메서드를 호출합니다.웹 서비스를 시작하려면 프로젝트 디버깅을 시작합니다. SSL 인증서를 신뢰하고 설치하는 방법에 대한 메시지를 받을 수 있습니다. 인증서를 신뢰하고 프로젝트 디버깅을 계속하려면 이러한 각 메시지에 대해 예를 클릭합니다.
웹 서비스는 .에서
https://localhost:7072/bank
사용할 수 있습니다. 웹 브라우저를 열고 웹 주소를 입력하여 웹 서비스를 테스트할 수 있습니다. 생성된 일기 예보 데이터가 JSON 형식으로 표시됩니다. 클라이언트 앱을 만드는 동안 웹 서비스를 계속 실행합니다.
ASP.NET Core 컨트롤러 기반 웹 API 작업에 대한 자세한 내용은 ASP.NET Core를 사용하여 웹 API 만들기를 참조 하세요.
인증서 인증을 사용하는 WinUI 앱 만들기
이제 하나 이상의 보안 웹 서비스가 있으니 앱은 인증서를 사용하여 해당 웹 서비스에 인증할 수 있습니다. WinRT API에서 HttpClient 개체를 사용하여 인증된 웹 서비스에 요청하면 초기 요청에 클라이언트 인증서가 포함되지 않습니다. 인증된 웹 서비스는 클라이언트 인증 요청으로 응답합니다. 이 경우 Windows 클라이언트는 인증서 저장소에서 사용 가능한 클라이언트 인증서를 자동으로 쿼리합니다. 사용자는 이러한 인증서 중 선택하여 웹 서비스 인증이 가능합니다. 일부 인증서는 암호로 보호되므로 사용자에게 인증서의 암호를 입력하는 방법을 제공해야 할 것입니다.
참고 항목
인증서를 관리하기 위한 Windows 앱 SDK API는 아직 없습니다. WinRT API를 사용하여 앱에서 인증서를 관리해야 합니다. 또한 WinRT 스토리지 API를 사용하여 PFX 파일에서 인증서를 가져올 예정입니다. 많은 WinRT API는 WinUI 앱을 포함하여 패키지 ID가 있는 모든 Windows 앱에서 사용할 수 있습니다.
구현할 HTTP 클라이언트 코드는 .를 사용합니다. NET의 HttpClient입니다. WinRT API에 포함된 HttpClient는 클라이언트 인증서를 지원하지 않습니다.
클라이언트 인증서가 없는 경우 사용자는 인증서 저장소에 인증서를 추가해야 합니다. 사용자가 클라이언트 인증서가 포함된 PFX 파일을 선택한 다음 해당 인증서를 클라이언트 인증서 저장소로 가져오는 것을 활성화 하는 코드를 앱에 포함할 수 있습니다.
팁
PowerShell cmdlet New-SelfSignedCertificate 및 Export-PfxCertificate를 사용하여 자체 서명된 인증서를 만들고 이 빠른 시작에서 사용할 PFX 파일로 내보낼 수 있습니다. 자세한 내용은 New-SelfSignedCertificate 및 Export-PfxCertificate를 참조하세요.
인증서를 생성할 때 유효성 검사를 위해 웹 서비스에서 사용할 인증서의 지문을 저장해야 합니다.
Visual Studio를 열고 시작 페이지에서 새 WinUI 프로젝트를 만듭니다. 새 프로젝트의 이름을 "FirstContosoBankApp" 으로 설정하세요. 만들기를 클릭하여 새 프로젝트를 만듭니다.
MainWindow.xaml 파일에서 다음 XAML을 Grid 요소에 추가하여 기존 StackPanel 요소와 해당 내용을 바꿉니다. 이 XAML에는 불러올 PFX 파일을 둘러보는 버튼, 암호로 보호된 PFX 파일의 암호를 입력할 텍스트 상자, 선택한 PFX 파일을 가져오는 버튼, 보안 웹 서비스에 로그인하는 버튼 및 현재 작업의 상태를 표시하는 텍스트 블록이 포함됩니다.
<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"/>
MainWindow 변경 내용을 저장합니다.
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;
MainWindow.xaml.cs 파일에서 MainWindow 클래스에 다음 변수를 추가합니다. "FirstContosoBank" 웹 서비스의 보안 로그인 서비스 엔드포인트에 대한 주소와 인증서 저장소로 가져올 PFX 인증서를 보유하는 전역 변수를 지정합니다.
<server-name>
localhost:7072
API 프로젝트의 launchSettings.json 파일의 "https" 구성에 지정된 포트를 업데이트합니다.private Uri requestUri = new Uri("https://<server-name>/bank/login"); private string pfxCert = null;
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(); }
다음으로, 단추에 대해 다음 클릭 처리기를 추가하여 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(); }
Package.appxmanifest 파일을 열고 기능 탭에 다음 기능을 추가합니다.
- EnterpriseAuthentication
- SharedUserCertificates
앱을 실행하고 보안 웹 서비스에 로그인하고 PFX 파일을 로컬 인증서 저장소로 불러오세요.
이런 단계들을 통해 동일한 사용자 인증서를 사용하여 같은 혹은 다른 보안 웹 서비스에 액세스하는 여러 앱들을 만들 수 있습니다.
관련 콘텐츠
Windows developer