最近在研究BizTalk 和 WCF,想到以前沒有實現的一個關於安全方面的案例,特意抽時間做了一個Demo。僅供參考。
在BizTalk2006R2時,可以使用WSE3實現各種安全性通訊協定,只需要寫一個WSEPolicy,然後添加各種安全認證的方式即可。使用WCF以後,可以支援更多的安全配置,而且在解析訊息的效率方面也比通常的Web Service高一些。所以使用WCF Service已經是趨勢。
廢話不多說,先說一下BizTalk發布WCF的幾種類型。通常情況下,可以選擇BasicHttp,WSHttp和CustomIsolate的發布方式。Basic發布的Service和Soap Servie差不多,可以支援早期的SOAP 方式的調用。WSHttp全面支援WS-*,對安全方面要求較高的可以採用這種方式。如果還需要有自訂的方式的話,就是用Custom的方式好了,這種模式下可以使用自訂的Behavior,自訂的Binding,各種好處都不在話下。本樣本主要使用WSHttp實現簡單的安全認證和授權。
首先用BizTalk WCF服務發布嚮導發布一個WSHttp的Service,啟用中繼資料終結點並建立接收位置,將Schema作為WCF服務發布,建立Web服務,並允許匿名訪問。這時候BizTalk會在IIS下面建立一個Web應用,並且在BizTalk Application裡面會建立相應的接收位置接受來自Web應用的訊息。
這時候建立的WCF-WSHttp接收埠的傳輸屬性下面,安全模式為"Message" ,訊息用戶端憑據類型為"Windows"。此時的WCF Service和一般的Service沒有任何區別,不需要使用安全口令就可以調用和執行。我們啟用接收埠之後就可以在IE瀏覽剛剛發布的Web Service。
此時的Web.Config包含的ServiceModel如下:
<!-- The <system.serviceModel> section specifies Windows Communication Foundation (WCF) configuration. --> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="ServiceBehaviorConfiguration"> <serviceDebug httpHelpPageEnabled="true" httpsHelpPageEnabled="false" includeExceptionDetailInFaults="false" /> <serviceMetadata httpGetEnabled="true" httpsGetEnabled="false" /> </behavior> </serviceBehaviors> </behaviors> <services> <!-- Note: the service name must match the configuration name for the service implementation. --> <service name="Microsoft.BizTalk.Adapter.Wcf.Runtime.BizTalkServiceInstance" behaviorConfiguration="ServiceBehaviorConfiguration"> <endpoint name="HttpMexEndpoint" address="mex" binding="mexHttpBinding" bindingConfiguration="" contract="IMetadataExchange" /> <!--<endpoint name="HttpsMexEndpoint" address="mex" binding="mexHttpsBinding" bindingConfiguration="" contract="IMetadataExchange" />--> </service> </services> </system.serviceModel>
接下來我們就要對這個Web服務和BizTalk的接收埠動手術了。
我們首先將BizTalk的接收埠的安全性裡面的安全模式改為TransportWithMessageCredential,並且將訊息用戶端憑據類型改為UserName。同時將ServiceModel裡面的HttpsGetEnable設定為"True"。這時候我們的Web服務就需要使用Https的安全連結來訪問了。
更新Web服務的引用,重新調用該服務會出現為提供Credential的異常。那麼,這個Credential該如何設定,設定成什麼呢?
我們來看一下WCF的ServiceModel,定義Credential的地方在serviceBehavior裡面,我們添加一個serviceCredential,採用簡單的userNameAuthentication,選用Custom的validateMode,ValidateType將採用我們自訂的UserNamePasswordValidator。代碼如下:
<serviceCredentials> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="CustomAuthBehaviors.CustomValidator,CustomAuthBehaviors"/></serviceCredentials>
這個CustomValidator我們怎麼寫呢?
由於我們指定了UserNamePasswordValidateMode為Custom,所以需要複寫UserNamePasswordValidator類的Validate(string userName, string password),這個方法的解釋如下:
// 摘要:當在衍生類別中重寫時,驗證指定的使用者名稱和密碼。 // 參數: // userName:要驗證的使用者名稱。 // password:要驗證的密碼。 public abstract void Validate(string userName, string password);
我們這樣實現這個方法:
public class CustomValidator : UserNamePasswordValidator{ public override void Validate(string userName, string password) { string username = System.Configuration.ConfigurationSettings.AppSettings.Get("username"); string pwd = System.Configuration.ConfigurationSettings.AppSettings.Get("password"); if (userName != username || password != pwd) { throw new SecurityTokenException("wrong password or username"); } }}
這個username和password將會配置在Web.Config的AppSetting中。
我們將這個類庫簽名,編譯,並加入GAC,同時Copy到Web服務的Bin檔案夾中。注意:CustomAuthBehaviors.CustomValidator,CustomAuthBehaviors(CustomAuthBehaviors為dll名和NameSpace,CustomValidator為繼承的類名)。
重新啟動IIS,在用戶端程式中輸入ClientCredentials,就可以成功的調用該WCF Service了。到這一步我們已經成功的實現了Authentication了。接下來就是要實現Authorization了。
我們注意到WCF的serviceBehaviors裡面有個節點叫serviceAuthorization,這個裡面就是我們可以定義Authorization的地方了。我們添加了一個這樣的節點如下:
<serviceAuthorization serviceAuthorizationManagerType="CustomAuthBehaviors.CustomServiceAuthorizationManager,CustomAuthBehaviors" />
這裡面的CustomServiceAuthorizationManager又是從何而來,如何?我們所要的方法呢?在WCF的ServiceAuthorizationManager中,有如下解釋:
//// 摘要:// 訪問所需的訊息時,檢查給定操作內容相關的授權。//// 參數:// operationContext:// System.ServiceModel.OperationContext。//// message:// 要接收檢查以確定授權的 System.ServiceModel.Channels.Message。//// 返回結果:// 如果授予了訪問權,則為 true;否則為 false。預設值為 true。[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]public virtual bool CheckAccess(OperationContext operationContext, ref Message message);
看來這個方法可以協助我們實現根據訊息的上下文去實現對操作的授權。在OperationContext這個類型裡面我們能擷取訊息IncomingMessageHeaders的內容屬性,在Message裡面我們可以擷取到訊息的本文。這樣我們就可以根據這些屬性和內容實現授權了,我們這樣實現這個方法:
public class CustomServiceAuthorizationManager : ServiceAuthorizationManager{ public override bool CheckAccess(OperationContext operationContext, ref Message message) { string username = operationContext.ServiceSecurityContext.PrimaryIdentity.Name; string action = operationContext.IncomingMessageHeaders.Action; string messageBody = message.ToString(); MemoryStream ms = new MemoryStream(); XmlWriter writer = XmlWriter.Create(ms); message.WriteMessage(writer); writer.Flush(); ms.Position = 0; XPathDocument doc = new XPathDocument(ms); string NodeName = doc.CreateNavigator().MoveToChild("localname", "nameSpace").ToString(); if (username == "UserName" && action == "Operation" && NodeName=="Request") return true; return false; }}
這裡面當然可以根據我們的實際需要去判斷使用者的許可權。這樣我們的授權方法就實現了。重新編譯CustomAuthBehaviors,GAC,iisreset,這時候在從用戶端提交訊息,就會根據我們在CheckAccess的設定去執行了。如果不能獲得授權的話,就會返回未授權的資訊了。
至此,我們的WCF的Authentication和Authorization就實現了,當然了,這些只是最簡單的實現方法,我們還可以據此實現更進階的應用。
將此方案記錄下來,希望能對各位有所協助。