WCFߝ从传入用户证书获取呼叫者身份

发布于 2025-01-18 12:23:10 字数 8345 浏览 5 评论 0原文

我有一个 WCF 项目,它将处理用户提交的信息。该服务将被托管,因此需要我们在调用该服务时向用户颁发客户端证书。

在服务中,我希望能够从传入证书中检索一些内容,这些内容可用于识别调用者以实现业务逻辑目的。

我相信我已经正确配置了 WCF 服务的 web.config 并正确配置了调用客户端的 app.config。在 WCF 服务中,我不知道如何从客户端调用者提供的证书中获取任何内容。下面的代码中包含我尝试过的一些方法。

我已经通过 Visual Studio 在 IIS Express 中以及通过我的开发盒上运行的常规 IIS 对其进行了测试。

这是 WCF 服务的代码(如果需要,可以提供 IMessagingService 的代码,但我认为这不是问题所在。):

using MessagingService.Contracts.Interfaces;
using System;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.Threading;

namespace MessagingService.WCFService
{
    public class MessagingService : IMessagingService
    {
        public MessageResponse ProcessMessage(MessageRequest request)
        {
            /* I need an identity of the calling client to make some business decision */

            var ID = GetClientId();

            /* Message Processing to occur here, code not relevent */

            return new MessageResponse { Result = "Success" };
        }

        private string GetClientId()
        {
            /*Both of these are empty*/
            var id1 = Thread.CurrentPrincipal.Identity;
            var id2 = ServiceSecurityContext.Current.PrimaryIdentity;

            /*I found a tutorial about possibly extracting the cert in this manner but this does not work ether*/
            Type x509IdentityType = id2.GetType();
            /*This field does not exist and so certificateField ends up being null*/
            FieldInfo certificateField = x509IdentityType.GetField("certificate", BindingFlags.Instance | BindingFlags.NonPublic);
            var cert = (X509Certificate2)certificateField.GetValue(id2);
            var id3 = cert.FriendlyName;


            return string.Empty;
        }
    }
}

这是该服务的 web.config 文件:

<?xml version="1.0"?>
<configuration>
    <appSettings>
        <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>
    </appSettings>
    <system.web>
        <compilation debug="true" targetFramework="4.7.2"/>
        <httpRuntime targetFramework="4.7.2"/>
    </system.web>
    <system.serviceModel>
        <protocolMapping>
            <add binding="basicHttpsBinding" scheme="https" />
        </protocolMapping>
        <bindings>
            <basicHttpBinding>
                <binding>
                    <security mode ="Message">
                        <message clientCredentialType="Certificate" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <behaviors>
            <serviceBehaviors>
                <behavior>
                    <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
                    <serviceDebug includeExceptionDetailInFaults="false"/>
                    <serviceCredentials useIdentityConfiguration="true">
                        <serviceCertificate findValue="localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
                        <clientCertificate>
                            <!--Development only setting. in PROD it will be ChainTrust-->
                            <authentication certificateValidationMode="PeerOrChainTrust" />
                        </clientCertificate>
                    </serviceCredentials>
                    <serviceAuthorization principalPermissionMode="Always" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    </system.serviceModel>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true"/>
        <directoryBrowse enabled="true"/>
    </system.webServer>
</configuration>

这是我的客户端调用者代码:

using MessagingService.Contracts.Interfaces;
using System.ServiceModel;

namespace MessagingService.WCFClient
{
    public class MessagingClient : ClientBase<IMessagingService>, IMessagingService
    {
        public MessagingClient()
        {

        }

        public MessageResponse ProcessMessage(MessageRequest request)
        {
            using (new OperationContextScope(this.InnerChannel))
            {
                return base.Channel.ProcessMessage(request);
            }
        }
    }
}

这里是进行调用的控制台应用程序:

using MessagingService.Contracts.Interfaces;
using System;

namespace MessagingService.WCFClient
{
    internal class Program
    {
        static void Main(string[] args)
        {
            /*This is here because I am dealing with self-sighed certs in my development environment - it wont be in PROD*/
            System.Net.ServicePointManager.ServerCertificateValidationCallback = (s, certificate, chain, sslPolicyErrors) => true;

            try
            {
                IMessagingService sender = new MessagingClient();
                var result = sender.ProcessMessage(new MessageRequest
                { 
                    Message = "Test Message"

                });

            }
            catch (Exception ex)
            {

                Console.WriteLine($"While Trying to call service\n{ex}");
                Console.ReadKey();
            }
        }
    }
}

最后,这是控制台应用程序的 app.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="basicHttpBinding_IMessaging" closeTimeout="00:01:00"
                         openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                         allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                         maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                         messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                                  maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
                    <security mode="Transport">
                        <transport clientCredentialType="Certificate"/>
                    </security>
                </binding>
            </basicHttpBinding>
            
        </bindings>
        <client>
            <endpoint address="https://localhost:44395/MessagingService.svc" binding="basicHttpBinding"
                      bindingConfiguration="basicHttpBinding_IMessaging" 
                      behaviorConfiguration="ClientCertificateBehavior" 
                      contract="MessagingService.Contracts.Interfaces.IMessagingService">
            </endpoint>
        </client>
        <behaviors>
            <endpointBehaviors>
                <behavior name="ClientCertificateBehavior">
                    <clientCredentials>
                        <clientCertificate findValue="[Thumbprint omitted but pulled from cert]" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint"/>
                        <serviceCertificate>
                            <!--Development only setting. in PROD it will be ChainTrust-->
                            <authentication certificateValidationMode="PeerOrChainTrust"/>
                        </serviceCertificate>
                    </clientCredentials>
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    </system.serviceModel>
</configuration>

I have a WCF project that will process information submitted from a user. The service will be hosted such that it will require client certificates that we will issue to the users to be supplied when the service is called.

Within the service, I’d like to be able to retrieve something from the incoming certificate that can be used to identify the caller for business logic purposes.

I believe I have configured the web.config of the WCF service correctly and have configure the app.config of the calling client correctly. From within the WCF service, I don’t know how to get anything form the certificate supplied from the client caller. Included in the code below are some of the approaches I have tried.

I have tested it both from within IIS Express via Visual Studio and via regular IIS running on my dev box.

Here is the code of the WCF service (The code for IMessagingService can be supplied if needed but I dont think that is where the issue is.):

using MessagingService.Contracts.Interfaces;
using System;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.Threading;

namespace MessagingService.WCFService
{
    public class MessagingService : IMessagingService
    {
        public MessageResponse ProcessMessage(MessageRequest request)
        {
            /* I need an identity of the calling client to make some business decision */

            var ID = GetClientId();

            /* Message Processing to occur here, code not relevent */

            return new MessageResponse { Result = "Success" };
        }

        private string GetClientId()
        {
            /*Both of these are empty*/
            var id1 = Thread.CurrentPrincipal.Identity;
            var id2 = ServiceSecurityContext.Current.PrimaryIdentity;

            /*I found a tutorial about possibly extracting the cert in this manner but this does not work ether*/
            Type x509IdentityType = id2.GetType();
            /*This field does not exist and so certificateField ends up being null*/
            FieldInfo certificateField = x509IdentityType.GetField("certificate", BindingFlags.Instance | BindingFlags.NonPublic);
            var cert = (X509Certificate2)certificateField.GetValue(id2);
            var id3 = cert.FriendlyName;


            return string.Empty;
        }
    }
}

Here is the web.config file for the service:

<?xml version="1.0"?>
<configuration>
    <appSettings>
        <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>
    </appSettings>
    <system.web>
        <compilation debug="true" targetFramework="4.7.2"/>
        <httpRuntime targetFramework="4.7.2"/>
    </system.web>
    <system.serviceModel>
        <protocolMapping>
            <add binding="basicHttpsBinding" scheme="https" />
        </protocolMapping>
        <bindings>
            <basicHttpBinding>
                <binding>
                    <security mode ="Message">
                        <message clientCredentialType="Certificate" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <behaviors>
            <serviceBehaviors>
                <behavior>
                    <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
                    <serviceDebug includeExceptionDetailInFaults="false"/>
                    <serviceCredentials useIdentityConfiguration="true">
                        <serviceCertificate findValue="localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
                        <clientCertificate>
                            <!--Development only setting. in PROD it will be ChainTrust-->
                            <authentication certificateValidationMode="PeerOrChainTrust" />
                        </clientCertificate>
                    </serviceCredentials>
                    <serviceAuthorization principalPermissionMode="Always" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    </system.serviceModel>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true"/>
        <directoryBrowse enabled="true"/>
    </system.webServer>
</configuration>

Here is my client caller code:

using MessagingService.Contracts.Interfaces;
using System.ServiceModel;

namespace MessagingService.WCFClient
{
    public class MessagingClient : ClientBase<IMessagingService>, IMessagingService
    {
        public MessagingClient()
        {

        }

        public MessageResponse ProcessMessage(MessageRequest request)
        {
            using (new OperationContextScope(this.InnerChannel))
            {
                return base.Channel.ProcessMessage(request);
            }
        }
    }
}

Here is console app that makes the call:

using MessagingService.Contracts.Interfaces;
using System;

namespace MessagingService.WCFClient
{
    internal class Program
    {
        static void Main(string[] args)
        {
            /*This is here because I am dealing with self-sighed certs in my development environment - it wont be in PROD*/
            System.Net.ServicePointManager.ServerCertificateValidationCallback = (s, certificate, chain, sslPolicyErrors) => true;

            try
            {
                IMessagingService sender = new MessagingClient();
                var result = sender.ProcessMessage(new MessageRequest
                { 
                    Message = "Test Message"

                });

            }
            catch (Exception ex)
            {

                Console.WriteLine(
quot;While Trying to call service\n{ex}");
                Console.ReadKey();
            }
        }
    }
}

And finally, here is the app.config of the console app:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="basicHttpBinding_IMessaging" closeTimeout="00:01:00"
                         openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                         allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                         maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                         messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                                  maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
                    <security mode="Transport">
                        <transport clientCredentialType="Certificate"/>
                    </security>
                </binding>
            </basicHttpBinding>
            
        </bindings>
        <client>
            <endpoint address="https://localhost:44395/MessagingService.svc" binding="basicHttpBinding"
                      bindingConfiguration="basicHttpBinding_IMessaging" 
                      behaviorConfiguration="ClientCertificateBehavior" 
                      contract="MessagingService.Contracts.Interfaces.IMessagingService">
            </endpoint>
        </client>
        <behaviors>
            <endpointBehaviors>
                <behavior name="ClientCertificateBehavior">
                    <clientCredentials>
                        <clientCertificate findValue="[Thumbprint omitted but pulled from cert]" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint"/>
                        <serviceCertificate>
                            <!--Development only setting. in PROD it will be ChainTrust-->
                            <authentication certificateValidationMode="PeerOrChainTrust"/>
                        </serviceCertificate>
                    </clientCredentials>
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    </system.serviceModel>
</configuration>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文