C# 为多个ASP.NET用户缓存WCF ChannelFactory

C# 为多个ASP.NET用户缓存WCF ChannelFactory,c#,asp.net,wcf,caching,C#,Asp.net,Wcf,Caching,我有一个企业系统,由少数WinForms客户端和一个面向公众的ASP.NET站点使用。后端WCF服务提供若干服务,供这些客户机使用。这些服务需要消息凭据,在WinForms应用程序中,消息凭据是在程序首次启动时由用户提供的 我在WinForm应用程序中缓存ChannelFactorys以提高性能。我想在ASP.NET网站上也这样做。但是,由于ClientCredentials存储为工厂的一部分(ChannelFactory.Credentials),我是否需要为每个用户的每个服务缓存一个Chan

我有一个企业系统,由少数WinForms客户端和一个面向公众的ASP.NET站点使用。后端WCF服务提供若干服务,供这些客户机使用。这些服务需要消息凭据,在WinForms应用程序中,消息凭据是在程序首次启动时由用户提供的

我在WinForm应用程序中缓存ChannelFactorys以提高性能。我想在ASP.NET网站上也这样做。但是,由于ClientCredentials存储为工厂的一部分(
ChannelFactory.Credentials
),我是否需要为每个用户的每个服务缓存一个ChannelFactory?看来,即使是在适度使用下,这种情况也会迅速增加。此外,我相信我需要将它们存储在应用程序级别,而不是会话级别,因为对于未来的可伸缩性,我不能保证我将始终使用InProc会话状态


我看不出有任何方法可以为每个服务创建一个ChannelFactory,然后在创建通道时指定凭据。我遗漏了什么吗?

那么您正在使用客户端凭据建立WCF连接,因为它们已登录到服务器

我的想法是将WCF绑定设置为使用模拟。实际上,频道工厂可以作为您网站的标识或最低权限标识进行连接,如果标识不正确,WCF代码会导致服务拒绝呼叫

通过这种方式,您可以创建一个具有多个服务器连接的通道工厂,并且在进行WCF调用时,您可以简单地模拟已登录的用户(从内存中,您可以通过将模拟调用包装到using语句中来很好地实现这一点)

这个链接应该会有帮助,我希望:

此代码示例取自以下页面:

public class HelloService : IHelloService
{
    [OperationBehavior]
    public string Hello(string message)
    {
        WindowsIdentity callerWindowsIdentity =
        ServiceSecurityContext.Current.WindowsIdentity;
        if (callerWindowsIdentity == null)
        {
            throw new InvalidOperationException
            ("The caller cannot be mapped to a WindowsIdentity");
        }
        using (callerWindowsIdentity.Impersonate())
        {
           // Access a file as the caller.
        }
        return "Hello";
    }
}

几个月后,我偶然发现了这个没有答案的问题,最后我可以给出一个答案。我提出了一个与之非常相似的解决方案。但我还是决定在这里写下我的方法

WCF服务的常规(厚客户端)用户通过用户名/密码进行身份验证,web用户通过请求提供的头进行身份验证。可以信任此标头,因为web服务器本身使用WCF服务具有公钥的X509证书进行身份验证。因此,解决方案是在ASP.NET应用程序中有一个ChannelFactory,它将在请求中插入一个头,告诉WCF服务哪个用户实际发出请求

我为我的ServiceHost设置了两个端点,URL略有不同,绑定也有所不同。两个绑定都是TransportWithMessageCredential,但一个是消息凭据类型Username,另一个是证书

var usernameBinding = new BasicHttpBinding( BasicHttpSecurityMode.TransportWithMessageCredential )
usernameBinding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;

var certificateBinding = new BasicHttpBinding( BasicHttpSecurityMode.TransportWithMessageCredential )
certificateBinding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.Certificate;

var serviceHost = new ServiceHost( new MyService() );
serviceHost.Description.Namespace = "http://schemas.mycompany.com/MyProject";
serviceHost.AddServiceEndpoint( typeof( T ), usernameBinding, "https://myserver/MyProject/MyService" );
serviceHost.AddServiceEndpoint( typeof( T ), certificateBinding, "https://myserver/MyProject/Web/MyService" );
我使用a)服务器端证书、b)自定义用户名/密码验证器和c)客户端证书配置了ServiceCredentials对象。这有点令人困惑,因为WCF在默认情况下会尝试使用所有这些机制(a+B+C),即使一个端点设置为a+B,另一个端点配置为a+C

var serviceCredentials = new ServiceCredentials();
serviceCredentials.ServiceCertificate.SetCertificate( StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "myserver" );
serviceCredentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
serviceCredentials.UserNameAuthentication.CustomUserNamePasswordValidator = this.UserNamePasswordValidator;
serviceCredentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
serviceCredentials.ClientCertificate.SetCertificate( StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "SelfSignedWebsiteCertificate" );
serviceHost.Description.Behaviors.Add( serviceCredentials );
我的解决方案是实现IAuthorizationPolicy,并将其用作ServiceHost的ServiceAuthorizationBehavior的一部分。此策略检查请求是否已由我的UserNamePasswordValidator实现进行了身份验证,如果是,我将使用提供的标识创建一个新的IPrincipal。如果请求通过X509证书进行身份验证,我会在当前请求上查找一个消息头,指示模拟用户是谁,然后使用该用户名创建主体。我的iAuthorizationPolicy。评估方法:

public bool Evaluate( EvaluationContext evaluationContext, ref object state )
{
    var identity = ((List<IIdentity>) evaluationContext.Properties[ "Identities" ] ).First();

    if ( identity.AuthenticationType == "MyCustomUserNamePasswordValidator" )
    {
        evaluationContext.Properties[ "Principal" ] = new GenericPrincipal( identity, null );
    }
    else if ( identity.AuthenticationType == "X509" )
    {
        var impersonatedUsername = OperationContext.Current.IncomingMessageHeaders.GetHeader<string>( "ImpersonatedUsername", "http://schemas.mycompany.com/MyProject" );

        evaluationContext.AddClaimSet( this, new DefaultClaimSet( Claim.CreateNameClaim( impersonatedUsername ) ) );

        var impersonatedIdentity = new GenericIdentity( impersonatedUsername, "ImpersonatedUsername" );
        evaluationContext.Properties[ "Identities" ] = new List<IIdentity>() { impersonatedIdentity };
        evaluationContext.Properties[ "Principal" ] = new GenericPrincipal( identity, null );
    }
    else
        throw new Exception( "Bad identity" );

    return true;
}
当然,我们仍然需要IEndpointBehavior来添加消息检查器。可以使用引用检查器的固定实现;我选择使用泛型类:

public class GenericClientInspectorBehavior : IEndpointBehavior
{
    public IClientMessageInspector Inspector { get; private set; }

    public GenericClientInspectorBehavior( IClientMessageInspector inspector )
    { Inspector = inspector; }

    // Empty methods excluded for brevity

    public void ApplyClientBehavior( ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime )
    { clientRuntime.MessageInspectors.Add( Inspector ); }
}
最后是使ChannelFactory使用正确的客户端证书和端点行为的粘合剂:

factory.Credentials.ClientCertificate.SetCertificate( StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "SelfSignedWebsiteCertificate" );
factory.Endpoint.Behaviors.Add( new GenericClientInspectorBehavior( new HttpContextAuthenticationInspector() ) );
最后一点是如何确保设置了
HttpContext.Current.User
,并且用户实际上已经过身份验证。当用户尝试登录网站时,我创建了一个ChannelFactory,它使用我的usernameBinding,将提供的用户名/密码分配为ClientCredentials,并向我的WCF服务发出单个请求。如果请求成功,我知道用户的凭据是正确的

然后,我可以使用
FormsAuthentication
类或直接为
HttpContext.Current.User
属性分配一个IPrincipal。此时,我不再需要使用usernameBinding的单一使用ChannelFactory,我可以使用certificateBinding的单一使用ChannelFactory,其中一个实例在我的ASP.NET应用程序中共享。该ChannelFactory将从
HttpContext.current.user
中提取当前用户,并在将来的WCF请求中插入相应的头


因此,我需要在我的ASP.NET应用程序中为每个WCF服务创建一个ChannelFactory,并在每次用户登录时创建一个临时ChannelFactory。在我的情况下,该网站使用时间长,登录次数不多,因此这是一个很好的解决方案。

我认为这是正确的做法,即让网站本身进行身份验证(通过证书),然后本质上说“当我告诉你用户在做什么时请相信我。”我确实发现了这一点()我想这让我更接近了。我有很多其他的小问题(我们不是都有),所以这个网站暂时处于次要地位。
public class GenericClientInspectorBehavior : IEndpointBehavior
{
    public IClientMessageInspector Inspector { get; private set; }

    public GenericClientInspectorBehavior( IClientMessageInspector inspector )
    { Inspector = inspector; }

    // Empty methods excluded for brevity

    public void ApplyClientBehavior( ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime )
    { clientRuntime.MessageInspectors.Add( Inspector ); }
}
factory.Credentials.ClientCertificate.SetCertificate( StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "SelfSignedWebsiteCertificate" );
factory.Endpoint.Behaviors.Add( new GenericClientInspectorBehavior( new HttpContextAuthenticationInspector() ) );