如何使用客户端证书在 Web API 中进行身份验证和授权

2024-02-27

我尝试使用客户端证书对使用 Web API 的设备进行身份验证和授权,并开发了一个简单的概念证明来解决潜在解决方案的问题。我遇到了 Web 应用程序未收到客户端证书的问题。许多人报告了这个问题,包括在这个问答中 https://stackoverflow.com/questions/22197762/webapi-httpclient-not-sending-client-certificate,但他们都没有答案。我的希望是提供更多细节来恢复这个问题,并希望得到我的问题的答案。我对其他解决方案持开放态度。主要要求是用 C# 编写的独立进程可以调用 Web API 并使用客户端证书进行身份验证。

此 POC 中的 Web API 非常简单,仅返回单个值。它使用一个属性来验证是否使用了 HTTPS 以及是否存在客户端证书。

public class SecureController : ApiController
{
    [RequireHttps]
    public string Get(int id)
    {
        return "value";
    }

}

以下是 RequireHttps 属性的代码:

public class RequireHttpsAttribute : 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 
        {
            var cert = actionContext.Request.GetClientCertificate();
            if (cert == null)
            {
                actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
                {
                    ReasonPhrase = "Client Certificate Required"
                }; 

            }
            base.OnAuthorization(actionContext); 
        } 
    } 
}

在此 POC 中,我只是检查客户端证书的可用性。一旦此功能生效,我可以添加对证书中信息的检查,以根据证书列表进行验证。

以下是 IIS 中针对此 Web 应用程序的 SSL 设置。

以下是发送带有客户端证书的请求的客户端的代码。这是一个控制台应用程序。

    private static async Task SendRequestUsingHttpClient()
    {
        WebRequestHandler handler = new WebRequestHandler();
        X509Certificate certificate = GetCert("ClientCertificate.cer");
        handler.ClientCertificates.Add(certificate);
        handler.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
        handler.ClientCertificateOptions = ClientCertificateOption.Manual;
        using (var client = new HttpClient(handler))
        {
            client.BaseAddress = new Uri("https://localhost:44398/");
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            HttpResponseMessage response = await client.GetAsync("api/Secure/1");
            if (response.IsSuccessStatusCode)
            {
                string content = await response.Content.ReadAsStringAsync();
                Console.WriteLine("Received response: {0}",content);
            }
            else
            {
                Console.WriteLine("Error, received status code {0}: {1}", response.StatusCode, response.ReasonPhrase);
            }
        }
    }

    public static bool ValidateServerCertificate(
      object sender,
      X509Certificate certificate,
      X509Chain chain,
      SslPolicyErrors sslPolicyErrors)
    {
        Console.WriteLine("Validating certificate {0}", certificate.Issuer);
        if (sslPolicyErrors == SslPolicyErrors.None)
            return true;

        Console.WriteLine("Certificate error: {0}", sslPolicyErrors);

        // Do not allow this client to communicate with unauthenticated servers.
        return false;
    }

当我运行此测试应用程序时,我收到状态代码 403 Forbidden,原因短语为“需要客户端证书”,表明它正在进入我的 RequireHttpsAttribute 并且未找到任何客户端证书。通过调试器运行它,我已经验证了证书已加载并添加到 WebRequestHandler 中。证书将导出到正在加载的 CER 文件中。带有私钥的完整证书位于 Web 应用程序服务器的本地计算机的个人和受信任根存储中。对于此测试,客户端和 Web 应用程序在同一台计算机上运行。

我可以使用 Fiddler 调用此 Web API 方法,附加相同的客户端证书,并且效果很好。使用 Fiddler 时,它通过 RequireHttpsAttribute 中的测试并返回成功状态代码 200 并返回预期值。

有人遇到过 HttpClient 不在请求中发送客户端证书的相同问题并找到解决方案吗?

更新1:

我还尝试从包含私钥的证书存储中获取证书。这是我检索它的方法:

    private static X509Certificate2 GetCert2(string hostname)
    {
        X509Store myX509Store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        myX509Store.Open(OpenFlags.ReadWrite);
        X509Certificate2 myCertificate = myX509Store.Certificates.OfType<X509Certificate2>().FirstOrDefault(cert => cert.GetNameInfo(X509NameType.SimpleName, false) == hostname);
        return myCertificate;
    }

我验证了该证书是否已正确检索,并且已添加到客户端证书集合中。但我得到了相同的结果,服务器代码没有检索任何客户端证书。

为了完整起见,这里是用于从文件检索证书的代码:

    private static X509Certificate GetCert(string filename)
    {
        X509Certificate Cert = X509Certificate.CreateFromCertFile(filename);
        return Cert;

    }

您会注意到,当您从文件中获取证书时,它返回一个 X509Certificate 类型的对象,而当您从证书存储中检索它时,它的类型为 X509Certificate2。 X509CertificateCollection.Add 方法需要 X509Certificate 类型。

更新2:我仍在试图解决这个问题,并尝试了许多不同的选择,但无济于事。

  • 我将 Web 应用程序更改为在主机名而不是本地主机上运行。
  • 我将 Web 应用程序设置为需要 SSL
  • 我验证了证书已设置为客户端身份验证并且位于受信任的根中
  • 除了在 Fiddler 中测试客户端证书之外,我还在 Chrome 中验证了它。

在尝试这些选项的过程中,有一次它开始起作用。然后我开始撤销更改,看看是什么导致它起作用。它继续工作。然后我尝试从受信任的根中删除证书以验证这是必需的,但它停止工作,现在即使我将证书放回受信任的根中,我也无法使其恢复工作。现在 Chrome 甚至不会提示我输入它所使用的证书,并且在 Chrome 中失败,但在 Fiddler 中仍然有效。我一定缺少一些神奇的配置。

我还尝试在绑定中启用“协商客户端证书”,但 Chrome 仍然不会提示我输入客户端证书。这是使用“netsh http show sslcert”的设置

 IP:port                 : 0.0.0.0:44398
 Certificate Hash        : 429e090db21e14344aa5d75d25074712f120f65f
 Application ID          : {4dc3e181-e14b-4a21-b022-59fc669b0914}
 Certificate Store Name  : MY
 Verify Client Certificate Revocation    : Disabled
 Verify Revocation Using Cached Client Certificate Only    : Disabled
 Usage Check    : Enabled
 Revocation Freshness Time : 0
 URL Retrieval Timeout   : 0
 Ctl Identifier          : (null)
 Ctl Store Name          : (null)
 DS Mapper Usage    : Disabled
 Negotiate Client Certificate    : Enabled

这是我正在使用的客户端证书:

我对问题所在感到困惑。我正在为任何可以帮助我解决这个问题的人添加赏金。


跟踪帮助我找到了问题所在(谢谢 Fabian 的建议)。经过进一步测试,我发现我可以让客户端证书在另一台服务器(Windows Server 2012)上工作。我正在我的开发机器(Windows 7)上对此进行测试,以便我可以调试此过程。因此,通过将跟踪与正常运行的 IIS 服务器和未正常运行的 IIS 服务器进行比较,我能够查明跟踪日志中的相关行。以下是客户端证书工作的日志的一部分。这是发送前的设置

System.Net Information: 0 : [17444] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=CredentialsNeeded).
System.Net Information: 0 : [17444] SecureChannel#54718731 - We have user-provided certificates. The server has not specified any issuers, so try all the certificates.
System.Net Information: 0 : [17444] SecureChannel#54718731 - Selected certificate:

以下是客户端证书失败的计算机上的跟踪日志的样子。

System.Net Information: 0 : [19616] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=CredentialsNeeded).
System.Net Information: 0 : [19616] SecureChannel#54718731 - We have user-provided certificates. The server has specified 137 issuer(s). Looking for certificates that match any of the issuers.
System.Net Information: 0 : [19616] SecureChannel#54718731 - Left with 0 client certificates to choose from.
System.Net Information: 0 : [19616] Using the cached credential handle.

关注指示服务器指定 137 个发行者的行,我发现了这一点与我的问题相似的问答 https://stackoverflow.com/questions/9858275/net-application-fails-to-send-client-certificate-win-7-vs-win-xp。对我来说,解决方案不是标记为答案的解决方案,因为我的证书位于受信任的根中。答案是其下的那个 https://stackoverflow.com/a/19502296/722393更新注册表的位置。我刚刚将该值添加到注册表项中。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL

值名称:SendTrustedIssuerList 值类型:REG_DWORD 值数据:0 (False)

将此值添加到注册表后,它开始在我的 Windows 7 计算机上运行。这似乎是 Windows 7 的问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用客户端证书在 Web API 中进行身份验证和授权 的相关文章

随机推荐

  • 无法在设备“emulator-5554”上安装 apk:超时

    我是 Android 开发新手 我尝试在此文件夹中运行 LunarLander 项目 根据示例创建新项目 C Program Files Android android sdk windows samples android 9 Lunar
  • 在 Android 10 上以编程方式断开 Wifi

    I use WifiNetworkSuggestion以编程方式将我的应用程序连接到我的热点 但是当我尝试以编程方式断开此连接时 我使用了removeNetworkSuggestions就像文档提到的那样 删除之前的部分或全部网络建议 由应
  • 在 PHP 中管理巨型数组

    我正在为某人对数百万条旧日志条目进行数据挖掘 并且真的想在这件事上使用 PHP 来呈现这些材料 并将它们轻松链接到现有的 PHP 系统 我在终端 OSX 10 8 的 PHP 5 4 4 中运行此代码 Settings ini set er
  • 如何在 R 中显式调用函数参数的默认值?

    如何告诉 R 使用函数参数的默认值 而无需 i 省略函数调用中的参数以及 ii 不知道默认值是什么 我知道我可以使用默认值mean in rnorm rnorm n 100 by omitting the argument or rnorm
  • 如何将 FormsAuthentication cookie 添加到 HttpClient HttpRequestMessage

    我试图通过调用 FormsAuthentication SetAuthCookie someUser false 来验证内部集成测试之后 我确实需要调用 WebAPI 并且不会收到未经授权的异常 因为我已经应用了授权属性 我正在使用此代码来
  • Ruby Koans:为什么将符号列表转换为字符串

    我指的是 Ruby Koans 中 about symbols rb 中的这个测试https github com edgecase ruby koans blob master src about symbols rb L26 https
  • 如何从 Perl 与 ClearCase 交互?

    我的项目需要使用 Excel 工作表中的 Perl 脚本从 ClearCase 数据中提取一些内容 这些内容是 通过给出两条特定的时间线或两条基线 该基线内关联的所有活动 列标题 活动 所有者 ID 列标题 所有者 特定活动中关联的所有元素
  • 插座之间的管道

    我有一个充当镜像的 C 服务器 输入的内容会输出到不同的套接字 现在 它将套接字读入缓冲区并将其写入另一个套接字 我想提高吞吐量 I ve 读东西 http www kegel com c10k html zerocopy about se
  • 点之间的欧几里得距离

    我在 numpy 中有一个点数组 points rand dim n points 我想 计算某个点与所有其他点之间的所有 l2 范数 欧几里得距离 计算所有成对距离 最好都是 numpy 而没有 for 一个人怎样才能做到呢 如果您愿意使
  • PropertyPlaceholderConfigurer 从 XML 文件读取(Apache Commons 配置)

    是否可以配置 Spring PropertyPlaceholderConfigurer 来读取 properties xml 通过 Apache Commons 配置 我在帮助下找到了解决方案seanizer https stackover
  • 如何使用nodejs禁用Chrome的会话恢复警告?

    如何通过 NodeJS 在 Windows 中重新启动 Chromium Google Chrome 信息亭模式 以便它在重新启动时正常启动浏览器 就像普通人使用它一样 当我每次重新启动 Chromium Google chrome 时使用
  • 图像周围出现尴尬的线条

    可能最容易用图像来解释我想要什么 当我浮动图像时 文本围绕它运行 这很棒 但是 根据文本量和图像大小 我经常会遇到这些尴尬的情况 在这种情况下 尴尬的文本在图像旁边的列中看起来会更好 I could根据有多少尴尬的文本为图像添加更多的底部边
  • 何时选择在 SSIS 的 Lookup 组件中进行缓存

    在SSIS查找中有3种类型的缓存 完整 部分和无缓存 在我们的解决方案中 它一直使用默认的 完整 是否有任何特定的场景 可以使用部分缓存 无缓存 在我们的解决方案中 锁定表总是很小 例如 我们一直在查看小表来获取类型或获取描述 这可能是它在
  • 按最新排序,但按另一个 ID 列放在一起

    我正在尝试进行一些排序并保持在一起 不是真正的分组 工作 在我的示例数据中 我想将 DealerID 保留在一起 按 IsPrimaryDealer DESC 排序 但按最新条目显示经销商组 好吧 也许是分组 结果集 2 是最接近的 但 G
  • 为ListView自定义CheckedTextView

    据我所知 ListView嵌入了CheckedTextView来形成列表 但是每个CheckedTextView只有一个TextView和一个CheckBox 我想要做的是将一些 TextView 添加到 CheckedTextView 中
  • 使用 PostgreSQL 在 WITH(CTE) 中创建

    我正在尝试使用 PostgreSQL 中的函数在WITH 中创建临时表 Example with mm as select from test create table xyz as select from mm Note 在创建附近出现错
  • 在 Django 模板中执行 Javascript 和 css

    我正在 Django 应用程序中通过 Weasyprint 将 HTML 导出为 PDF 我注意到 如果我将模板 html 发送到前端并将该 html 返回到后端以将其导出为 pdf 它会完美打印 但如果我直接将模板 html 发送到 We
  • 如何在 C++ 中的 while 循环中存储先前的迭代?

    我看到有一个类似标题的答案 但内容对我来说太密集了 因为我不太了解 C 我对编程非常陌生 我不知道如何在 while 循环中存储先前的迭代 我正在尝试使用 while 循环将用户文本写入文件 并以两个结束输入 n人物 这就是我的问题所在 因
  • 当我添加到数组时,svelte 列表不会更新

    我刚刚开始使用 svelte 所以 这可能是一个菜鸟问题 我有一个列表 我可以从数组中删除项目 并且列表 each 更新没有问题 但是如果我将一个项目添加到数组中 列表不会重新绘制 直到我删除另一个项目 https svelte dev r
  • 如何使用客户端证书在 Web API 中进行身份验证和授权

    我尝试使用客户端证书对使用 Web API 的设备进行身份验证和授权 并开发了一个简单的概念证明来解决潜在解决方案的问题 我遇到了 Web 应用程序未收到客户端证书的问题 许多人报告了这个问题 包括在这个问答中 https stackove