閱讀182 返回首頁    go 阿裏雲 go 技術社區[雲棲]


WCF的安全審核——記錄誰在敲打你的門

WCF所謂的安全審核就是針對認證和授權所做的針對EventLog的日誌記錄。我們不但可以設置進行審核的事件(認證成功/失敗,授權成功或失敗),還可以選擇記錄信息被寫入的EventLog類型,即應用程序日誌(Application)還是安全日誌(Security)。WCF的安全審核是通過ServiceSecurityAuditBehavior服務行為實現的。

針對WCF安全審核的編程隻涉及ServiceSecurityAuditBehavior服務行為。下麵給出了定義在ServiceSecurityAuditBehavior中具體審核行為進行控製的三個可讀寫的屬性。

   1: public sealed class ServiceSecurityAuditBehavior : IServiceBehavior
   2: {
   3:     //其他成員
   4:     public AuditLogLocation AuditLogLocation { get; set; }
   5:     public AuditLevel MessageAuthenticationAuditLevel { get; set; }
   6:     public AuditLevel ServiceAuthorizationAuditLevel { get; set; }
   7:     public bool SuppressAuditFailure { get; set; }
   8: }

屬性AuditLogLocation代表的是日誌信息被寫入的EventLog類型,該屬性的類型是一個具有如下定義的AuditLogLocation枚舉。其中Application和Security分別代表應用程序日誌和安全日誌。如果選擇Default,則最終日誌被寫入的位置決定於當前的操作係統。如果支持寫入安全日誌,則選擇安全日誌類型,否則選擇應用程序日誌類型。Default是默認選項。

   1: public enum AuditLogLocation
   2: {
   3:     Default,
   4:     Application,
   5:     Security
   6: }

MessageAuthenticationAuditLevel和ServiceAuthorizationAuditLevel兩個屬性分別代表針對認證和授權審核的級別。所謂審核的級別在這裏指的應該在審核事件(認證和授權)在成功或者失敗的情況下進行日誌記錄。審核級別通過具有如下定義的AuditLevel枚舉表示。Success和Failure代表分別針對認證/授權成功和失敗進行審核日誌。SuccessOrFailure則意味著不管認證/授權是成功還是失敗,都會進行審核日誌。None為默認值,表示不進行審核日記記錄。

   1: public enum AuditLevel
   2: {
   3:     None,
   4:     Success,
   5:     Failure,
   6:     SuccessOrFailure
   7: }

布爾類型的SuppressAuditFailure屬性表示審核日誌失敗是否會影響應用本身。在默認的情況下該屬性值為True,意味著為認證和授權進行審核日誌的時候出現的異常不會對應用(服務)本身造成任何影響。

既然是服務行為,我們就可以通過將創建的ServiceSecurityAuditBehavior添加到服務的行為列表的方式來進行安全審核的控製。當然我們還是推薦采用配置的方式來進行安全什麼的相關設置。服務行為ServiceSecurityAuditBehavior對應的配置節是<serviceSecurityAudit>。在下麵的配置中,我定義了一個包含了ServiceSecurityAuditBehavior的服務行為,並對其四個屬性進行了顯式設置。

   1: <configuration>
   2:   <system.serviceModel>     
   3:     <behaviors>
   4:       <serviceBehaviors>
   5:         <behavior ...>
   6:           <serviceSecurityAudit auditLogLocation ="Application" 
   7:                                 messageAuthenticationAuditLevel ="Failure"
   8:                                 serviceAuthorizationAuditLevel="SuccessOrFailure"
   9:                                 suppressAuditFailure="true"/>
  10:         </behavior>
  11:       </serviceBehaviors>
  12:     </behaviors>
  13:   </system.serviceModel>
  14: </configuration>

WCF最終進行安全審核的控製信息是從基於某個終結點的分發上下文中獲取的。如下麵的代碼片段所示,對於定義在ServiceSecurityAuditBehavior中的四個屬性,在DispatchRuntime中具有相應的定義。WCF在認證和授權成功或者失敗的時候,會根據該運行時這四個屬性進行相應安全審核日誌。

   1: public sealed class DispatchRuntime
   2: {
   3:     //其他成員
   4:     public AuditLogLocation SecurityAuditLogLocation { get; set; }
   5:     public AuditLevel MessageAuthenticationAuditLevel { get; set; }
   6:     public AuditLevel ServiceAuthorizationAuditLevel { get; set; }
   7:     public bool SuppressAuditFailure { get; set; }
   8: }

而作為服務行為的ServiceSecurityAuditBehavior,最終的目的就是將定義在自身的這四個屬性賦值給分發上下文。而具體操作定義在ApplyDispatchBehavior方法上,整個邏輯大體上如下麵的代碼所示。

   1: public sealed class ServiceSecurityAuditBehavior : IServiceBehavior
   2: {
   3:     //其他成員
   4:     public void ApplyDispatchBehavior(ServiceDescription serviceDescription, 
   5:     ServiceHostBase serviceHostBase)
   6:     {
   7:         foreach (ChannelDispatcher channelDispatcher in 
   8:            serviceHostBase.ChannelDispatchers)
   9:         {
  10:             foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
  11:             {
  12:                 DispatchRuntime dispatchRuntime = endpointDispatcher.DispatchRuntime;
  13:                 dispatchRuntime.SecurityAuditLogLocation = this.AuditLogLocation;
  14:                 dispatchRuntime.MessageAuthenticationAuditLevel = this.MessageAuthenticationAuditLevel;
  15:                 dispatchRuntime.ServiceAuthorizationAuditLevel = this.ServiceAuthorizationAuditLevel;
  16:                 dispatchRuntime.SuppressAuditFailure = this.SuppressAuditFailure;
  17:             }
  18:         }
  19:     }
  20: }

接下來我們通過一個簡單的實例來演示如何進行針對認證和授權的安全審核,並且看看在認證或者授權成功或者失敗的情況下,會有怎樣的日誌信息被記錄下來。

基於認證的安全審核

先來演示針對認證的安全審核。我們還是直接使用一直在使用的計算服務的例子,服務契約和服務的定義我們已經很熟悉了,我們現在隻來介紹采用的服務端的配置。從下麵的配置可以看出,被寄宿的服務具有為一個基於WS2007HttpBinding的終結點,該綁定采用默認的Windows認證。通過服務行為,我們將安全審核的AuditLogLocation和MessageAuthenticationAuditLevel分別設置為Application和SuccessOrFailure。

   1: <configuration>
   2:   <system.serviceModel>    
   3:     <services>
   4:       <service name="Artech.WcfServices.Service.CalculatorService" 
   5:                behaviorConfiguration="authenticationAudit">
   6:         <endpoint address="https://127.0.0.1:3721/calculatorservice" 
   7:                   binding="ws2007HttpBinding" 
   8:                   contract="Artech.WcfServices.Service.Interface.ICalculator"/>
   9:       </service>
  10:     </services>
  11:     <behaviors>
  12:       <serviceBehaviors>
  13:         <behavior  name="authenticationAudit">
  14:           <serviceSecurityAudit auditLogLocation ="Application" 
  15:                                 messageAuthenticationAuditLevel ="SuccessOrFailure"/>
  16:         </behavior>
  17:       </serviceBehaviors>
  18:     </behaviors>
  19:   </system.serviceModel>
  20: </configuration>

客戶端利用如下的代碼進行服務的調用,。而輔助方法Invoke旨在避免避免認證失敗導致的異常是程序終止。

   1: ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService");
   2: NetworkCredential credential = channelFactory.Credentials.Windows.ClientCredential;
   3: credential.UserName = "Foo";
   4: credential.Password = "Password";
   5: ICalculator calculator = channelFactory.CreateChannel();
   6: Invoke(calculator);
   7:  
   8: channelFactory = new ChannelFactory<ICalculator>("calculatorService");
   9: credential = channelFactory.Credentials.Windows.ClientCredential;
  10: credential.UserName = "Bar";
  11: credential.Password = "InvalidPass";
  12: calculator = channelFactory.CreateChannel();
  13: Invoke(calculator);

由於我們通過配置將應用在服務上的ServiceSecurityAuditBehavior服務行為的AuditLogLocation和MessageAuthenticationAuditLevel分別設置為Application和SuccessOrFailure,意味著不論是認證成功或者失敗都會進行安全審核日誌。而審核日誌最終會被寫入EventLog的應用程序日誌。當程序執行後,在事件查看器的應用程序節點,你會發現具有如下圖所示的4條新的日誌(之前的日誌在程序運行前被清空)。

clip_image002

下麵列出了這4條日誌的內容。其中前3條基於認證成功的信息(Information)日誌,最後一條是基於認證失敗的錯誤(Error)日誌。[源代碼從這裏下載]

   1: Security negotiation succeeded.
   2:  Service: https://127.0.0.1:3721/calculatorservice
   3:  Action: https://docs.oasis-open.org/ws-sx/ws-trust/200512/RSTR/Issue
   4:  ClientIdentity: Jinnan-PC\Foo; S-1-5-21-3534336654-2901585401-846244909-1006
   5:  ActivityId: <null>
   6:  Negotiation: SpnegoTokenAuthenticator
   7:  
   8: Message authentication succeeded.
   9:  Service: https://127.0.0.1:3721/calculatorservice
  10:  Action: https://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/SCT
  11:  ClientIdentity: Jinnan-PC\Foo; S-1-5-21-3534336654-2901585401-846244909-1006
  12:  ActivityId: <null>
  13:  
  14: Message authentication succeeded.
  15:  Service: https://127.0.0.1:3721/calculatorservice
  16:  Action: https://www.artech.com/ICalculator/Add
  17:  ClientIdentity: Jinnan-PC\Foo; S-1-5-21-3534336654-2901585401-846244909-1006
  18:  ActivityId: <null>
  19:  
  20: Security negotiation failed.
  21:  Service: https://127.0.0.1:3721/calculatorservice
  22:  Action: https://docs.oasis-open.org/ws-sx/ws-trust/200512/RSTR/Issue
  23:  ClientIdentity: 
  24:  ActivityId: <null>
  25:  Negotiation: SpnegoTokenAuthenticator
  26:  Win32Exception: The Security Support Provider Interface (SSPI) negotiation failed.

基於授權的安全審核

接下來我們演示授權的安全審核,並查看分別在授權成功和失敗的情況下分別由怎樣的日誌被寫入到EventLog中。我們首先按照如下的方式在服務類型CalculatorService的Add方法上應用PrincipalPermissionAttribute特性使該方法隻有在才有權限調用。

   1: public class CalculatorService : ICalculator
   2: {
   3:     [PrincipalPermission(SecurityAction.Demand,Role="Administrators")]
   4:     public double Add(double x, double y)
   5:     {
   6:         return x + y;
   7:     }
   8: }

然後我們將服務端配置中關於安全審核的相關配置進行了如下的修改。我們隻關心授權相關的安全審核,所以將messageAuthenticationAuditLevel屬性替換成serviceAuthorizationAuditLevel。

   1: <configuration>
   2:   <system.serviceModel>   
   3:    ... 
   4:     <behaviors>
   5:       <serviceBehaviors>
   6:         <behavior  name="authenticationAudit">
   7:           <serviceSecurityAudit auditLogLocation ="Application" 
   8:                                 serviceAuthorizationAuditLevel ="SuccessOrFailure"/>
   9:         </behavior>
  10:       </serviceBehaviors>
  11:     </behaviors>
  12:   </system.serviceModel>
  13: </configuration>

客戶端在進行服務調用的時候需要為帳號Bar指定正確的密碼。和前麵一樣,這裏的帳號,所以隻有第一次服務調用才是被成功授權的。

   1: ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService");
   2: NetworkCredential credential = channelFactory.Credentials.Windows.ClientCredential;
   3: credential.UserName = "Foo";
   4: credential.Password = "Password";
   5: ICalculator calculator = channelFactory.CreateChannel();
   6: Invoke(calculator);
   7:  
   8: channelFactory = new ChannelFactory<ICalculator>("calculatorService");
   9: credential = channelFactory.Credentials.Windows.ClientCredential;
  10: credential.UserName = "Bar";
  11: credential.Password = "Password";
  12: calculator = channelFactory.CreateChannel();
  13: Invoke(calculator);

當客戶端完成兩次服務調用後,如下兩條基於授權的審核日誌被寫入應用程序日誌。雖然隻有第一次服務調用才是真正被授權的操作,但是從日誌的內容我們卻發現兩條均是“”的審核日誌。[源代碼從這裏下載]

   1: Service authorization succeeded.
   2:  Service: https://127.0.0.1:3721/calculatorservice
   3:  Action: https://www.artech.com/ICalculator/Add
   4:  ClientIdentity: Jinnan-PC\Foo; S-1-5-21-3534336654-2901585401-846244909-1006
   5:  AuthorizationContext: uuid-528e86ce-a4f4-48b3-9a2e-b713e1dea539-1
   6:  ActivityId: <null>
   7:  ServiceAuthorizationManager: <default>
   8:  
   9: Service authorization succeeded.
  10:  Service: https://127.0.0.1:3721/calculatorservice
  11:  Action: https://www.artech.com/ICalculator/Add
  12:  ClientIdentity: Jinnan-PC\Bar; S-1-5-21-3534336654-2901585401-846244909-1007
  13:  AuthorizationContext: uuid-528e86ce-a4f4-48b3-9a2e-b713e1dea539-2
  14:  ActivityId: <null>
  15:  ServiceAuthorizationManager: <default>

實際對於安全審核中所謂的。在默認的情況下,,所以授權總是會“成功”。

為了迎合安全審核對“授權失敗”的判斷,我在Service項目中創建了如下一個簡單的自定ServiceAuthorizationManager。在重寫的CheckAccessCore方法中,如果當前用戶是Foo(Jinnan-PC\Jinnan)就發返回True,否則返回False。

   1: using System.ServiceModel;
   2: namespace Artech.WcfServices.Service
   3: {
   4:     public class MyServiceAuthorizationManager : ServiceAuthorizationManager
   5:     {
   6:         protected override bool CheckAccessCore(OperationContext operationContext)
   7:         {
   8:             string userName = operationContext.ServiceSecurityContext.PrimaryIdentity.Name;
   9:             return  string.Compare(userName, @"Jinnan-PC\Foo",true) == 0;
  10:         }
  11:     }
  12: }

然後通過如下的配置將這個自定義的MyServiceAuthorizationManager應用到ServiceAuthorizationBehavior服務行為上。再次運行我們的程序,將會得到分別代表授權成功和失敗的兩條審核日誌,並且在日誌中還包含了我們自定的ServiceAuthorizationManager類型(ServiceAuthorizationManager: MyServiceAuthorizationManager)。[源代碼從這裏下載]

   1: <configuration>
   2:   <system.serviceModel>    
   3:     ...
   4:     <behaviors>
   5:       <serviceBehaviors>
   6:         <behavior  name="authenticationAudit">
   7:           <serviceAuthorization serviceAuthorizationManagerType="Artech.WcfServices.Service.MyServiceAuthorizationManager, Artech.WcfServices.Service">
   8:           </serviceAuthorization>
   9:           <serviceSecurityAudit auditLogLocation ="Application" 
  10:                                 serviceAuthorizationAuditLevel ="SuccessOrFailure"/>
  11:         </behavior>
  12:       </serviceBehaviors>
  13:     </behaviors>
  14:   </system.serviceModel>
  15: </configuration>

授權成功/失敗審核日誌:

   1: Service authorization succeeded.
   2:  Service: https://127.0.0.1:3721/calculatorservice
   3:  Action: https://www.artech.com/ICalculator/Add
   4:  ClientIdentity: Jinnan-PC\Foo; S-1-5-21-3534336654-2901585401-846244909-1006
   5:  AuthorizationContext: uuid-91ff29c8-84cb-4c1a-837c-0913c12c0be5-1
   6:  ActivityId: <null>
   7:  ServiceAuthorizationManager: MyServiceAuthorizationManager
   8:  
   9: Service authorization failed.
  10:  Service: https://127.0.0.1:3721/calculatorservice
  11:  Action: https://www.artech.com/ICalculator/Add
  12:  ClientIdentity: Jinnan-PC\Bar; S-1-5-21-3534336654-2901585401-846244909-1007
  13:  AuthorizationContext: uuid-91ff29c8-84cb-4c1a-837c-0913c12c0be5-2
  14:  ActivityId: <null>
  15:  ServiceAuthorizationManager: MyServiceAuthorizationManager
  16:  FaultException: Access is denied.

作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 14:34:38

  上一篇:go  [WCF-Discovery]如何利用”發現代理”實現可用服務的實時維護?
  下一篇:go  WCF 4.0新特性匯總[共12篇]