Bra*_*olz 4 c# asp.net wcf caching
我有一个企业系统,由少数WinForms客户端和面向公众的ASP.NET站点使用.后端WCF服务提供了每个客户端要使用的几种服务.这些服务需要消息凭据,在WinForms应用程序的情况下,在程序首次启动时由用户提供.
我在WinForm应用程序中缓存ChannelFactories以提高性能.我想在ASP.NET站点上做同样的事情.但是,由于ClientCredentials存储为factory(ChannelFactory<T>.Credentials)的一部分,我是否需要为每个用户的每个服务缓存一个ChannelFactory?似乎即使在适度使用下也会很快加起来.另外,我相信我需要将它们存储在应用程序级别,而不是会话级别,因为为了将来的可伸缩性,我无法保证我将始终使用InProc会话状态.
我没有看到任何方法可以为每个服务创建一个ChannelFactory,然后在创建通道时指定凭据.我错过了什么吗?
几个月后我刚刚遇到这个悬而未决的问题,最后我可以为它提供答案.我使用的解决方案非常类似于ASP.NET网站+ Windows Forms App + WCF服务:客户端凭证.我仍然决定在这里写一下我的方法.
WCF服务的常规(胖客户端)用户使用用户名/密码进行身份验证,并且Web用户通过随请求提供的标头进行身份验证.此标头可以信任,因为Web服务器本身使用XCF9证书进行身份验证,WCF服务具有公钥.因此,解决方案是在ASP.NET应用程序中使用单个ChannelFactory,它将在请求中插入标头,告诉WCF服务哪个用户实际发出请求.
我为我的ServiceHost设置了两个端点,URL略有不同,绑定也不同.两个绑定都是TransportWithMessageCredential,但其中一个是消息凭据类型Username,另一个是Certificate.
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" );
Run Code Online (Sandbox Code Playgroud)
我配置了一个ServiceCredentials对象,其中包括a)服务器端证书,b)自定义用户名/密码验证器和c)客户端证书.这有点令人困惑,因为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 );
Run Code Online (Sandbox Code Playgroud)
我的解决方案是实现IAuthorizationPolicy,并将其用作ServiceHost的ServiceAuthorizationBehavior的一部分.此策略检查请求是否已通过我的UserNamePasswordValidator实现进行身份验证,如果是,则创建一个具有已提供标识的新IPrincipal.如果请求通过X509证书进行身份验证,我会在当前请求中查找消息头,指示模拟用户是谁,然后使用该用户名创建主体.我的IAuthorzationPolicy.Evaluate方法:
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;
}
Run Code Online (Sandbox Code Playgroud)
将策略添加到ServiceHost非常简单:
serviceHost.Authorization.ExternalAuthorizationPolicies = new List<IAuthorizationPolicy>() { new CustomAuthorizationPolicy() }.AsReadOnly();
serviceHost.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.Custom;
Run Code Online (Sandbox Code Playgroud)
现在,ServiceSecurityContext.Current.PrimaryIdentity无论用户如何进行身份验证,都是正确的.这或多或少地处理了WCF服务方面的繁重工作.在我的ASP.NET应用程序中,我必须设置一个适当的绑定(certificateBinding,如上所述),并创建我的ChannelFactory.但是,我向factory.Endpoint.Behaviors添加了一个新行为,用于从当前用户的标识中提取当前用户的标识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;
}
Run Code Online (Sandbox Code Playgroud)
当然,我们仍然需要一个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 ); }
}
Run Code Online (Sandbox Code Playgroud)
最后,使ChannelFactory使用正确的客户端证书和端点行为的粘合剂:
factory.Credentials.ClientCertificate.SetCertificate( StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "SelfSignedWebsiteCertificate" );
factory.Endpoint.Behaviors.Add( new GenericClientInspectorBehavior( new HttpContextAuthenticationInspector() ) );
Run Code Online (Sandbox Code Playgroud)
唯一的最后一部分是如何确保HttpContext.Current.User设置,以及用户实际已经过身份验证.当用户尝试登录网站时,我创建一个使用我的usernameBinding的ChannelFactory,将提供的用户名/密码分配为ClientCredentials,并向我的WCF服务发出单个请求.如果请求成功,我知道用户的凭据是正确的.
然后我可以使用FormsAuthentication该类或HttpContext.Current.User直接将属性IPrincipal分配给该属性.此时我不再需要使用usernameBinding的一次性ChannelFactory,我可以使用单一的ChannelFactory使用certificateBinding,其中一个实例在我的ASP.NET应用程序中共享.ChannelFactory将从HttpContext.Current.User未来的WCF请求中获取当前用户并插入适当的头.
因此,我在ASP.NET应用程序中每个WCF服务需要一个ChannelFactory,并且每次用户登录时都需要创建一个临时的ChannelFactory.在我的情况下,该站点使用很长时间并且登录不是那么频繁,所以这是一个很好的解决方
| 归档时间: |
|
| 查看次数: |
2411 次 |
| 最近记录: |