代码之家  ›  专栏  ›  技术社区  ›  Brad Nabholz

为多个asp.net用户缓存wcf channelfactories

  •  4
  • Brad Nabholz  · 技术社区  · 16 年前

    我有一个企业系统,它被一些winforms客户端和一个面向公众的asp.net站点使用。后端wcf服务提供了多个服务供这些客户端使用。这些服务需要消息凭据,对于winforms应用程序,消息凭据是在程序首次启动时由用户提供的。

    为了提高性能,我在winform应用程序中缓存channelfactories。我想在asp.net网站上也这样做。但是,由于clientcredentials是作为工厂的一部分存储的( ChannelFactory<T>.Credentials ),是否需要为每个用户的每个服务缓存一个channelfactory?看来即使是适度使用也会迅速增加。另外,我相信我需要将它们存储在应用程序级别,而不是会话级别,因为为了将来的可伸缩性,我不能保证总是使用inproc会话状态。

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

    2 回复  |  直到 16 年前
        1
  •  5
  •   Community Mohan Dere    9 年前

    几个月后,我遇到了这个没有答案的问题,最后我可以给出答案。我用了一个非常类似于 ASP.NET Web Site + Windows Forms App + WCF Service: Client Credentials . 但我还是决定在这里写一篇我的方法。

    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 );
    

    我的解决方案是实现一个iaauthorizationpolicy,并将其用作servicehost的serviceauthorizationbehavior的一部分。此策略检查请求是否已由我的usernamepasswordvalidator实现进行身份验证,如果是,则使用已提供的标识创建新的iprincipal。如果请求由X509证书进行身份验证,我将在当前请求上查找一个消息头,指示模拟用户是谁,然后使用该用户名创建主体。我的授权策略。评估方法:

    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;
    }
    

    将策略添加到servicehost很简单:

    serviceHost.Authorization.ExternalAuthorizationPolicies = new List<IAuthorizationPolicy>() { new CustomAuthorizationPolicy() }.AsReadOnly();
    serviceHost.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.Custom;
    

    现在, ServiceSecurityContext.Current.PrimaryIdentity 是正确的,无论用户是如何进行身份验证的。这或多或少地解决了WCF服务一侧的重吊问题。在我的asp.net应用程序中,我必须设置适当的绑定(上面提到的certificatebinding),并创建我的channelfactory。但是,我向factory.endpoint.behavior s添加了一个新行为,以从中提取当前用户的标识 HttpContext.Current.User 并将其放入我的服务查找的wcf请求头中。这与实现iclientMessageInspector和使用beforeSendrequest一样简单(尽管在适当的情况下添加了空检查):

    public object BeforeSendRequest( ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel )
    {
        request.Headers.Add( MessageHeader.CreateHeader( "ImpersonatedUsername", "http://schemas.mycompany.com/MyProject", HttpContext.Current.User.Identity.Name ) );
    
        return null;
    }
    

    当然,我们仍然需要一个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 将iprincipal类或分配给 httpcontext.current.user 直接财产。此时,我不再需要使用usernamebinding的一次性channelfactory,我可以使用certificatebinding的一次性channelfactory,其中一个实例在我的asp.net应用程序中共享。通道工厂将从 httpcontext.current.user 并在以后的wcf请求中插入适当的头。

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

        2
  •  0
  •   Spence    16 年前

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

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

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

    我希望这个链接会有帮助: MSDN Delegation and Impersonation

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

    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";
        }
    }