首页 > 解决方案 > OnAuthorization 中没有证书(HttpActionContext actionContext)

问题描述

我从这个示例中创建了 CertificateTestController 和 ValuesController 如何使用客户端证书在 Web API 中进行身份验证和授权。如果您从用户 Ogglas 向下滚动到“更新”。我以他为例,让“CertificateTestController”工作,我可以从我的商店获取证书并将其添加到“处理程序”中。当我调用“ValuesController”时,没有证书被初始化

X509Certificate2 cert = actionContext.Request.GetClientCertificate();

这是我拥有的完整代码

值控制器

{
    [RequireSpecificCert]
    public class ValuesController : ApiController
    {
        // GET api/values
        public IHttpActionResult Get()
        {
            return Ok("It works!");
        }

        public class RequireSpecificCertAttribute : AuthorizationFilterAttribute
        {
            public override void OnAuthorization(HttpActionContext actionContext)
            {

                if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
                {
                    actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
                    {
                        ReasonPhrase = "HTTPS Required"
                    };
                }
                else
                {
                    X509Certificate2 cert = actionContext.Request.GetClientCertificate();
                    X509Certificate2 cert2 = actionContext.RequestContext.ClientCertificate;

                    if (cert == null)
                    {
                        actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
                        {
                            ReasonPhrase = "Client Certificate Required"
                        };
                    }
                    else
                    {
                        X509Chain chain = new X509Chain();

                        //Needed because the error "The revocation function was unable to check revocation for the certificate" happened to me otherwise
                        chain.ChainPolicy = new X509ChainPolicy()
                        {
                            RevocationMode = X509RevocationMode.NoCheck,
                        };
                        try
                        {
                            var chainBuilt = chain.Build(cert);
                            Debug.WriteLine(string.Format("Chain building status: {0}", chainBuilt));

                            var validCert = CheckCertificate(chain, cert);

                            if (chainBuilt == false || validCert == false)
                            {
                                actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
                                {
                                    ReasonPhrase = "Client Certificate not valid"
                                };
                                foreach (X509ChainStatus chainStatus in chain.ChainStatus)
                                {
                                    Debug.WriteLine(string.Format("Chain error: {0} {1}", chainStatus.Status, chainStatus.StatusInformation));
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            Debug.WriteLine(ex.ToString());
                        }
                    }

                    base.OnAuthorization(actionContext);
                }
            }

            private bool CheckCertificate(X509Chain chain, X509Certificate2 cert)
            {
                var rootThumbprint = WebConfigurationManager.AppSettings["rootThumbprint"].ToUpper().Replace(" ", string.Empty);

                var clientThumbprint = WebConfigurationManager.AppSettings["clientThumbprint"].ToUpper().Replace(" ", string.Empty);

                //Check that the certificate have been issued by a specific Root Certificate
                var validRoot = chain.ChainElements.Cast<X509ChainElement>().Any(x => x.Certificate.Thumbprint.Equals(rootThumbprint, StringComparison.InvariantCultureIgnoreCase));

                //Check that the certificate thumbprint matches our expected thumbprint
                var validCert = cert.Thumbprint.Equals(clientThumbprint, StringComparison.InvariantCultureIgnoreCase);

                return validRoot && validCert;
            }
        }

用下面的 CertificateTestController 调用上面的 ValuesController

{
    [RoutePrefix("api/certificatetest")]
    public class CertificateTestController : ApiController
    {
        public IHttpActionResult Get()
        {
            var handler = new WebRequestHandler();
            handler.ClientCertificateOptions = ClientCertificateOption.Manual;
            handler.ClientCertificates.Add(GetClientCert());
            handler.UseProxy = false;
            var client = new HttpClient(handler);
            var result = client.GetAsync("https://localhost:44301//values").GetAwaiter().GetResult();
            var resultString = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
            return Ok(resultString);
        }

        private static X509Certificate GetClientCert()
        {
            X509Store store = null;
            try
            {
                store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
                store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);

                var certificateSerialNumber = "2bc034466b6960d2fee84d86e6c2532a".ToUpper().Replace(" ", string.Empty);

                var cert = store.Certificates.Cast<X509Certificate>().FirstOrDefault(x => x.GetSerialNumberString().Equals(certificateSerialNumber, StringComparison.InvariantCultureIgnoreCase));
                return cert;
            }
            finally
            {
                store.Close();
            }
        }
    }
}

请帮忙!

标签: c#securityasp.net-web-apissl-certificateclient-certificates

解决方案


以下是我在尝试解决此问题时回答的问题/问题。

Q1。

为什么我的证书没有到达客户端(代码)?

A1。

VS 会在 OnAuthorization(HttpActionContext actionContext) 被命中之前进行初始 SSL 协商。此时,服务器在证书存储中搜索安装了私钥以进行验证的客户端证书。如果没有私钥,则失败。我通过打开详细信息找到了问题。请看下文。

以下是需要更改才能使其正常工作的配置。

1. Web Config 文件更改

• SSL 协商

下面的配置告诉 Visual Studio,“api/values” URL 需要一个 ssl 协商。这有助于我们限制预期证书协商的时间和地点。位置路径属性

<location path="api/values">
  <system.webServer>
     <security>
        <access sslFlags="SslNegotiateCert" />
        <authentication>
          <iisClientCertificateMappingAuthentication enabled="true">
          </iisClientCertificateMappingAuthentication>
        </authentication>
      </security>
    </system.webServer>
  </location>

• 证书详细

下面的诊断有助于我们进行故障排除,并找出证书验证可能导致的任何问题。

<system.diagnostics>
    <trace autoflush="true" />
    <sources>
      <source name="System.Net">
        <listeners>
          <add name="System.Net"/>
        </listeners>
      </source>
      <source name="System.Net.HttpListener">
        <listeners>
          <add name="System.Net"/>
        </listeners>
      </source>
      <source name="System.Net.Sockets">
        <listeners>
          <add name="System.Net"/>
        </listeners>
      </source>
      <source name="System.Net.Cache">
        <listeners>
          <add name="System.Net"/>
        </listeners>
      </source>
    </sources>
    <sharedListeners>
      <add name="System.Net"
       type="System.Diagnostics.TextWriterTraceListener"
       initializeData="System.Net.trace.log"
       traceOutputOptions = "ProcessId, DateTime"/>
    </sharedListeners>
    <switches>
      <add name="System.Net" value="Verbose" />
      <add name="System.Net.Sockets" value="Verbose" />
      <add name="System.Net.Cache" value="Verbose" />
      <add name="System.Net.HttpListener" value="Verbose" />
    </switches>
  </system.diagnostics>

• 应用设置

将 rootThumbprint 值更改为任何服务器证书的指纹,而 clientThumprint 将是任何客户端证书指纹。certificateSerialNumber 应该是传出证书序列号。

<appSettings>
            <add key="webpages:Version" value="3.0.0.0" />
            <add key="webpages:Enabled" value="false" />
            <add key="ClientValidationEnabled" value="true" />
            <add key="UnobtrusiveJavaScriptEnabled" value="true" />
            <add key="rootThumbprint" value="change"/>
            <add key="clientThumbprint" value="change"/>
<add key="certificateSerialNumber" value="change"/>
</appSettings>

2.本地Visual Studio和IIS express

1) 应用程序主机.config

 路径 Visual Studio 早于 2015 年但在 2008 年之后位置 --> c:\Users\e#\Documents\IISExpress\Config Visual Studio 2015 & 2017 {project_name}.vs\config\config.host.config
 更改确保以下属性设置为“允许”

2) 启用 SSL

在 Visual Studio 属性窗口中启用 SSL。这很重要,因为 URL 从 http 变为 https。

在此处输入图像描述

3) WebApiConfig.cs

创建一个类过滤器文件夹。适当地命名它。在运行任何其他代码之前,证书验证需要成为第一个检查。这就是 WebApiConfig.cs 派上用场的地方。例如,我调用了我的类 RequireHttpsAttribute。为了运行检查,只需将以下行放入 WebApiConfig.cs config.Filters.Add(new RequireHttpsAttribute()); 在此处输入图像描述

4) 证书测试控制器.cs

这是类充当客户端。此类用于将证书附加到请求中并将其发送出去。这堂课有一个变化。这仅在计算机上进行了本地测试。var result = client.GetAsync(" https://localhost:44300//api//values ").GetAwaiter().GetResult(); 更改网址。 我们根据下面的序列号附上证书。证书也可以基于指纹、主题、过期/撤销、链验证等附加。

var certificateSerialNumber = WebConfigurationManager.AppSettings["certificateSerialNumber"].ToUpper().Replace(" ", string.Empty);

5) 值控制器.cs

此类充当服务器。这是证书验证发生的地方。引用 webApiConfig.cs 的“CheckCertificate”方法/函数有两个变化。如果您按照上面 WebApiConfig.cs 下的应用程序设置更改,那么您就可以开始了。

3. 文件

1) 证书测试控制器.cs

2) 值控制器.cs


推荐阅读