在 ASP.NET Core 中設定憑證驗證Configure certificate authentication in ASP.NET Core

Microsoft.AspNetCore.Authentication.Certificate 包含類似于 ASP.NET Core 憑證驗證 的實作為。Microsoft.AspNetCore.Authentication.Certificate contains an implementation similar to Certificate Authentication for ASP.NET Core. 憑證驗證會在 TLS 層級進行,但在達到 ASP.NET Core 之前也是如此。Certificate authentication happens at the TLS level, long before it ever gets to ASP.NET Core. 更精確地說,這是驗證憑證的驗證處理常式,然後為您提供可將該憑證解析為的事件 ClaimsPrincipalMore accurately, this is an authentication handler that validates the certificate and then gives you an event where you can resolve that certificate to a ClaimsPrincipal.

您的伺服器設定為使用 IIS、Kestrel、Azure Web Apps,或您所使用的任何其他伺服器進行憑證驗證。Configure your server for certificate authentication, be it IIS, Kestrel, Azure Web Apps, or whatever else you're using.

Proxy 和負載平衡器案例Proxy and load balancer scenarios

憑證驗證是一種可設定狀態的案例,主要用於 proxy 或負載平衡器不會處理用戶端與伺服器之間的流量。Certificate authentication is a stateful scenario primarily used where a proxy or load balancer doesn't handle traffic between clients and servers. 如果使用 proxy 或負載平衡器,憑證驗證只適用于 proxy 或負載平衡器:If a proxy or load balancer is used, certificate authentication only works if the proxy or load balancer:

  • 處理驗證。Handles the authentication.
  • 將使用者驗證資訊傳遞給應用程式 (例如,在要求標頭中,) ,其會對驗證資訊採取行動。Passes the user authentication information to the app (for example, in a request header), which acts on the authentication information.

使用 proxy 和負載平衡器的環境中的憑證驗證替代方法是使用 OpenID Connect (OIDC) Active Directory 同盟服務 (ADFS) 。An alternative to certificate authentication in environments where proxies and load balancers are used is Active Directory Federated Services (ADFS) with OpenID Connect (OIDC).

開始使用Get started

取得 HTTPS 憑證、加以套用,並 設定您的伺服器 以要求憑證。Acquire an HTTPS certificate, apply it, and configure your server to require certificates.

在您的 web 應用程式中,新增 AspNetCore 的參考。 憑證 套件。In your web app, add a reference to the Microsoft.AspNetCore.Authentication.Certificate package. 然後,在 Startup.ConfigureServices 方法中, services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); 使用您的選項呼叫,提供的委派, OnCertificateValidated 以在隨要求傳送的用戶端憑證上進行任何補充驗證。Then in the Startup.ConfigureServices method, call services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); with your options, providing a delegate for OnCertificateValidated to do any supplementary validation on the client certificate sent with requests. 請將該資訊轉換成 ClaimsPrincipal ,並在屬性上設定它 context.PrincipalTurn that information into a ClaimsPrincipal and set it on the context.Principal property.

如果驗證失敗,此處理程式 403 (Forbidden) 401 (Unauthorized) 會如您所預期般傳迴響應,而不是。If authentication fails, this handler returns a 403 (Forbidden) response rather a 401 (Unauthorized), as you might expect. 原因是必須在初始 TLS 連接期間進行驗證。The reasoning is that the authentication should happen during the initial TLS connection. 到達處理常式時,時間太晚。By the time it reaches the handler, it's too late. 沒有任何方法可將連線從匿名連接升級為具有憑證的連接。There's no way to upgrade the connection from an anonymous connection to one with a certificate.

此外,也要加入 app.UseAuthentication(); Startup.Configure 方法。Also add app.UseAuthentication(); in the Startup.Configure method. 否則, HttpContext.User 將不會設定為 ClaimsPrincipal 從憑證建立。Otherwise, the HttpContext.User will not be set to ClaimsPrincipal created from the certificate. 例如:For example:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(
        CertificateAuthenticationDefaults.AuthenticationScheme)
        .AddCertificate()
        // Adding an ICertificateValidationCache results in certificate auth caching the results.
        // The default implementation uses a memory cache.
        .AddCertificateCache();

    // All other service configuration
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseAuthentication();

    // All other app configuration
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(
        CertificateAuthenticationDefaults.AuthenticationScheme)
        .AddCertificate();

    // All other service configuration
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseAuthentication();

    // All other app configuration
}

上述範例示範新增憑證驗證的預設方式。The preceding example demonstrates the default way to add certificate authentication. 處理常式會使用一般憑證屬性來建立使用者主體。The handler constructs a user principal using the common certificate properties.

設定憑證驗證Configure certificate validation

CertificateAuthenticationOptions處理常式具有一些內建驗證,這是您應該對憑證執行的最小驗證。The CertificateAuthenticationOptions handler has some built-in validations that are the minimum validations you should perform on a certificate. 預設會啟用這些設定。Each of these settings is enabled by default.

AllowedCertificateTypes = 鏈式、SelfSigned 或 All (連鎖 |SelfSigned) AllowedCertificateTypes = Chained, SelfSigned, or All (Chained | SelfSigned)

預設值:CertificateTypes.ChainedDefault value: CertificateTypes.Chained

這項檢查會驗證是否只允許適當的憑證類型。This check validates that only the appropriate certificate type is allowed. 如果應用程式使用自我簽署憑證,則必須將此選項設定為 CertificateTypes.AllCertificateTypes.SelfSignedIf the app is using self-signed certificates, this option needs to be set to CertificateTypes.All or CertificateTypes.SelfSigned.

ValidateCertificateUseValidateCertificateUse

預設值:trueDefault value: true

這項檢查會驗證用戶端出示的憑證是否有用戶端驗證擴充金鑰使用 (EKU) ,或完全沒有 Eku。This check validates that the certificate presented by the client has the Client Authentication extended key use (EKU), or no EKUs at all. 如同規定,如果未指定 EKU,則所有的 Eku 都會被視為有效。As the specifications say, if no EKU is specified, then all EKUs are deemed valid.

ValidateValidityPeriodValidateValidityPeriod

預設值:trueDefault value: true

這項檢查會驗證憑證是否在其有效期間內。This check validates that the certificate is within its validity period. 在每個要求中,此處理程式可確保在其目前的會話中呈現時,憑證的有效期限未過期。On each request, the handler ensures that a certificate that was valid when it was presented hasn't expired during its current session.

RevocationFlagRevocationFlag

預設值:X509RevocationFlag.ExcludeRootDefault value: X509RevocationFlag.ExcludeRoot

旗標,指定要檢查鏈中的哪些憑證以進行撤銷。A flag that specifies which certificates in the chain are checked for revocation.

只有當憑證連結到根憑證時,才會執行撤銷檢查。Revocation checks are only performed when the certificate is chained to a root certificate.

RevocationModeRevocationMode

預設值:X509RevocationMode.OnlineDefault value: X509RevocationMode.Online

指定如何執行撤銷檢查的旗標。A flag that specifies how revocation checks are performed.

指定線上檢查可能會在與憑證授權單位單位聯繫時造成長時間延遲。Specifying an online check can result in a long delay while the certificate authority is contacted.

只有當憑證連結到根憑證時,才會執行撤銷檢查。Revocation checks are only performed when the certificate is chained to a root certificate.

我可以將應用程式設定為只需要特定路徑上的憑證嗎?Can I configure my app to require a certificate only on certain paths?

這是不可能的。This isn't possible. 請記住,憑證交換是在 HTTPS 對話的開頭完成,伺服器會在該連接上收到第一個要求之前完成,因此無法根據任何要求欄位進行範圍。Remember the certificate exchange is done that the start of the HTTPS conversation, it's done by the server before the first request is received on that connection so it's not possible to scope based on any request fields.

處理程式事件Handler events

處理常式有兩個事件:The handler has two events:

  • OnAuthenticationFailed:如果在驗證期間發生例外狀況,並可讓您進行回應,則呼叫。OnAuthenticationFailed: Called if an exception happens during authentication and allows you to react.
  • OnCertificateValidated:憑證經過驗證之後呼叫,通過驗證和預設主體已建立。OnCertificateValidated: Called after the certificate has been validated, passed validation and a default principal has been created. 此事件可讓您執行自己的驗證,並增強或取代主體。This event allows you to perform your own validation and augment or replace the principal. 範例包括:For examples include:
    • 判斷您的服務是否知道憑證。Determining if the certificate is known to your services.

    • 建立您自己的主體。Constructing your own principal. 請考慮 Startup.ConfigureServices 中的下列範例:Consider the following example in Startup.ConfigureServices:

      services.AddAuthentication(
          CertificateAuthenticationDefaults.AuthenticationScheme)
          .AddCertificate(options =>
          {
              options.Events = new CertificateAuthenticationEvents
              {
                  OnCertificateValidated = context =>
                  {
                      var claims = new[]
                      {
                          new Claim(
                              ClaimTypes.NameIdentifier, 
                              context.ClientCertificate.Subject,
                              ClaimValueTypes.String, 
                              context.Options.ClaimsIssuer),
                          new Claim(ClaimTypes.Name,
                              context.ClientCertificate.Subject,
                              ClaimValueTypes.String, 
                              context.Options.ClaimsIssuer)
                      };
      
                      context.Principal = new ClaimsPrincipal(
                          new ClaimsIdentity(claims, context.Scheme.Name));
                      context.Success();
      
                      return Task.CompletedTask;
                  }
              };
          });
      

如果您發現輸入憑證不符合額外的驗證,請呼叫 context.Fail("failure reason") 失敗原因。If you find the inbound certificate doesn't meet your extra validation, call context.Fail("failure reason") with a failure reason.

對於真正的功能,您可能會想要呼叫在相依性插入中註冊的服務,以連接到資料庫或其他類型的使用者存放區。For real functionality, you'll probably want to call a service registered in dependency injection that connects to a database or other type of user store. 使用傳遞至委派的內容來存取您的服務。Access your service by using the context passed into your delegate. 請考慮 Startup.ConfigureServices 中的下列範例:Consider the following example in Startup.ConfigureServices:

services.AddAuthentication(
    CertificateAuthenticationDefaults.AuthenticationScheme)
    .AddCertificate(options =>
    {
        options.Events = new CertificateAuthenticationEvents
        {
            OnCertificateValidated = context =>
            {
                var validationService =
                    context.HttpContext.RequestServices
                        .GetRequiredService<ICertificateValidationService>();
                
                if (validationService.ValidateCertificate(
                    context.ClientCertificate))
                {
                    var claims = new[]
                    {
                        new Claim(
                            ClaimTypes.NameIdentifier, 
                            context.ClientCertificate.Subject, 
                            ClaimValueTypes.String, 
                            context.Options.ClaimsIssuer),
                        new Claim(
                            ClaimTypes.Name, 
                            context.ClientCertificate.Subject, 
                            ClaimValueTypes.String, 
                            context.Options.ClaimsIssuer)
                    };

                    context.Principal = new ClaimsPrincipal(
                        new ClaimsIdentity(claims, context.Scheme.Name));
                    context.Success();
                }                     

                return Task.CompletedTask;
            }
        };
    });

就概念而言,憑證的驗證是授權的考慮。Conceptually, the validation of the certificate is an authorization concern. 例如,在授權原則中(而非內部)新增檢查, OnCertificateValidated 是完全可接受的。Adding a check on, for example, an issuer or thumbprint in an authorization policy, rather than inside OnCertificateValidated, is perfectly acceptable.

設定您的伺服器以要求憑證Configure your server to require certificates

KestrelKestrel

Program.cs 中,設定 Kestrel,如下所示:In Program.cs, configure Kestrel as follows:

public static void Main(string[] args)
{
    CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args)
{
    return Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
            webBuilder.ConfigureKestrel(o =>
            {
                o.ConfigureHttpsDefaults(o => 
            o.ClientCertificateMode = 
                ClientCertificateMode.RequireCertificate);
            });
        });
}

注意

在呼叫之前呼叫所建立的端點 Listen ConfigureHttpsDefaults ,將不會套用預設值。Endpoints created by calling Listen before calling ConfigureHttpsDefaults won't have the defaults applied.

IISIIS

在 IIS 管理員中完成下列步驟:Complete the following steps in IIS Manager:

  1. 從 [ 連接 ] 索引標籤中選取您的網站。Select your site from the Connections tab.
  2. 在 [功能] 視圖 視窗中,按兩下 [ SSL 設定] 選項。Double-click the SSL Settings option in the Features View window.
  3. 選取 [需要 SSL ] 核取方塊,然後在 [用戶端憑證] 區段中選取 [需要] 選項按鈕。Check the Require SSL checkbox, and select the Require radio button in the Client certificates section.

IIS 中的用戶端憑證設定

Azure 和自訂 web proxyAzure and custom web proxies

如需如何設定憑證轉送中介軟體的詳細說明,請參閱 主機和部署檔See the host and deploy documentation for how to configure the certificate forwarding middleware.

在 Azure Web Apps 中使用憑證驗證Use certificate authentication in Azure Web Apps

Azure 不需要轉送設定。No forwarding configuration is required for Azure. 這已在憑證轉送中介軟體中設定。This is already setup in the certificate forwarding middleware.

注意

這需要有 CertificateForwardingMiddleware。This requires that the CertificateForwardingMiddleware is present.

在自訂 web proxy 中使用憑證驗證Use certificate authentication in custom web proxies

AddCertificateForwarding方法是用來指定:The AddCertificateForwarding method is used to specify:

  • 用戶端標頭名稱。The client header name.
  • 如何使用屬性) 載入憑證 (HeaderConverterHow the certificate is to be loaded (using the HeaderConverter property).

例如,在自訂 web proxy 中,會將憑證作為自訂要求標頭來傳遞 X-SSL-CERTIn custom web proxies, the certificate is passed as a custom request header, for example X-SSL-CERT. 若要使用它,請在中設定憑證轉送 Startup.ConfigureServicesTo use it, configure certificate forwarding in Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCertificateForwarding(options =>
    {
        options.CertificateHeader = "X-SSL-CERT";
        options.HeaderConverter = (headerValue) =>
        {
            X509Certificate2 clientCertificate = null;

            if(!string.IsNullOrWhiteSpace(headerValue))
            {
                byte[] bytes = StringToByteArray(headerValue);
                clientCertificate = new X509Certificate2(bytes);
            }

            return clientCertificate;
        };
    });
}

private static byte[] StringToByteArray(string hex)
{
    int NumberChars = hex.Length;
    byte[] bytes = new byte[NumberChars / 2];

    for (int i = 0; i < NumberChars; i += 2)
    {
        bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
    }

    return bytes;
}

Startup.Configure然後,方法會新增中介軟體。The Startup.Configure method then adds the middleware. UseCertificateForwarding 呼叫和之前呼叫 UseAuthentication UseAuthorizationUseCertificateForwarding is called before the calls to UseAuthentication and UseAuthorization:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...

    app.UseRouting();

    app.UseCertificateForwarding();
    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

您可以使用不同的類別來執行驗證邏輯。A separate class can be used to implement validation logic. 因為此範例中使用的是相同的自我簽署憑證,請確定只可使用您的憑證。Because the same self-signed certificate is used in this example, ensure that only your certificate can be used. 驗證用戶端憑證和伺服器憑證的指紋是否相符,否則任何憑證都可供使用,並且足以進行驗證。Validate that the thumbprints of both the client certificate and the server certificate match, otherwise any certificate can be used and will be enough to authenticate. 這會在方法內使用 AddCertificateThis would be used inside the AddCertificate method. 如果您使用的是中繼憑證或子系憑證,您也可以在此處驗證主體或簽發者。You could also validate the subject or the issuer here if you're using intermediate or child certificates.

using System.IO;
using System.Security.Cryptography.X509Certificates;

namespace AspNetCoreCertificateAuthApi
{
    public class MyCertificateValidationService
    {
        public bool ValidateCertificate(X509Certificate2 clientCertificate)
        {
            // Do not hardcode passwords in production code
            // Use thumbprint or key vault
            var cert = new X509Certificate2(
                Path.Combine("sts_dev_cert.pfx"), "1234");

            if (clientCertificate.Thumbprint == cert.Thumbprint)
            {
                return true;
            }

            return false;
        }
    }
}

使用憑證和 HttpClientHandler 來執行 HttpClientImplement an HttpClient using a certificate and the HttpClientHandler

HttpClientHandler可以直接加入類別的函式中 HttpClientThe HttpClientHandler could be added directly in the constructor of the HttpClient class. 建立的實例時,請務必小心 HttpClientCare should be taken when creating instances of the HttpClient. HttpClient然後,會將憑證傳送給每個要求。The HttpClient will then send the certificate with each request.

private async Task<JsonDocument> GetApiDataUsingHttpClientHandler()
{
    var cert = new X509Certificate2(Path.Combine(_environment.ContentRootPath, "sts_dev_cert.pfx"), "1234");
    var handler = new HttpClientHandler();
    handler.ClientCertificates.Add(cert);
    var client = new HttpClient(handler);
     
    var request = new HttpRequestMessage()
    {
        RequestUri = new Uri("https://localhost:44379/api/values"),
        Method = HttpMethod.Get,
    };
    var response = await client.SendAsync(request);
    if (response.IsSuccessStatusCode)
    {
        var responseContent = await response.Content.ReadAsStringAsync();
        var data = JsonDocument.Parse(responseContent);
        return data;
    }
 
    throw new ApplicationException($"Status code: {response.StatusCode}, Error: {response.ReasonPhrase}");
}

使用憑證和 IHttpClientFactory 中的命名 HttpClient 來執行 HttpClientImplement an HttpClient using a certificate and a named HttpClient from IHttpClientFactory

在下列範例中,會 HttpClientHandler 使用處理程式中的屬性,將用戶端憑證新增至 ClientCertificatesIn the following example, a client certificate is added to a HttpClientHandler using the ClientCertificates property from the handler. 然後,您可以使用方法,在的已命名實例中使用此處理程式 HttpClient ConfigurePrimaryHttpMessageHandlerThis handler can then be used in a named instance of an HttpClient using the ConfigurePrimaryHttpMessageHandler method. 這是在中設定 Startup.ConfigureServicesThis is setup in Startup.ConfigureServices:

var clientCertificate = 
    new X509Certificate2(
      Path.Combine(_environment.ContentRootPath, "sts_dev_cert.pfx"), "1234");
 
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(clientCertificate);
 
services.AddHttpClient("namedClient", c =>
{
}).ConfigurePrimaryHttpMessageHandler(() => handler);

IHttpClientFactory然後可以用來取得具有處理常式和憑證的已命名實例。The IHttpClientFactory can then be used to get the named instance with the handler and the certificate. CreateClient具有類別中定義之用戶端名稱的方法 Startup 會用來取得實例。The CreateClient method with the name of the client defined in the Startup class is used to get the instance. 您可以視需要使用用戶端來傳送 HTTP 要求。The HTTP request can be sent using the client as required.

private readonly IHttpClientFactory _clientFactory;
 
public ApiService(IHttpClientFactory clientFactory)
{
    _clientFactory = clientFactory;
}
 
private async Task<JsonDocument> GetApiDataWithNamedClient()
{
    var client = _clientFactory.CreateClient("namedClient");
 
    var request = new HttpRequestMessage()
    {
        RequestUri = new Uri("https://localhost:44379/api/values"),
        Method = HttpMethod.Get,
    };
    var response = await client.SendAsync(request);
    if (response.IsSuccessStatusCode)
    {
        var responseContent = await response.Content.ReadAsStringAsync();
        var data = JsonDocument.Parse(responseContent);
        return data;
    }
 
    throw new ApplicationException($"Status code: {response.StatusCode}, Error: {response.ReasonPhrase}");
}

如果將正確的憑證傳送至伺服器,就會傳回資料。If the correct certificate is sent to the server, the data is returned. 如果未傳送憑證或錯誤的憑證,則會傳回 HTTP 403 狀態碼。If no certificate or the wrong certificate is sent, an HTTP 403 status code is returned.

在 PowerShell 中建立憑證Create certificates in PowerShell

建立憑證是設定此流程最困難的部分。Creating the certificates is the hardest part in setting up this flow. 您可以使用 PowerShell Cmdlet 來建立根憑證 New-SelfSignedCertificateA root certificate can be created using the New-SelfSignedCertificate PowerShell cmdlet. 建立憑證時,請使用強式密碼。When creating the certificate, use a strong password. 請務必新增 KeyUsageProperty 參數和 KeyUsage 參數,如下所示。It's important to add the KeyUsageProperty parameter and the KeyUsage parameter as shown.

建立根 CACreate root CA

New-SelfSignedCertificate -DnsName "root_ca_dev_damienbod.com", "root_ca_dev_damienbod.com" -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(20) -FriendlyName "root_ca_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature

$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText

Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\root_ca_dev_damienbod.pfx -Password $mypwd

Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath root_ca_dev_damienbod.crt

注意

-DnsName參數值必須符合應用程式的部署目標。The -DnsName parameter value must match the deployment target of the app. 例如,"localhost" 用於開發。For example, "localhost" for development.

在信任的根目錄中安裝Install in the trusted root

您的主機系統上必須信任根憑證。The root certificate needs to be trusted on your host system. 預設不會信任憑證授權單位單位所建立的根憑證。A root certificate which was not created by a certificate authority won't be trusted by default. 下列連結說明如何在 Windows 上完成這項作業:The following link explains how this can be accomplished on Windows:

https://social.msdn.microsoft.com/Forums/SqlServer/5ed119ef-1704-4be4-8a4f-ef11de7c8f34/a-certificate-chain-processed-but-terminated-in-a-root-certificate-which-is-not-trusted-by-the

中繼憑證Intermediate certificate

您現在可以從根憑證建立中繼憑證。An intermediate certificate can now be created from the root certificate. 並非所有使用案例都需要此項,但您可能需要建立許多憑證,或需要啟用或停用憑證群組。This isn't required for all use cases, but you might need to create many certificates or need to activate or disable groups of certificates. TextExtension需要參數才能在憑證的基本條件約束中設定路徑長度。The TextExtension parameter is required to set the path length in the basic constraints of the certificate.

中繼憑證接著可以新增至 Windows 主機系統中受信任的中繼憑證。The intermediate certificate can then be added to the trusted intermediate certificate in the Windows host system.

$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText

$parentcert = ( Get-ChildItem -Path cert:\LocalMachine\My\"The thumbprint of the root..." )

New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "intermediate_dev_damienbod.com" -Signer $parentcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "intermediate_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature -TextExtension @("2.5.29.19={text}CA=1&pathlength=1")

Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\intermediate_dev_damienbod.pfx -Password $mypwd

Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath intermediate_dev_damienbod.crt

從中繼憑證建立子憑證Create child certificate from intermediate certificate

您可以從中繼憑證建立子系憑證。A child certificate can be created from the intermediate certificate. 這是終端實體,不需要建立更多子系憑證。This is the end entity and doesn't need to create more child certificates.

$parentcert = ( Get-ChildItem -Path cert:\LocalMachine\My\"The thumbprint from the Intermediate certificate..." )

New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_a_dev_damienbod.com" -Signer $parentcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_a_dev_damienbod.com"

$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText

Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_a_dev_damienbod.pfx -Password $mypwd

Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath child_a_dev_damienbod.crt

從根憑證建立子憑證Create child certificate from root certificate

您也可以直接從根憑證建立子系憑證。A child certificate can also be created from the root certificate directly.

$rootcert = ( Get-ChildItem -Path cert:\LocalMachine\My\"The thumbprint from the root cert..." )

New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_a_dev_damienbod.com" -Signer $rootcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_a_dev_damienbod.com"

$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText

Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_a_dev_damienbod.pfx -Password $mypwd

Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath child_a_dev_damienbod.crt

範例跟中繼憑證-憑證Example root - intermediate certificate - certificate

$mypwdroot = ConvertTo-SecureString -String "1234" -Force -AsPlainText
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText

New-SelfSignedCertificate -DnsName "root_ca_dev_damienbod.com", "root_ca_dev_damienbod.com" -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(20) -FriendlyName "root_ca_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature

Get-ChildItem -Path cert:\localMachine\my\0C89639E4E2998A93E423F919B36D4009A0F9991 | Export-PfxCertificate -FilePath C:\git\root_ca_dev_damienbod.pfx -Password $mypwdroot

Export-Certificate -Cert cert:\localMachine\my\0C89639E4E2998A93E423F919B36D4009A0F9991 -FilePath root_ca_dev_damienbod.crt

$rootcert = ( Get-ChildItem -Path cert:\LocalMachine\My\0C89639E4E2998A93E423F919B36D4009A0F9991 )

New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_a_dev_damienbod.com" -Signer $rootcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_a_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature -TextExtension @("2.5.29.19={text}CA=1&pathlength=1")

Get-ChildItem -Path cert:\localMachine\my\BA9BF91ED35538A01375EFC212A2F46104B33A44 | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_a_dev_damienbod.pfx -Password $mypwd

Export-Certificate -Cert cert:\localMachine\my\BA9BF91ED35538A01375EFC212A2F46104B33A44 -FilePath child_a_dev_damienbod.crt

$parentcert = ( Get-ChildItem -Path cert:\LocalMachine\My\BA9BF91ED35538A01375EFC212A2F46104B33A44 )

New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_b_from_a_dev_damienbod.com" -Signer $parentcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_b_from_a_dev_damienbod.com" 

Get-ChildItem -Path cert:\localMachine\my\141594A0AE38CBBECED7AF680F7945CD51D8F28A | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_b_from_a_dev_damienbod.pfx -Password $mypwd

Export-Certificate -Cert cert:\localMachine\my\141594A0AE38CBBECED7AF680F7945CD51D8F28A -FilePath child_b_from_a_dev_damienbod.crt

使用根、中繼或子系憑證時,您可以視需要使用指紋或 PublicKey 來驗證憑證。When using the root, intermediate, or child certificates, the certificates can be validated using the Thumbprint or PublicKey as required.

using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography.X509Certificates;

namespace AspNetCoreCertificateAuthApi
{
    public class MyCertificateValidationService 
    {
        public bool ValidateCertificate(X509Certificate2 clientCertificate)
        {
            return CheckIfThumbprintIsValid(clientCertificate);
        }

        private bool CheckIfThumbprintIsValid(X509Certificate2 clientCertificate)
        {
            var listOfValidThumbprints = new List<string>
            {
                "141594A0AE38CBBECED7AF680F7945CD51D8F28A",
                "0C89639E4E2998A93E423F919B36D4009A0F9991",
                "BA9BF91ED35538A01375EFC212A2F46104B33A44"
            };

            if (listOfValidThumbprints.Contains(clientCertificate.Thumbprint))
            {
                return true;
            }

            return false;
        }
    }
}

憑證驗證快取Certificate validation caching

ASP.NET Core 5.0 和更新版本支援啟用驗證結果快取的功能。ASP.NET Core 5.0 and later versions support the ability to enable caching of validation results. 快取可大幅改善憑證驗證的效能,因為驗證是昂貴的作業。The caching dramatically improves performance of certificate authentication, as validation is an expensive operation.

根據預設,憑證驗證會停用快取。By default, certificate authentication disables caching. 若要啟用快取,請 AddCertificateCache 在中呼叫 Startup.ConfigureServicesTo enable caching, call AddCertificateCache in Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(
        CertificateAuthenticationDefaults.AuthenticationScheme)
            .AddCertificate()
            .AddCertificateCache(options =>
            {
                options.CacheSize = 1024;
                options.CacheEntryExpiration = TimeSpan.FromMinutes(2);
            });
}

預設的快取執行會將結果儲存在記憶體中。The default caching implementation stores results in memory. 您可以藉由使用相依性插入來執行和註冊,來提供您自己的快取 ICertificateValidationCacheYou can provide your own cache by implementing ICertificateValidationCache and registering it with dependency injection. 例如: services.AddSingleton<ICertificateValidationCache, YourCache>()For example, services.AddSingleton<ICertificateValidationCache, YourCache>().

選用用戶端憑證Optional client certificates

本節提供的資訊適用于必須使用憑證來保護應用程式子集的應用程式。This section provides information for apps that must protect a subset of the app with a certificate. 例如, Razor 應用程式中的頁面或控制器可能需要用戶端憑證。For example, a Razor Page or controller in the app might require client certificates. 這會以用戶端憑證的形式呈現挑戰:This presents challenges as client certificates:

  • 是 TLS 功能,而不是 HTTP 功能。Are a TLS feature, not an HTTP feature.
  • 會依連線進行協商,而且必須在任何 HTTP 資料可用之前,于連接開始時進行協商。Are negotiated per-connection and must be be negotiated at the start of the connection before any HTTP data is available. 在連接開始時,只會知道伺服器名稱指示 (SNI) † 。At the start of the connection, only the Server Name Indication (SNI)† is known. 用戶端和伺服器憑證會在連線上的第一個要求之前進行協商,要求通常無法重新進行協商。The client and server certificates are negotiated prior to the first request on a connection and requests generally aren't able to renegotiate.

TLS 重新協商是執行選用用戶端憑證的舊方式。TLS renegotiation was an old way to implement optional client certificates. 因為下列原因,所以不再建議您這樣做:This is no longer recommended because:

  • 在 HTTP/1.1 中,POST 要求期間的重新交涉可能會造成鎖死,要求主體填滿 TCP 視窗,而且無法接收重新協商封包。In HTTP/1.1, renegotiating during a POST request could cause a deadlock where the request body filled up the TCP window and the renegotiation packets can't be received.
  • HTTP/2 明確禁止 重新協商。HTTP/2 explicitly prohibits renegotiation.
  • TLS 1.3 已 移除 重新協商的支援。TLS 1.3 has removed support for renegotiation.

ASP.NET Core 5 preview 7 和更新版本為選用用戶端憑證新增更便利的支援。ASP.NET Core 5 preview 7 and later adds more convenient support for optional client certificates. 如需詳細資訊,請參閱 選用的憑證範例For more information, see the Optional certificates sample.

下列方法支援選用用戶端憑證:The following approach supports optional client certificates:

  • 針對需要用戶端憑證且沒有用戶端憑證的 web 應用程式要求:For requests to the web app that require a client certificate and don't have one:
    • 使用受用戶端憑證保護的子域重新導向至相同的頁面。Redirect to the same page using the client certificate protected subdomain.
    • 例如,重新導向至 myClient.contoso.com/requestedPageFor example, redirect to myClient.contoso.com/requestedPage. 因為的要求與 myClient.contoso.com/requestedPage 不同的主機名稱 contoso.com/requestedPage ,用戶端會建立不同的連線,並提供用戶端憑證。Because the request to myClient.contoso.com/requestedPage is a different hostname than contoso.com/requestedPage, the client establishes a different connection and the client certificate is provided.
    • 如需詳細資訊,請參閱ASP.NET Core 的授權簡介For more information, see ASP.NET Core 的授權簡介.

針對 此 GitHub 討論 問題中的選用用戶端憑證留下問題、留言和其他意見反應。Leave questions, comments, and other feedback on optional client certificates in this GitHub discussion issue.

† 伺服器名稱指示 (SNI) 是 TLS 延伸模組,可將虛擬網域納入 SSL 協商的一部分。† Server Name Indication (SNI) is a TLS extension to include a virtual domain as a part of SSL negotiation. 這實際上是指虛擬功能變數名稱(或主機名稱)可以用來識別網路端點。This effectively means the virtual domain name, or a hostname, can be used to identify the network end point.