Unity IoC + WCF + wsHTTPBinding +证书授权

发布于 2024-12-02 12:16:59 字数 8244 浏览 1 评论 0原文

我在 IIS 上有两个应用程序需要开发。第一个 WCF 应用程序包含所有逻辑以及与数据库的通信(我们称之为服务器)。另一个 ASP.NET MVC 3 应用程序引用了 WCF 应用程序(我们称之为客户端)。

我在将 WCF web.config 配置与 Unity IoC 自定义服务主机和自定义行为混合时遇到问题。

当 Unity 完成所有配置后,它会创建简单的 BasicHttpBinding,但我的要求是通过证书授权确保其安全,因此我需要 wsHTTPBinding。

------------- 配置 BasicHttpBinding ------------

首先看一下 WCF 的常见 Unity 实现:

internal class UnityInstanceProvider :  IInstanceProvider
{
    private readonly IUnityContainer container;
    private readonly Type contractType;

    public UnityInstanceProvider(
        [NotNull] IUnityContainer container, 
        [NotNull] Type contractType)
    {
        this.container = container;
        this.contractType = contractType;
    }

    #region IInstanceProvider Members

    public object GetInstance(InstanceContext instanceContext)
    {
        return GetInstance(instanceContext, null);
    }

    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        return container.Resolve(contractType);
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
        container.Teardown(instance);
    }
}

internal class UnityServiceBehavior : IServiceBehavior
{
    private readonly IUnityContainer container;

    public UnityServiceBehavior(
        [NotNull] IUnityContainer container)
    {
        this.container = container;
    }

    #region IServiceBehavior Members

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    {

    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
        {
            foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
            {
                if (endpointDispatcher.ContractName != "IMetadataExchange")
                {
                    endpointDispatcher.DispatchRuntime.InstanceProvider = new UnityInstanceProvider(container, serviceDescription.ServiceType);



                }
            }
        }
    }

    #endregion
}

public class UnityServiceHost : ServiceHost
{
    private readonly IUnityContainer container;

    public UnityServiceHost(
        [NotNull] IUnityContainer container, 
        [NotNull] Type serviceType, 
        Uri[] baseAddresses)
        : base(serviceType, baseAddresses)
    {
        this.container = container;
    }

    protected override void OnOpening()
    {
        base.OnOpening();
        if (Description.Behaviors.Find<UnityServiceBehavior>() == null)
        {
            Description.Behaviors.Add(new UnityServiceBehavior(container));
        }
    }
}

public class UnityServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        IUnityContainer container = new UnityContainer();
        UnityContainerConfigurator.Configure(container);
        return new UnityServiceHost(container, serviceType, baseAddresses);
    }
}

WCF 应用程序 web.config 仅包含基本信息: 没有端点,没有服务定义。

现在想象一下我们有 SecurityService 的定义:

<%@ ServiceHost Language="C#" Debug="true" 
Service="myNamespace.SecurityService" 
Factory="myNamespace.UnityServiceHostFactory" %>

现在我可以将 SecurityService 的服务引用添加到我的客户端。 这一步它在客户端 web.config 中生成:

<basicHttpBinding>
<binding name="BasicHttpBinding_ISecurityService" 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="None">
  <transport clientCredentialType="None" proxyCredentialType="None"
   realm="" />
  <message clientCredentialType="UserName" algorithmSuite="Default" />
 </security>
</binding>

<endpoint address="http://localhost/wcf-app/SecurityService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ISecurityService"
contract="SecurityServiceReference.ISecurityService" name="BasicHttpBinding_ISecurityService" />

此时,我为 Unity 进行了配置:

container.RegisterType<SecurityServiceClient>(new InjectionConstructor());

在客户端应用程序中,我可以简单地使用它(我在这里不提及构造函数注入):

var securityService = DependencyResolver.Current.GetService<SecurityServiceClient>();

这一切都有效!但如果我想使用 wsHTTPBinding 就不会...

------------- 配置 wsHTTPBinding ------------

为了启用 wsHTTPBinding 我在 web 中配置了它WCF 应用程序的 .config。作为 BasicHttpBinding 的其余部分,它不包含有关绑定、端点等的任何信息。

但现在对于 wsHTTPBinding 我添加了:

<bindings>
  <wsHttpBinding>
    <binding name="wsHttpEndpointBinding">
      <security>
        <message clientCredentialType="Certificate" />
      </security>
    </binding>
  </wsHttpBinding>
</bindings>

<services>   
  <service behaviorConfiguration="ServiceBehavior" name="myNamespace.SecurityService">
    <endpoint address="" binding="wsHttpBinding"
      bindingConfiguration="wsHttpEndpointBinding"
      name="wsHttpEndpoint" contract="myNamespace.ISecurityService">
    </endpoint>
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
  </service>
</services>

<behaviors>
  <serviceBehaviors>
    <behavior name="ServiceBehavior">
      <serviceMetadata httpGetEnabled="true" />
      <serviceDebug includeExceptionDetailInFaults="false" />
      <serviceCredentials>
        <serviceCertificate findValue="CN=myClientCert" />
      </serviceCredentials>
    </behavior>
  </serviceBehaviors>
</behaviors>

在向客户端应用程序添加服务引用后,它会生成:

<wsHttpBinding>
<binding name="wsHttpEndpoint" closeTimeout="00:01:00" openTimeout="00:01:00"
 receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false"
 transactionFlow="false" hostNameComparisonMode="StrongWildcard"
 maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text"
 textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
 <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
  maxBytesPerRead="4096" maxNameTableCharCount="16384" />
 <reliableSession ordered="true" inactivityTimeout="00:10:00"
  enabled="false" />
 <security mode="Message">
  <transport clientCredentialType="Windows" proxyCredentialType="None"
   realm="" />
  <message clientCredentialType="Certificate" negotiateServiceCredential="true"
   algorithmSuite="Default" />
 </security>
</binding>

我手动添加了behaviorConfiguration="CertBehavior",即:

<behaviors>
 <endpointBehaviors>
   <behavior name="CertBehavior">
     <clientCredentials>
       <clientCertificate findValue="CN=myClientCert"/>
     </clientCredentials>
   </behavior>
 </endpointBehaviors>

现在,当我想使用 Unity 解决它时:

var securityService = DependencyResolver.Current.GetService<SecurityServiceClient>();

我总是为 null...

当我简单地创建实例时,有趣的是:

var client = new SecurityServiceReference.SecurityServiceClient();

它工作正常...所以可以肯定问题与错误的 wsHttpBinding 配置无关,而是结合 Unity +来自 web.config 的 wsHttpBinding...

任何人都可以帮助我解决这个问题吗?

丹尼尔

I have two applications on IIS for development. First WCF application that contains all the logic and communication with database (we call this Server). And another ASP.NET MVC 3 application that has reference to WCF application (we call this Client).

I have issue connected with mixing WCF web.config configuration with Unity IoC custom service host and custom behavior.

When all configuration is done by Unity it creates simple BasicHttpBinding, but my requirement is to make it secure with Certificate Authorization, so I need wsHTTPBinding.

------------- Configuring for BasicHttpBinding ------------

At the beginning look at common Unity implementation for WCF:

internal class UnityInstanceProvider :  IInstanceProvider
{
    private readonly IUnityContainer container;
    private readonly Type contractType;

    public UnityInstanceProvider(
        [NotNull] IUnityContainer container, 
        [NotNull] Type contractType)
    {
        this.container = container;
        this.contractType = contractType;
    }

    #region IInstanceProvider Members

    public object GetInstance(InstanceContext instanceContext)
    {
        return GetInstance(instanceContext, null);
    }

    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        return container.Resolve(contractType);
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
        container.Teardown(instance);
    }
}

internal class UnityServiceBehavior : IServiceBehavior
{
    private readonly IUnityContainer container;

    public UnityServiceBehavior(
        [NotNull] IUnityContainer container)
    {
        this.container = container;
    }

    #region IServiceBehavior Members

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    {

    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
        {
            foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
            {
                if (endpointDispatcher.ContractName != "IMetadataExchange")
                {
                    endpointDispatcher.DispatchRuntime.InstanceProvider = new UnityInstanceProvider(container, serviceDescription.ServiceType);



                }
            }
        }
    }

    #endregion
}

public class UnityServiceHost : ServiceHost
{
    private readonly IUnityContainer container;

    public UnityServiceHost(
        [NotNull] IUnityContainer container, 
        [NotNull] Type serviceType, 
        Uri[] baseAddresses)
        : base(serviceType, baseAddresses)
    {
        this.container = container;
    }

    protected override void OnOpening()
    {
        base.OnOpening();
        if (Description.Behaviors.Find<UnityServiceBehavior>() == null)
        {
            Description.Behaviors.Add(new UnityServiceBehavior(container));
        }
    }
}

public class UnityServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        IUnityContainer container = new UnityContainer();
        UnityContainerConfigurator.Configure(container);
        return new UnityServiceHost(container, serviceType, baseAddresses);
    }
}

WCF application web.config contains only basic information:
None endpoints, none service definition.

Now imagine that we have SecurityService with definition:

<%@ ServiceHost Language="C#" Debug="true" 
Service="myNamespace.SecurityService" 
Factory="myNamespace.UnityServiceHostFactory" %>

Now I can add service reference to SecurityService to my Client.
A this step it generates in client web.config:

<basicHttpBinding>
<binding name="BasicHttpBinding_ISecurityService" 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="None">
  <transport clientCredentialType="None" proxyCredentialType="None"
   realm="" />
  <message clientCredentialType="UserName" algorithmSuite="Default" />
 </security>
</binding>

<endpoint address="http://localhost/wcf-app/SecurityService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ISecurityService"
contract="SecurityServiceReference.ISecurityService" name="BasicHttpBinding_ISecurityService" />

At this point I configure this for Unity:

container.RegisterType<SecurityServiceClient>(new InjectionConstructor());

And in Client application I can use it simply by (I don't mention here constructor injection):

var securityService = DependencyResolver.Current.GetService<SecurityServiceClient>();

And this all works! But It don't if I want to use wsHTTPBinding ...

------------- Configuring for wsHTTPBinding ------------

To enable wsHTTPBinding I configured it inside web.config of WCF Application. As a remainder for BasicHttpBinding it didn't contained any information concerning binding, endpoin, etc.

But now for wsHTTPBinding I added:

<bindings>
  <wsHttpBinding>
    <binding name="wsHttpEndpointBinding">
      <security>
        <message clientCredentialType="Certificate" />
      </security>
    </binding>
  </wsHttpBinding>
</bindings>

<services>   
  <service behaviorConfiguration="ServiceBehavior" name="myNamespace.SecurityService">
    <endpoint address="" binding="wsHttpBinding"
      bindingConfiguration="wsHttpEndpointBinding"
      name="wsHttpEndpoint" contract="myNamespace.ISecurityService">
    </endpoint>
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
  </service>
</services>

<behaviors>
  <serviceBehaviors>
    <behavior name="ServiceBehavior">
      <serviceMetadata httpGetEnabled="true" />
      <serviceDebug includeExceptionDetailInFaults="false" />
      <serviceCredentials>
        <serviceCertificate findValue="CN=myClientCert" />
      </serviceCredentials>
    </behavior>
  </serviceBehaviors>
</behaviors>

And after adding service reference to Client application it generates:

<wsHttpBinding>
<binding name="wsHttpEndpoint" closeTimeout="00:01:00" openTimeout="00:01:00"
 receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false"
 transactionFlow="false" hostNameComparisonMode="StrongWildcard"
 maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text"
 textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
 <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
  maxBytesPerRead="4096" maxNameTableCharCount="16384" />
 <reliableSession ordered="true" inactivityTimeout="00:10:00"
  enabled="false" />
 <security mode="Message">
  <transport clientCredentialType="Windows" proxyCredentialType="None"
   realm="" />
  <message clientCredentialType="Certificate" negotiateServiceCredential="true"
   algorithmSuite="Default" />
 </security>
</binding>

I manually added behaviorConfiguration="CertBehavior", that is:

<behaviors>
 <endpointBehaviors>
   <behavior name="CertBehavior">
     <clientCredentials>
       <clientCertificate findValue="CN=myClientCert"/>
     </clientCredentials>
   </behavior>
 </endpointBehaviors>

And now when I want to resolve it using Unity:

var securityService = DependencyResolver.Current.GetService<SecurityServiceClient>();

I get always null...

What is funny when I create simply instance by:

var client = new SecurityServiceReference.SecurityServiceClient();

It works fine... So for sure issue is not connected with wrong wsHttpBinding configuration but rather combining Unity + wsHttpBinding from web.config...

Can any one help me with this issue?

Daniel

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

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

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

无人接听 2024-12-09 12:16:59

好吧,我明白了。

拉迪斯拉夫,你是对的,它应该显示一个例外。
UnityDependencyResolver 只是捕获了它。

internal class UnityDependencyResolver : IDependencyResolver
{
    private readonly IUnityContainer container;

    public UnityDependencyResolver([NotNull] IUnityContainer container)
    {
        this.container = container;
    }

    #region IDependencyResolver Members

    public object GetService(Type serviceType)
    {
        try
        {
            return container.Resolve(serviceType);
        }
        catch
        {
            return null;
        }
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        try
        {
            return container.ResolveAll(serviceType);
        }
        catch
        {
            return new List<object>();
        }
    }

我还必须明确设置证书的证书位置:

<serviceCertificate findValue="CN=myClientCert"
                            storeName="My"
                            x509FindType="FindBySubjectDistinguishedName"
                            storeLocation="LocalMachine"
                            />

<clientCertificate findValue="CN=myClientCert" storeName="My"
                            x509FindType="FindBySubjectDistinguishedName"
                            storeLocation="LocalMachine"
                            />

现在它工作正常。

Ok, I figured it out.

Ladislav you were right that it should show an exception.
UnityDependencyResolver was simply catching it.

internal class UnityDependencyResolver : IDependencyResolver
{
    private readonly IUnityContainer container;

    public UnityDependencyResolver([NotNull] IUnityContainer container)
    {
        this.container = container;
    }

    #region IDependencyResolver Members

    public object GetService(Type serviceType)
    {
        try
        {
            return container.Resolve(serviceType);
        }
        catch
        {
            return null;
        }
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        try
        {
            return container.ResolveAll(serviceType);
        }
        catch
        {
            return new List<object>();
        }
    }

I also had to explicitly set certificate locations for the certificates:

<serviceCertificate findValue="CN=myClientCert"
                            storeName="My"
                            x509FindType="FindBySubjectDistinguishedName"
                            storeLocation="LocalMachine"
                            />

<clientCertificate findValue="CN=myClientCert" storeName="My"
                            x509FindType="FindBySubjectDistinguishedName"
                            storeLocation="LocalMachine"
                            />

Now it works fine.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文