ASP.NET Core 데이터 보호 구성
데이터 보호 시스템은 초기화되면 운영 환경에 따라 기본 설정을 적용합니다. 이러한 설정은 단일 컴퓨터에서 실행되는 앱에 적합합니다. 그러나 개발자가 기본 설정을 변경해야 하는 경우가 있습니다.
- 앱이 여러 머신에 분산되어 있습니다.
- 규정 준수가 필요합니다.
데이터 보호 시스템은 이러한 시나리오를 위한 여러 구성 API를 제공합니다.
Warning
데이터 보호 키 링은 구성 파일과 마찬가지로 적절한 권한을 사용하여 보호해야 합니다. 키를 rest암호화하도록 선택할 수 있지만 사이버 공격이 새 키를 만드는 것을 방지하지는 않습니다. 따라서 앱의 보안이 영향을 받게 됩니다. 데이터 보호로 구성된 스토리지 위치는 구성 파일을 보호하는 방식과 비슷하게 앱 자체에 대한 액세스 권한이 제한되어야 합니다. 예를 들어, 키 링을 디스크에 저장하는 경우에는 파일 시스템 권한을 사용하세요. 웹앱이 identity 실행되는 아래에 해당 디렉터리에 대한 읽기, 쓰기 및 만들기 액세스 권한만 있는지 확인합니다. Azure Blob Storage를 사용하는 경우, 웹앱만 Blob 저장소의 항목을 읽고, 쓰고, 만들 수 있어야 합니다.
확장 메서드 AddDataProtection IDataProtectionBuilder는 . IDataProtectionBuilder
는 함께 연결하여 데이터 보호 옵션을 구성할 수 있는 확장 메서드를 노출합니다.
참고 항목
이 문서는 Docker 컨테이너 내에서 실행되는 앱에 대해 작성되었습니다. Docker 컨테이너에서 앱의 경로는 항상 같으므로 동일한 애플리케이션 판별자입니다. 여러 환경(예: 로컬 및 배포됨)에서 실행해야 하는 앱은 환경에 대한 기본 애플리케이션 판별자를 설정해야 합니다. 여러 환경에서 앱을 실행하는 것은 이 문서의 범위를 벗어납니다.
이 문서에서 사용하는 데이터 보호 확장에는 다음과 같은 NuGet 패키지가 필요합니다.
ProtectKeysWithAzureKeyVault
CLI를 사용하여 Azure에 로그인합니다. 예를 들면 다음과 같습니다.
az login
Azure Key Vault를 사용하여 키를 관리하려면 다음을 사용하여 시스템을 ProtectKeysWithAzureKeyVault 구성합니다Program.cs
. blobUriWithSasToken
은 키 파일이 저장되는 전체 URI입니다. URI는 쿼리 문자열 매개 변수로 SAS 토큰을 포함해야 합니다.
builder.Services.AddDataProtection()
.PersistKeysToAzureBlobStorage(new Uri("<blobUriWithSasToken>"))
.ProtectKeysWithAzureKeyVault(new Uri("<keyIdentifier>"), new DefaultAzureCredential());
앱이 KeyVault와 통신하고 권한을 부여하려면 AzureIdentity 패키지를 추가해야 합니다.
키 링 스토리지 위치(예: PersistKeysToAzureBlobStorage)를 설정합니다. 호출 ProtectKeysWithAzureKeyVault
은 키 링 스토리지 위치를 포함하여 자동 데이터 보호 설정을 사용하지 않도록 설정하는 것을 구현 IXmlEncryptor 하기 때문에 위치를 설정해야 합니다. 위 예제에서는 Azure Blob Storage를 사용하여 키 링을 저장합니다. 자세한 내용은 키 스토리지 공급자: Azure Storage를 참조하세요. PersistKeysToFileSystem을 사용하여 키 링을 로컬에 저장할 수도 있습니다.
keyIdentifier
는 키 암호화에 사용되는 키 자격 증명 모음 키 식별자입니다. 예를 들어, contosokeyvault
에서 키 자격 증명 모음 dataprotection
에 만들어진 키는 키 식별자 https://contosokeyvault.vault.azure.net/keys/dataprotection/
을 갖습니다. 앱에 키 자격 증명 모음에 대한 Get, Unwrap Key 및 Wrap Key 권한을 제공합니다.
ProtectKeysWithAzureKeyVault
오버로드:
- ProtectKeysWithAzureKeyVault(IDataProtectionBuilder, Uri, TokenCredential) 는 keyIdentifier Uri 및 tokenCredential을 사용하여 데이터 보호 시스템이 키 자격 증명 모음을 사용할 수 있도록 합니다.
- ProtectKeysWithAzureKeyVault(IDataProtectionBuilder, String, IKeyEncryptionKeyResolver) 는 keyIdentifier 문자열 및 IKeyEncryptionKeyResolver를 사용하여 데이터 보호 시스템에서 키 자격 증명 모음을 사용할 수 있도록 허용합니다.
앱에서 이전 Azure 패키지(Microsoft.AspNetCore.DataProtection.AzureStorage 및 Microsoft.AspNetCore.DataProtection.AzureKeyVault)를 사용하는 경우 이러한 참조를 제거하고 Azure.Extensions.AspNetCore.DataProtection.Blobs 및 Azure.Extensions.AspNetCore.DataProtection.Keys로 업그레이드하는 것이 좋습니다. 이러한 패키지는 새 업데이트가 제공되는 위치이며 이전 패키지의 몇 가지 키 보안 및 안정성 문제를 해결합니다.
builder.Services.AddDataProtection()
// This blob must already exist before the application is run
.PersistKeysToAzureBlobStorage("<storageAccountConnectionString", "<containerName>", "<blobName>")
// Removing this line below for an initial run will ensure the file is created correctly
.ProtectKeysWithAzureKeyVault(new Uri("<keyIdentifier>"), new DefaultAzureCredential());
PersistKeysToFileSystem
%LOCALAPPDATA% 기본 위치 대신 UNC 공유에 키를 저장하려면 다음을 사용하여 시스템을 PersistKeysToFileSystem구성합니다.
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"));
Warning
키 지속성 위치를 변경하면 DPAPI가 적절한 암호화 메커니즘인지 여부를 알 수 없으므로 시스템은 더 이상 키를 자동으로 암호화 rest하지 않습니다.
PersistKeysToDbContext
EntityFramework를 사용하는 데이터베이스에 키를 저장하려면 Microsoft.AspNetCore.DataProtection.EntityFrameworkCore 패키지를 사용하여 시스템을 구성합니다.
builder.Services.AddDataProtection()
.PersistKeysToDbContext<SampleDbContext>();
위 코드는 구성된 데이터베이스에 키를 저장합니다. 사용되는 데이터베이스 컨텍스트는 IDataProtectionKeyContext
를 구현해야 합니다. IDataProtectionKeyContext
는 DataProtectionKeys
속성을 노출합니다.
public DbSet<DataProtectionKey> DataProtectionKeys { get; set; } = null!;
이 속성은 키가 저장되는 테이블을 나타냅니다. 테이블을 수동으로 또는 DbContext
마이그레이션을 사용하여 만듭니다. 자세한 내용은 DataProtectionKey를 참조하세요.
ProtectKeysWith*
ProtectKeysWith* 구성 API를 호출하여 키를 rest 보호하도록 시스템을 구성할 수 있습니다. UNC 공유에 키를 저장하고 특정 X.509 인증서를 사용하여 해당 키를 rest 암호화하는 아래 예제를 살펴보세요.
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
.ProtectKeysWithCertificate(builder.Configuration["CertificateThumbprint"]);
파일에서 로드된 인증서와 같이 ProtectKeysWithCertificate에 X509Certificate2를 제공할 수 있습니다.
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
.ProtectKeysWithCertificate(
new X509Certificate2("certificate.pfx", builder.Configuration["CertificatePassword"]));
기본 제공 키 암호화 메커니즘에 대한 자세한 예제 및 논의는 키 암호화 At Rest 를 참조하세요.
UnprotectKeysWithAnyCertificate
다음을 사용하여 인증서 배열 X509Certificate2 을 사용하여 인증서를 회전하고 키를 rest 해독할 수 있습니다.UnprotectKeysWithAnyCertificate
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
.ProtectKeysWithCertificate(
new X509Certificate2("certificate.pfx", builder.Configuration["CertificatePassword"]))
.UnprotectKeysWithAnyCertificate(
new X509Certificate2("certificate_1.pfx", builder.Configuration["CertificatePassword_1"]),
new X509Certificate2("certificate_2.pfx", builder.Configuration["CertificatePassword_2"]));
SetDefaultKeyLifetime
기본 90일 대신 14일의 키 수명을 사용하도록 시스템을 구성하려면 다음을 사용합니다 SetDefaultKeyLifetime.
builder.Services.AddDataProtection()
.SetDefaultKeyLifetime(TimeSpan.FromDays(14));
SetApplicationName
기본적으로 데이터 보호 시스템은 앱들 간에 동일한 물리적 키 리포지토리를 공유하는 경우에도 콘텐츠 루트 경로를 기반으로 앱을 서로 격리합니다. 이러한 격리에 따라 앱이 다른 앱의 보호된 페이로드를 알 수 없게 됩니다.
앱 간에 보호된 페이로드를 공유하려면:
- 각 앱에서 SetApplicationName을 같은 값으로 구성합니다.
- 각 앱에서 동일한 버전의 데이터 보호 API 스택을 사용합니다. 앱의 프로젝트 파일에서 다음 중 하나를 수행합니다.
- Microsoft.AspNetCore.App metapackage를 통해 동일한 공유 프레임워크 버전을 참조합니다.
- 동일한 데이터 보호 패키지 버전을 참조합니다.
builder.Services.AddDataProtection()
.SetApplicationName("<sharedApplicationName>");
SetApplicationName이 DataProtectionOptions.ApplicationDiscriminator를 내부적으로 설정합니다. 문제 해결을 위해 프레임워크에 의해 판별자에 할당된 값은 WebApplication이 Program.cs
에 빌드된 후 배치된 다음 코드로 기록할 수 있습니다.
var discriminator = app.Services.GetRequiredService<IOptions<DataProtectionOptions>>()
.Value.ApplicationDiscriminator;
app.Logger.LogInformation("ApplicationDiscriminator: {ApplicationDiscriminator}", discriminator);
판별자가 사용되는 방법에 대한 자세한 내용은 이 문서의 뒷부분에 나오는 다음 섹션을 참조하세요.
Warning
.NET 6에서는 WebApplicationBuilder가 콘텐츠 루트 경로를 정규화하여 DirectorySeparatorChar로 끝납니다. 예를 들어 Windows에서 콘텐츠 루트 경로가 \
로 끝나고 Linux에서는 /
로 끝납니다. 다른 호스트는 경로를 정규화하지 않습니다. HostBuilder 또는 WebHostBuilder에서 마이그레이션하는 대부분의 앱은 종료 DirectorySeparatorChar
가 없으므로 동일한 앱 이름을 공유하지 않습니다. 이 문제를 해결하려면 다음 코드와 같이 디렉터리 구분 기호 문자를 제거하고 앱 이름을 수동으로 설정합니다.
using Microsoft.AspNetCore.DataProtection;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
var trimmedContentRootPath = builder.Environment.ContentRootPath.TrimEnd(Path.DirectorySeparatorChar);
builder.Services.AddDataProtection()
.SetApplicationName(trimmedContentRootPath);
var app = builder.Build();
app.MapGet("/", () => Assembly.GetEntryAssembly()!.GetName().Name);
app.Run();
DisableAutomaticKeyGeneration
키의 만료가 가까워지면 앱이 자동으로 키를 롤링(새 키 만들기)하지 못하도록 해야 하는 시나리오가 있을 수 있습니다. 이 시나리오의 한 가지 예로 기본/보조 관계로 설정된 앱을 들 수 있습니다. 여기서 기본 앱은 키 관리 문제를 담당하고, 보조 앱은 키 링에 대한 읽기 전용 보기만 가능합니다. 보조 앱은 DisableAutomaticKeyGeneration을 사용하여 시스템을 구성함으로써 키 링을 읽기 전용으로 취급하도록 구성할 수 있습니다.
builder.Services.AddDataProtection()
.DisableAutomaticKeyGeneration();
애플리케이션별 격리
데이터 보호 시스템이 ASP.NET Core 호스트에 의해 제공되는 경우, 앱이 동일한 작업자 프로세스 계정에서 실행되고 동일한 마스터 키 링 자료를 사용하는 경우에도 자동으로 앱을 서로 격리합니다. 이는 System.Web의 <machineKey>
요소의 IsolateApps 한정자와 비슷합니다.
격리 메커니즘은 로컬 컴퓨터에 있는 각 앱을 고유한 테넌트로 간주하는 방식으로 작동합니다. 따라서 지정된 앱에 대해 루트된 IDataProtector에는 자동으로 앱 ID가 판별자(ApplicationDiscriminator)로 포함됩니다. 앱의 고유 ID는 앱의 실제 경로입니다.
- IIS에 호스트되는 앱의 경우, 고유 ID는 앱의 IIS 실제 경로입니다. 앱이 웹 팜 환경에 배포된 경우, 웹 팜에 있는 모든 머신에서 IIS 환경이 비슷하게 구성되었다는 가정하에 이 값은 일정합니다.
- Kestrel 서버에서 실행되는 자체 호스트 앱의 경우, 고유 ID는 디스크에 있는 앱의 실제 경로입니다.
고유 식별자는 재설정-개별 앱과 컴퓨터 자체의 재설정-이 이루어져도 지속되도록 설계되었습니다.
이 격리 메커니즘에서는 앱이 악성이 아니라고 가정합니다. 악성 앱은 동일한 작업자 프로세스 계정에서 실행되는 다른 앱에 언제든지 영향을 줄 수 있습니다. 앱이 상호 신뢰하지 않는 공유 호스트 환경에서는 호스트 공급자가 앱의 기본 키 리포지토리를 분리하는 등 앱 간에 OS 수준 격리를 유지하기 위한 조치를 취해야 합니다.
데이터 보호 시스템이 ASP.NET Core 호스트에 의해 제공되지 않은 경우(예를 들어, DataProtectionProvider
구체적인 형식을 통해 인스턴스화한 경우), 앱 격리가 기본적으로 사용하지 않도록 설정됩니다. 앱 격리가 사용하지 않도록 설정되면, 동일한 키 링 자료를 사용하는 모든 앱은 적절한 용도를 제공하는 한 페이로드를 공유할 수 있습니다. 이 환경에서 앱 격리를 제공하려면 구성 개체에서 SetApplicationName
메서드를 호출하고 각 앱에 고유한 이름을 제공합니다.
데이터 보호와 앱 격리
앱 격리 시 다음을 고려해야 합니다.
여러 앱이 동일한 키 리포지토리를 가리킨다면 그 의도는 앱이 동일한 마스터 키 자료를 공유하도록 하는 것입니다. 데이터 보호는 키 링을 공유하는 모든 앱이 해당 키 링에 있는 모든 항목에 액세스할 수 있다는 가정하에 개발됩니다. 애플리케이션 고유 식별자는 키 링이 제공하는 키에서 파생된 애플리케이션별 키를 격리하는 데 사용됩니다. 추가 격리를 적용하기 위해 Azure KeyVault가 제공하는 것과 같은 항목 수준 권한은 필요하지 않습니다. 항목 수준 권한을 시도하면 애플리케이션 오류가 발생합니다. 기본 제공 애플리케이션 격리를 사용하지 않으려면 개별적인 키 저장소 위치를 사용해야 하며 애플리케이션 간에 공유되지 않아야 합니다.
애플리케이션 판별자(ApplicationDiscriminator)는 여러 앱이 동일한 마스터 키 재질을 공유하도록 하되 암호화 페이로드는 서로 구분하는 용도로 사용됩니다. 앱이 다른 앱의 암호화 페이로드를 읽을 수 있으려면 동일한 애플리케이션 판별자를 가져야 합니다. 동일한 애플리케이션 판별자는
SetApplicationName
을 호출하여 설정할 수 있습니다.앱이 손상된 경우(예: RCE 공격에 의해) 해당 앱에 액세스할 수 있는 모든 마스터 키 자료도 보호rest 상태에 관계없이 손상된 것으로 간주되어야 합니다. 이는 두 앱이 동일한 리포지토리를 가리키는 경우, 서로 다른 앱 판별자를 사용하는 경우에도 하나의 앱의 손상되면 두 앱이 모두 손상된 것과 기능적으로 동일함을 의미합니다.
이 "기능적으로 두 절의 손상에 해당" 절은 두 앱이 키 보호를 rest위해 서로 다른 메커니즘을 사용하는 경우에도 유지됩니다. 이는 일반적으로 사용되는 구성이 아닙니다. 보호rest 메커니즘은 사이버 공격이 리포지토리에 대한 읽기 액세스 권한을 얻는 경우 보호를 제공하기 위한 것입니다. 리포지토리에 대한 쓰기 액세스 권한을 얻는 사이버 공격(앱 내에서 코드 실행 권한을 획득했기 때문일 수 있습니다)은 스토리지에 악성 키를 삽입할 수 있습니다. 데이터 보호 시스템은 의도적으로 키 리포지토리에 대한 쓰기 액세스 권한을 얻는 사이버 공격자에 대한 보호를 제공하지 않습니다.
앱이 다른 앱과 진정으로 격리된 상태로 유지되려면 서로 다른 키 리포지토리를 사용해야 합니다. 이는 자연스럽게 “격리된”에 대한 정의를 벗어나게 됩니다. 다른 앱의 데이터 저장소에 대한 읽기 및 쓰기 권한을 갖는 앱은 격리되지 않습니다.
UseCryptographicAlgorithms를 사용하여 알고리즘 변경
데이터 보호 스택을 통해 새로 생성된 키에서 사용하는 기본 알고리즘을 변경할 수 있습니다. 이 작업을 수행하는 가장 간단한 방법은 구성 콜백에서 호출 UseCryptographicAlgorithms 하는 것입니다.
builder.Services.AddDataProtection()
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});
기본 EncryptionAlgorithm은 AES-256-CBC이고, 기본 ValidationAlgorithm은 HMACSHA256입니다. 기본 정책은 시스템 관리자가 설정하거나 머신 전체에 적용되는 정책을 통해 설정할 수 있으며, UseCryptographicAlgorithms
를 명시적으로 호출하면 기본 정책이 재정의됩니다.
UseCryptographicAlgorithms
를 호출하면 미리 정의된 기본 제공 목록에서 원하는 알고리즘을 지정할 수 있습니다. 알고리즘의 구현에 대해서는 신경 쓸 필요가 없습니다. 위 시나리오에서 데이터 보호 시스템은 Windows에서 실행되는 경우 AES의 CNG 구현을 사용하려고 시도합니다. 그렇지 않으면 관리 System.Security.Cryptography.Aes 되는 클래스로 돌아갑니다.
호출 UseCustomCryptographicAlgorithms을 통해 구현을 수동으로 지정할 수 있습니다.
팁
알고리즘을 변경해도 키 링에 있는 기존 키는 영향을 받으며, 새로 생성되는 키만 영향을 받습니다.
사용자 지정 관리형 알고리즘 지정
사용자 지정 관리 알고리즘을 지정하려면 구현 형식을 ManagedAuthenticatedEncryptorConfiguration 가리키는 인스턴스를 만듭니다.
builder.Services.AddDataProtection()
.UseCustomCryptographicAlgorithms(new ManagedAuthenticatedEncryptorConfiguration
{
// A type that subclasses SymmetricAlgorithm
EncryptionAlgorithmType = typeof(Aes),
// Specified in bits
EncryptionAlgorithmKeySize = 256,
// A type that subclasses KeyedHashAlgorithm
ValidationAlgorithmType = typeof(HMACSHA256)
});
일반적으로 *Type 속성은 매개 변수 없는 퍼블릭 ctor를 통해 구체적이고 인스턴스화 가능한 SymmetricAlgorithm 및 KeyedHashAlgorithm의 구현을 가리켜야 합니다. 단, 시스템에서 편의를 위해 특수 사례로 취급하는 typeof(Aes)
와 같은 값도 있습니다.
참고 항목
SymmetricAlgorithm은 키 길이가 128비트 이상이고 블록 크기가 64비트 이상이어야 하며, PKCS #7 패딩을 사용하여 CBC 모드 암호화를 지원해야 합니다. KeyedHashAlgorithm은 다이제스트 크기가 >=128비트(128비트 이상)이어야 하며, 해시 알고리즘의 다이제스트 길이와 동일한 길이의 키를 지원해야 합니다. KeyedHashAlgorithm은 반드시 HMAC여야 할 필요는 없습니다.
사용자 지정 Windows CNG 알고리즘 지정
HMAC 유효성 검사와 함께 CBC 모드 암호화를 사용하여 사용자 지정 Windows CNG 알고리즘을 CngCbcAuthenticatedEncryptorConfiguration 지정하려면 알고리즘 정보를 포함하는 인스턴스를 만듭니다.
builder.Services.AddDataProtection()
.UseCustomCryptographicAlgorithms(new CngCbcAuthenticatedEncryptorConfiguration
{
// Passed to BCryptOpenAlgorithmProvider
EncryptionAlgorithm = "AES",
EncryptionAlgorithmProvider = null,
// Specified in bits
EncryptionAlgorithmKeySize = 256,
// Passed to BCryptOpenAlgorithmProvider
HashAlgorithm = "SHA256",
HashAlgorithmProvider = null
});
참고 항목
대칭 블록 암호 알고리즘은 키 길이가 >= 128비트(128비트 이상)이고 블록 크기가 >= 64비트(64비트 이상)이어야 하며, PKCS #7 패딩을 사용하여 CBC 모드 암호화를 지원해야 합니다. 해시 알고리즘은 다이제스트 크기가 >= 128비트(128비트 이상)이어야 하며, BCRYPT_ALG_HANDLE_HMAC_FLAG 플래그를 사용하여 열 수 있어야 합니다. *Provider 속성을 null로 설정하면 지정된 알고리즘에 대해 기본 공급자를 사용할 수 있습니다. 자세한 내용은 BCryptOpenAlgorithmProvider 설명서를 참조하세요.
유효성 검사와 함께 Galois/카운터 모드 암호화를 사용하여 사용자 지정 Windows CNG 알고리즘을 CngGcmAuthenticatedEncryptorConfiguration 지정하려면 알고리즘 정보가 포함된 인스턴스를 만듭니다.
builder.Services.AddDataProtection()
.UseCustomCryptographicAlgorithms(new CngGcmAuthenticatedEncryptorConfiguration
{
// Passed to BCryptOpenAlgorithmProvider
EncryptionAlgorithm = "AES",
EncryptionAlgorithmProvider = null,
// Specified in bits
EncryptionAlgorithmKeySize = 256
});
참고 항목
대칭 블록 암호 알고리즘은 키 길이가 >= 128비트(128비트 이상)이고 블록 크기가 정확히 128비트이어야 하며 GCM 암호화를 지원해야 합니다. 지정된 알고리즘에 EncryptionAlgorithmProvider 기본 공급자를 사용하도록 속성을 null로 설정할 수 있습니다. 자세한 내용은 BCryptOpenAlgorithmProvider 설명서를 참조하세요.
그 밖의 사용자 지정 알고리즘 지정
데이터 보호 시스템은 1급 API로 노출되지 않지만, 거의 모든 종류의 알고리즘을 지정할 수 있을 만큼 확장성이 뛰어납니다. 예를 들어, 하나의 HSM(하드웨어 보안 모듈) 내에 포함된 모든 키를 유지하고 핵심 암호화 및 암호 해독 루틴의 사용자 지정 구현을 제공할 수 있습니다. 자세한 내용은 핵심 암호화 확장성의 IAuthenticatedEncryptor를 참조하세요.
Docker 컨테이너를 호스트할 때 키 저장
Docker 컨테이너를 호스트할 때는 키를 다음 중 하나에 저장해야 합니다.
- 컨테이너의 수명을 초과하여 유지되는 Docker의 볼륨인 폴더(예: 공유 볼륨 또는 호스트 마운트된 볼륨)
- 외부 공급자(예: Azure Blob Storage(
ProtectKeysWithAzureKeyVault
섹션 참조) 또는 Redis)
Redis를 사용하여 키 저장
Redis 데이터 지속성을 지원하는 Redis 버전만 키를 저장하는 데 사용해야 합니다. Azure Blob 스토리지는 지속성을 지원하므로 키를 저장하는 데 사용할 수 있습니다. 자세한 내용은 해당 GitHub 이슈를 참조하세요.
DataProtection 로깅
문제 진단에 도움이 되도록 DataProtection의 Information
수준 로깅을 사용하도록 설정합니다. 다음 appsettings.json
파일은 DataProtection API의 정보 로깅을 사용하도록 설정합니다.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.DataProtection": "Information"
}
},
"AllowedHosts": "*"
}
로깅에 대한 자세한 내용은 .NET Core 및 ASP.NET Core의 로깅을 참조하세요.
추가 리소스
데이터 보호 시스템은 초기화되면 운영 환경에 따라 기본 설정을 적용합니다. 이러한 설정은 단일 컴퓨터에서 실행되는 앱에 적합합니다. 그러나 개발자가 기본 설정을 변경해야 하는 경우가 있습니다.
- 앱이 여러 머신에 분산되어 있습니다.
- 규정 준수가 필요합니다.
데이터 보호 시스템은 이러한 시나리오를 위한 여러 구성 API를 제공합니다.
Warning
데이터 보호 키 링은 구성 파일과 마찬가지로 적절한 권한을 사용하여 보호해야 합니다. 키를 rest암호화하도록 선택할 수 있지만 사이버 공격이 새 키를 만드는 것을 방지하지는 않습니다. 따라서 앱의 보안이 영향을 받게 됩니다. 데이터 보호로 구성된 스토리지 위치는 구성 파일을 보호하는 방식과 비슷하게 앱 자체에 대한 액세스 권한이 제한되어야 합니다. 예를 들어, 키 링을 디스크에 저장하는 경우에는 파일 시스템 권한을 사용하세요. 웹앱이 identity 실행되는 아래에 해당 디렉터리에 대한 읽기, 쓰기 및 만들기 액세스 권한만 있는지 확인합니다. Azure Blob Storage를 사용하는 경우, 웹앱만 Blob 저장소의 항목을 읽고, 쓰고, 만들 수 있어야 합니다.
확장 메서드 AddDataProtection IDataProtectionBuilder는 . IDataProtectionBuilder
는 함께 연결하여 데이터 보호 옵션을 구성할 수 있는 확장 메서드를 노출합니다.
이 문서에서 사용하는 데이터 보호 확장에는 다음과 같은 NuGet 패키지가 필요합니다.
ProtectKeysWithAzureKeyVault
CLI를 사용하여 Azure에 로그인합니다. 예를 들면 다음과 같습니다.
az login
Azure Key Vault에 키를 저장하려면 클래스에서 Startup
시스템을 ProtectKeysWithAzureKeyVault 구성합니다. blobUriWithSasToken
은 키 파일이 저장되는 전체 URI입니다. URI는 쿼리 문자열 매개 변수로 SAS 토큰을 포함해야 합니다.
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToAzureBlobStorage(new Uri("<blobUriWithSasToken>"))
.ProtectKeysWithAzureKeyVault(new Uri("<keyIdentifier>"), new DefaultAzureCredential());
}
앱이 KeyVault와 통신하고 권한을 부여하려면 AzureIdentity 패키지를 추가해야 합니다.
키 링 스토리지 위치(예: PersistKeysToAzureBlobStorage)를 설정합니다. 호출 ProtectKeysWithAzureKeyVault
은 키 링 스토리지 위치를 포함하여 자동 데이터 보호 설정을 사용하지 않도록 설정하는 것을 구현 IXmlEncryptor 하기 때문에 위치를 설정해야 합니다. 위 예제에서는 Azure Blob Storage를 사용하여 키 링을 저장합니다. 자세한 내용은 키 스토리지 공급자: Azure Storage를 참조하세요. PersistKeysToFileSystem을 사용하여 키 링을 로컬에 저장할 수도 있습니다.
keyIdentifier
는 키 암호화에 사용되는 키 자격 증명 모음 키 식별자입니다. 예를 들어, contosokeyvault
에서 키 자격 증명 모음 dataprotection
에 만들어진 키는 키 식별자 https://contosokeyvault.vault.azure.net/keys/dataprotection/
을 갖습니다. 앱에 키 자격 증명 모음에 대한 Get, Unwrap Key 및 Wrap Key 권한을 제공합니다.
ProtectKeysWithAzureKeyVault
오버로드:
- ProtectKeysWithAzureKeyVault(IDataProtectionBuilder, Uri, TokenCredential) 는 keyIdentifier Uri 및 tokenCredential을 사용하여 데이터 보호 시스템이 키 자격 증명 모음을 사용할 수 있도록 합니다.
- ProtectKeysWithAzureKeyVault(IDataProtectionBuilder, String, IKeyEncryptionKeyResolver) 는 keyIdentifier 문자열 및 IKeyEncryptionKeyResolver를 사용하여 데이터 보호 시스템에서 키 자격 증명 모음을 사용할 수 있도록 허용합니다.
앱에서 이전 Azure 패키지(Microsoft.AspNetCore.DataProtection.AzureStorage 및 Microsoft.AspNetCore.DataProtection.AzureKeyVault)를 사용하는 경우 이러한 참조를 제거하고 Azure.Extensions.AspNetCore.DataProtection.Blobs 및 Azure.Extensions.AspNetCore.DataProtection.Keys로 업그레이드하는 것이 좋습니다. 이러한 패키지는 새 업데이트가 제공되는 위치이며 이전 패키지의 몇 가지 키 보안 및 안정성 문제를 해결합니다.
services.AddDataProtection()
//This blob must already exist before the application is run
.PersistKeysToAzureBlobStorage("<storage account connection string", "<key store container name>", "<key store blob name>")
//Removing this line below for an initial run will ensure the file is created correctly
.ProtectKeysWithAzureKeyVault(new Uri("<keyIdentifier>"), new DefaultAzureCredential());
PersistKeysToFileSystem
%LOCALAPPDATA% 기본 위치 대신 UNC 공유에 키를 저장하려면 다음을 사용하여 시스템을 PersistKeysToFileSystem구성합니다.
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"));
}
Warning
키 지속성 위치를 변경하면 DPAPI가 적절한 암호화 메커니즘인지 여부를 알 수 없으므로 시스템은 더 이상 키를 자동으로 암호화 rest하지 않습니다.
PersistKeysToDbContext
EntityFramework를 사용하는 데이터베이스에 키를 저장하려면 Microsoft.AspNetCore.DataProtection.EntityFrameworkCore 패키지를 사용하여 시스템을 구성합니다.
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToDbContext<DbContext>()
}
위 코드는 구성된 데이터베이스에 키를 저장합니다. 사용되는 데이터베이스 컨텍스트는 IDataProtectionKeyContext
를 구현해야 합니다. IDataProtectionKeyContext
는 DataProtectionKeys
속성을 노출합니다.
public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
이 속성은 키가 저장되는 테이블을 나타냅니다. 테이블을 수동으로 또는 DbContext
마이그레이션을 사용하여 만듭니다. 자세한 내용은 DataProtectionKey를 참조하세요.
ProtectKeysWith*
ProtectKeysWith* 구성 API를 호출하여 키를 rest 보호하도록 시스템을 구성할 수 있습니다. UNC 공유에 키를 저장하고 특정 X.509 인증서를 사용하여 해당 키를 rest 암호화하는 아래 예제를 살펴보세요.
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
.ProtectKeysWithCertificate(Configuration["Thumbprint"]);
}
파일에서 로드된 인증서와 같이 ProtectKeysWithCertificate에 X509Certificate2를 제공할 수 있습니다.
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
.ProtectKeysWithCertificate(
new X509Certificate2("certificate.pfx", Configuration["Thumbprint"]));
}
기본 제공 키 암호화 메커니즘에 대한 자세한 예제 및 논의는 키 암호화 At Rest 를 참조하세요.
UnprotectKeysWithAnyCertificate
다음을 사용하여 인증서 배열 X509Certificate2 을 사용하여 인증서를 회전하고 키를 rest 해독할 수 있습니다.UnprotectKeysWithAnyCertificate
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
.ProtectKeysWithCertificate(
new X509Certificate2("certificate.pfx", Configuration["MyPasswordKey"));
.UnprotectKeysWithAnyCertificate(
new X509Certificate2("certificate_old_1.pfx", Configuration["MyPasswordKey_1"]),
new X509Certificate2("certificate_old_2.pfx", Configuration["MyPasswordKey_2"]));
}
SetDefaultKeyLifetime
기본 90일 대신 14일의 키 수명을 사용하도록 시스템을 구성하려면 다음을 사용합니다 SetDefaultKeyLifetime.
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.SetDefaultKeyLifetime(TimeSpan.FromDays(14));
}
SetApplicationName
기본적으로 데이터 보호 시스템은 앱들 간에 동일한 물리적 키 리포지토리를 공유하는 경우에도 콘텐츠 루트 경로를 기반으로 앱을 서로 격리합니다. 이러한 격리에 따라 앱이 다른 앱의 보호된 페이로드를 알 수 없게 됩니다.
앱 간에 보호된 페이로드를 공유하려면:
- 각 앱에서 SetApplicationName을 같은 값으로 구성합니다.
- 각 앱에서 동일한 버전의 데이터 보호 API 스택을 사용합니다. 앱의 프로젝트 파일에서 다음 중 하나를 수행합니다.
- Microsoft.AspNetCore.App metapackage를 통해 동일한 공유 프레임워크 버전을 참조합니다.
- 동일한 데이터 보호 패키지 버전을 참조합니다.
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.SetApplicationName("shared app name");
}
SetApplicationName이 DataProtectionOptions.ApplicationDiscriminator를 내부적으로 설정합니다. 판별자가 사용되는 방법에 대한 자세한 내용은 이 문서의 뒷부분에 나오는 다음 섹션을 참조하세요.
DisableAutomaticKeyGeneration
키의 만료가 가까워지면 앱이 자동으로 키를 롤링(새 키 만들기)하지 못하도록 해야 하는 시나리오가 있을 수 있습니다. 이 시나리오의 한 가지 예로 기본/보조 관계로 설정된 앱을 들 수 있습니다. 여기서 기본 앱은 키 관리 문제를 담당하고, 보조 앱은 키 링에 대한 읽기 전용 보기만 가능합니다. 보조 앱은 DisableAutomaticKeyGeneration을 사용하여 시스템을 구성함으로써 키 링을 읽기 전용으로 취급하도록 구성할 수 있습니다.
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.DisableAutomaticKeyGeneration();
}
애플리케이션별 격리
데이터 보호 시스템이 ASP.NET Core 호스트에 의해 제공되는 경우, 앱이 동일한 작업자 프로세스 계정에서 실행되고 동일한 마스터 키 링 자료를 사용하는 경우에도 자동으로 앱을 서로 격리합니다. 이는 System.Web의 <machineKey>
요소의 IsolateApps 한정자와 비슷합니다.
격리 메커니즘은 로컬 컴퓨터에 있는 각 앱을 고유한 테넌트로 간주하는 방식으로 작동합니다. 따라서 지정된 앱에 대해 루트된 IDataProtector에는 자동으로 앱 ID가 판별자(ApplicationDiscriminator)로 포함됩니다. 앱의 고유 ID는 앱의 실제 경로입니다.
- IIS에 호스트되는 앱의 경우, 고유 ID는 앱의 IIS 실제 경로입니다. 앱이 웹 팜 환경에 배포된 경우, 웹 팜에 있는 모든 머신에서 IIS 환경이 비슷하게 구성되었다는 가정하에 이 값은 일정합니다.
- Kestrel 서버에서 실행되는 자체 호스트 앱의 경우, 고유 ID는 디스크에 있는 앱의 실제 경로입니다.
고유 식별자는 재설정-개별 앱과 컴퓨터 자체의 재설정-이 이루어져도 지속되도록 설계되었습니다.
이 격리 메커니즘에서는 앱이 악성이 아니라고 가정합니다. 악성 앱은 동일한 작업자 프로세스 계정에서 실행되는 다른 앱에 언제든지 영향을 줄 수 있습니다. 앱이 상호 신뢰하지 않는 공유 호스트 환경에서는 호스트 공급자가 앱의 기본 키 리포지토리를 분리하는 등 앱 간에 OS 수준 격리를 유지하기 위한 조치를 취해야 합니다.
데이터 보호 시스템이 ASP.NET Core 호스트에 의해 제공되지 않은 경우(예를 들어, DataProtectionProvider
구체적인 형식을 통해 인스턴스화한 경우), 앱 격리가 기본적으로 사용하지 않도록 설정됩니다. 앱 격리가 사용하지 않도록 설정되면, 동일한 키 링 자료를 사용하는 모든 앱은 적절한 용도를 제공하는 한 페이로드를 공유할 수 있습니다. 이 환경에서 앱 격리를 제공하려면 구성 개체에 대해 SetApplicationName 메서드를 호출하고 각 앱의 고유한 이름을 제공하세요.
데이터 보호와 앱 격리
앱 격리 시 다음을 고려해야 합니다.
여러 앱이 동일한 키 리포지토리를 가리킨다면 그 의도는 앱이 동일한 마스터 키 자료를 공유하도록 하는 것입니다. 데이터 보호는 키 링을 공유하는 모든 앱이 해당 키 링에 있는 모든 항목에 액세스할 수 있다는 가정하에 개발됩니다. 애플리케이션 고유 식별자는 키 링이 제공하는 키에서 파생된 애플리케이션별 키를 격리하는 데 사용됩니다. 추가 격리를 적용하기 위해 Azure KeyVault가 제공하는 것과 같은 항목 수준 권한은 필요하지 않습니다. 항목 수준 권한을 시도하면 애플리케이션 오류가 발생합니다. 기본 제공 애플리케이션 격리를 사용하지 않으려면 개별적인 키 저장소 위치를 사용해야 하며 애플리케이션 간에 공유되지 않아야 합니다.
애플리케이션 판별자(ApplicationDiscriminator)는 여러 앱이 동일한 마스터 키 재질을 공유하도록 하되 암호화 페이로드는 서로 구분하는 용도로 사용됩니다. 앱이 다른 앱의 암호화 페이로드를 읽을 수 있으려면 동일한 애플리케이션 판별자를 가져야 합니다. 동일한 애플리케이션 판별자는
SetApplicationName
을 호출하여 설정할 수 있습니다.앱이 손상된 경우(예: RCE 공격에 의해) 해당 앱에 액세스할 수 있는 모든 마스터 키 자료도 보호rest 상태에 관계없이 손상된 것으로 간주되어야 합니다. 이는 두 앱이 동일한 리포지토리를 가리키는 경우, 서로 다른 앱 판별자를 사용하는 경우에도 하나의 앱의 손상되면 두 앱이 모두 손상된 것과 기능적으로 동일함을 의미합니다.
이 "기능적으로 두 절의 손상에 해당" 절은 두 앱이 키 보호를 rest위해 서로 다른 메커니즘을 사용하는 경우에도 유지됩니다. 이는 일반적으로 사용되는 구성이 아닙니다. 보호rest 메커니즘은 사이버 공격이 리포지토리에 대한 읽기 액세스 권한을 얻는 경우 보호를 제공하기 위한 것입니다. 리포지토리에 대한 쓰기 액세스 권한을 얻는 사이버 공격(앱 내에서 코드 실행 권한을 획득했기 때문일 수 있습니다)은 스토리지에 악성 키를 삽입할 수 있습니다. 데이터 보호 시스템은 의도적으로 키 리포지토리에 대한 쓰기 액세스 권한을 얻는 사이버 공격자에 대한 보호를 제공하지 않습니다.
앱이 다른 앱과 진정으로 격리된 상태로 유지되려면 서로 다른 키 리포지토리를 사용해야 합니다. 이는 자연스럽게 “격리된”에 대한 정의를 벗어나게 됩니다. 다른 앱의 데이터 저장소에 대한 읽기 및 쓰기 권한을 갖는 앱은 격리되지 않습니다.
UseCryptographicAlgorithms를 사용하여 알고리즘 변경
데이터 보호 스택을 통해 새로 생성된 키에서 사용하는 기본 알고리즘을 변경할 수 있습니다. 이 작업을 수행하는 가장 간단한 방법은 구성 콜백에서 호출 UseCryptographicAlgorithms 하는 것입니다.
services.AddDataProtection()
.UseCryptographicAlgorithms(
new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});
기본 EncryptionAlgorithm은 AES-256-CBC이고, 기본 ValidationAlgorithm은 HMACSHA256입니다. 기본 정책은 시스템 관리자가 설정하거나 머신 전체에 적용되는 정책을 통해 설정할 수 있으며, UseCryptographicAlgorithms
를 명시적으로 호출하면 기본 정책이 재정의됩니다.
UseCryptographicAlgorithms
를 호출하면 미리 정의된 기본 제공 목록에서 원하는 알고리즘을 지정할 수 있습니다. 알고리즘의 구현에 대해서는 신경 쓸 필요가 없습니다. 위 시나리오에서 데이터 보호 시스템은 Windows에서 실행되는 경우 AES의 CNG 구현을 사용하려고 시도합니다. 그렇지 않으면 관리 System.Security.Cryptography.Aes 되는 클래스로 돌아갑니다.
호출 UseCustomCryptographicAlgorithms을 통해 구현을 수동으로 지정할 수 있습니다.
팁
알고리즘을 변경해도 키 링에 있는 기존 키는 영향을 받으며, 새로 생성되는 키만 영향을 받습니다.
사용자 지정 관리형 알고리즘 지정
사용자 지정 관리 알고리즘을 지정하려면 구현 형식을 ManagedAuthenticatedEncryptorConfiguration 가리키는 인스턴스를 만듭니다.
serviceCollection.AddDataProtection()
.UseCustomCryptographicAlgorithms(
new ManagedAuthenticatedEncryptorConfiguration()
{
// A type that subclasses SymmetricAlgorithm
EncryptionAlgorithmType = typeof(Aes),
// Specified in bits
EncryptionAlgorithmKeySize = 256,
// A type that subclasses KeyedHashAlgorithm
ValidationAlgorithmType = typeof(HMACSHA256)
});
일반적으로 *Type 속성은 매개 변수 없는 퍼블릭 ctor를 통해 구체적이고 인스턴스화 가능한 SymmetricAlgorithm 및 KeyedHashAlgorithm의 구현을 가리켜야 합니다. 단, 시스템에서 편의를 위해 특수 사례로 취급하는 typeof(Aes)
와 같은 값도 있습니다.
참고 항목
SymmetricAlgorithm은 키 길이가 128비트 이상이고 블록 크기가 64비트 이상이어야 하며, PKCS #7 패딩을 사용하여 CBC 모드 암호화를 지원해야 합니다. KeyedHashAlgorithm은 다이제스트 크기가 >=128비트(128비트 이상)이어야 하며, 해시 알고리즘의 다이제스트 길이와 동일한 길이의 키를 지원해야 합니다. KeyedHashAlgorithm은 반드시 HMAC여야 할 필요는 없습니다.
사용자 지정 Windows CNG 알고리즘 지정
HMAC 유효성 검사와 함께 CBC 모드 암호화를 사용하여 사용자 지정 Windows CNG 알고리즘을 CngCbcAuthenticatedEncryptorConfiguration 지정하려면 알고리즘 정보를 포함하는 인스턴스를 만듭니다.
services.AddDataProtection()
.UseCustomCryptographicAlgorithms(
new CngCbcAuthenticatedEncryptorConfiguration()
{
// Passed to BCryptOpenAlgorithmProvider
EncryptionAlgorithm = "AES",
EncryptionAlgorithmProvider = null,
// Specified in bits
EncryptionAlgorithmKeySize = 256,
// Passed to BCryptOpenAlgorithmProvider
HashAlgorithm = "SHA256",
HashAlgorithmProvider = null
});
참고 항목
대칭 블록 암호 알고리즘은 키 길이가 >= 128비트(128비트 이상)이고 블록 크기가 >= 64비트(64비트 이상)이어야 하며, PKCS #7 패딩을 사용하여 CBC 모드 암호화를 지원해야 합니다. 해시 알고리즘은 다이제스트 크기가 >= 128비트(128비트 이상)이어야 하며, BCRYPT_ALG_HANDLE_HMAC_FLAG 플래그를 사용하여 열 수 있어야 합니다. *Provider 속성을 null로 설정하면 지정된 알고리즘에 대해 기본 공급자를 사용할 수 있습니다. 자세한 내용은 BCryptOpenAlgorithmProvider 설명서를 참조하세요.
유효성 검사와 함께 Galois/카운터 모드 암호화를 사용하여 사용자 지정 Windows CNG 알고리즘을 CngGcmAuthenticatedEncryptorConfiguration 지정하려면 알고리즘 정보가 포함된 인스턴스를 만듭니다.
services.AddDataProtection()
.UseCustomCryptographicAlgorithms(
new CngGcmAuthenticatedEncryptorConfiguration()
{
// Passed to BCryptOpenAlgorithmProvider
EncryptionAlgorithm = "AES",
EncryptionAlgorithmProvider = null,
// Specified in bits
EncryptionAlgorithmKeySize = 256
});
참고 항목
대칭 블록 암호 알고리즘은 키 길이가 >= 128비트(128비트 이상)이고 블록 크기가 정확히 128비트이어야 하며 GCM 암호화를 지원해야 합니다. 지정된 알고리즘에 EncryptionAlgorithmProvider 기본 공급자를 사용하도록 속성을 null로 설정할 수 있습니다. 자세한 내용은 BCryptOpenAlgorithmProvider 설명서를 참조하세요.
그 밖의 사용자 지정 알고리즘 지정
데이터 보호 시스템은 1급 API로 노출되지 않지만, 거의 모든 종류의 알고리즘을 지정할 수 있을 만큼 확장성이 뛰어납니다. 예를 들어, 하나의 HSM(하드웨어 보안 모듈) 내에 포함된 모든 키를 유지하고 핵심 암호화 및 암호 해독 루틴의 사용자 지정 구현을 제공할 수 있습니다. 자세한 내용은 핵심 암호화 확장성의 IAuthenticatedEncryptor를 참조하세요.
Docker 컨테이너를 호스트할 때 키 저장
Docker 컨테이너를 호스트할 때는 키를 다음 중 하나에 저장해야 합니다.
- 컨테이너의 수명을 초과하여 유지되는 Docker의 볼륨인 폴더(예: 공유 볼륨 또는 호스트 마운트된 볼륨)
- 외부 공급자(예: Azure Blob Storage(
ProtectKeysWithAzureKeyVault
섹션 참조) 또는 Redis)
Redis를 사용하여 키 저장
Redis 데이터 지속성을 지원하는 Redis 버전만 키를 저장하는 데 사용해야 합니다. Azure Blob 스토리지는 지속성을 지원하므로 키를 저장하는 데 사용할 수 있습니다. 자세한 내용은 해당 GitHub 이슈를 참조하세요.
DataProtection 로깅
문제 진단에 도움이 되도록 DataProtection의 Information
수준 로깅을 사용하도록 설정합니다. 다음 appsettings.json
파일은 DataProtection API의 정보 로깅을 사용하도록 설정합니다.
{
"Logging": {
"LogLevel": {
"Microsoft.AspNetCore.DataProtection": "Information"
}
}
}
로깅에 대한 자세한 내용은 .NET Core 및 ASP.NET Core의 로깅을 참조하세요.
추가 리소스
ASP.NET Core