2010-01-15 54 views
3

我有一個企業系統由少數WinForms客戶端和麪向公衆的ASP.NET站點使用。後端WCF服務提供了幾個服務供每個客戶端使用。這些服務需要消息憑證,在WinForms應用程序的情況下,該消息憑證由用戶在程序第一次啓動時提供。爲多個ASP.NET用戶緩存WCF ChannelFactories

我緩存WinForm應用程序中的ChannelFactories性能。我想在ASP.NET網站上做同樣的事情。但是,由於ClientCredentials作爲工廠的一部分存儲(ChannelFactory<T>.Credentials),我是否需要爲每個用戶的每個服務緩存一個ChannelFactory?看起來,即使在適度使用,將迅速加起來。另外,我相信我需要將它們存儲在應用程序級別,而不是會話級別,因爲爲了將來的可伸縮性,我不能保證我將始終使用InProc會話狀態。

我沒有看到任何方式可以爲每個服務創建一個ChannelFactory,然後在創建通道時指定憑據。我錯過了什麼嗎?

回答

4

我只是碰到這種懸而未決的問題跑很多個月後,最後我可以爲它提供一個答案。我採用了與ASP.NET Web Site + Windows Forms App + WCF Service: Client Credentials非常相似的解決方案。不過,我決定我會在這裏寫一下我的方法。

WCF服務的常規(胖客戶端)用戶使用用戶名/密碼進行身份驗證,並且Web用戶通過隨請求提供的標頭進行身份驗證。由於Web服務器本身使用WCF服務擁有公鑰的X509證書進行身份驗證,因此可以信任此頭文件。因此,解決方案是在ASP.NET應用程序中使用一個ChannelFactory,它將在請求中插入一個標頭,告訴WCF服務哪個用戶實際發出請求。

我爲ServiceHost設置了兩個端點,URL和綁定略有不同。兩個綁定都是TransportWithMessageCredential,但其中一個是消息憑證類型用戶名,另一個是證書。

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

我配置了一個與ServiceCredentials a)所述服務器端證書,b)將定製的用戶名/密碼驗證器和c)客戶端證書對象。這有點令人困惑,因爲即使一個端點爲A + B設置,而另一個端點爲A + C配置,WCF默認會嘗試使用所有這些機制(A + B + 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證書進行身份驗證,則在當前請求上查找消息標頭,指明模擬用戶是誰,然後使用該用戶名創建一個委託人。我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; 
} 

添加政策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.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; 
} 

當然,我們仍然需要一個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已設置,並且用戶已實際進行了身份驗證。當用戶嘗試登錄網站時,我創建了一個使用我的usernameBinding的ChannelFactory,將提供的用戶名/密碼分配爲ClientCredentials,並向我的WCF服務發出一個請求。如果請求成功,我知道用戶的憑據是正確的。

然後,我可以使用FormsAuthentication類或直接爲HttpContext.Current.User屬性指定IPrincipal。此時,我不再需要使用usernameBinding的一次性ChannelFactory,並且我可以使用certificateBinding使用一個ChannelFactory,其中一個實例在我的ASP.NET應用程序中共享。該ChannelFactory將從HttpContext.Current.User中挑選當前用戶,並在未來的WCF請求中插入適當的標頭。

因此,我的ASP.NET應用程序中每個WCF服務需要一個ChannelFactory,並且每次用戶登錄時都會創建一個臨時ChannelFactory。在我的情況下,該站點使用了很長時間,登錄並非頻繁,所以這是一個很好的解決方案。

0

因此,你使用客戶端憑證進行WCF連接,因爲他們已登錄到服務器?

我的想法是設置WCF綁定以使用模擬。有效地,渠道工廠可以連接爲您的網站身份,或作爲最小特權身份,並且WCF代碼可能導致服務在身份不正確時拒絕該呼叫。

通過這種方式,您可以創建一個與服務器具有多個連接的通道工廠,並且您可以在進行WCF調用時模擬登錄的用戶(從內存中,您可以通過將impersonate調用包裝爲使用。語句)

這個環節應該是有幫助的,我希望: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"; 
    } 
} 
+0

我認爲這是正確的方向,關於將網站認證爲自己(通過證書),然後基本上說「當我告訴你用戶正在做這件事情時,請相信我」。我確實發現了這個(http://stackoverflow.com/questions/345414),我認爲這讓我更接近了一點。我一直有很多其他小問題(不是全部),所以這個網站現在已經佔據了一席之地。 – 2010-02-16 18:02:28