本文章是由機器翻譯。

Windows 工作流程

保障 WF 4 工作流程服務的安全性

Zulfiqar Ahmed

下載代碼示例

Windows Workflow Foundation (WF) 為編寫軟體邏輯提供了視覺化的創作體驗。一旦軟體邏輯實現為工作流,您只需將該工作流部署到工作流宿主上就可以執行了。工作流服務是一種通過工作流實現的特殊 Web 服務。該服務在部署到 WorkflowServiceHost 上之後即可提供給使用者使用。本文將介紹各種工作流宿主的安全選項,尤其是工作流服務和 WorkflowServiceHost 的安全選項。我還介紹一些可以將安全範圍從工作流服務擴展到工作流層的重要擴展功能。另外,還將討論工作流安全包 (WFSP) 專案,及其活動集在工作流解決方案中實現端到端安全性的方法。作為 Microsoft .NET Framework 4 元件推出的 WF 4 提供了一個可擴展的宿主 API,並自帶三種現成的具有不同功能的宿主。

WorkflowInvoker。這是最簡單也是功能最少的宿主介面,提供了簡單的工作流調用 API。一個 WorkflowInvoker 物件只支援一個工作流實例,並要通過構造函數或靜態 Invoke 方法傳入該實例。由於所有工作流定然通過同一調用執行緒來執行,因此,如果調用代碼在類比特定的安全上下文,那麼所有活動就都在該類比上下文中執行。WorkflowInvoker 並不是真正意義上的工作流宿主;因為,它不但封裝基於 WorkflowApplication 的宿主,並使用基於泵的同步環境來提供包含統一執行語義的易用型 API。例如,異常和事務等活動可以跨調用邊界無縫流動。這種行為簡化了安全性,因為調用方的安全上下文在工作流執行期間一直處於可用狀態,並且活動可以在各種安全方案下使用這個上下文。例如,主體許可權授權和 Kerberos 委派活動可與 WorkflowInvoker 無縫協作。

WorkflowApplication。這是一個功能略強一點的宿主,不過它仍只支援一個實例。該宿主通過 CLR ThreadPool 中的 IO 執行緒來執行工作流。調用執行緒的安全上下文不會複製到相應的工作流執行緒,因此,即使工作流用戶端在執行類比操作,執行活動的 WF 執行緒也不會執行類比操作。通過使用自訂同步環境在同一傳入非同步執行緒上轉發調用(類似于 WorkflowInvoker 所用的同步環境),可將調用方的安全上下文傳給 WF 執行緒:

public class MySyncContext : SynchronizationContext
{
  public override void Post(SendOrPostCallback d, object state)
  {
    d(state);
  }
}

WorkflowServiceHost。這是最全面的宿主,提供的宿主環境可以調用多個工作流實例。工作流服務是一種可以通過工作流實現的特殊 Web 服務。WorkflowServiceHost 派生自標準 Windows Communication Foundation (WCF) ServiceHostBase 類,支援所有的 WCF 安全概念。消息傳送活動是 WorkflowServiceHost 和 WorkflowHostingEndpoint(支援在不必使用消息傳送活動的情況下使用 WorkflowServiceHost)支援的主要交互模型。本文重點介紹消息傳送活動、工作流服務和 WorkflowServiceHost 的安全主題。要概括瞭解工作流服務技術,請參閱 Leon Welicki 在 2010 年 5 月期 MSDN 雜誌 上的文章“使用 WCF 和 WF 4 的工作流視覺化設計”(msdn.microsoft.com/magazine/ff646977)。

工作流服務網路安全

由於工作流服務是標準的 WCF 服務,因此,網路安全功能可以通過標準 WCF 綁定機制來配置。工作流服務可以根據其安全需要,通過一個或多個使用特定綁定的端點來公開。WCF 分派管道只有在傳入消息符合目標端點的安全要求時才會執行。工作流邏輯在分派管道的末端執行,因此,所有常用的 WCF 擴展功能同樣適用于工作流服務。例如,也可以使用標準 ServiceAuthorizationManager 擴展功能來應用工作流服務的授權功能。Windows Identity Foundation (WIF) 等框架可以在調度程式級別與 WCF 集成,並且它們還可以透明地用於工作流服務。執行緒方面存在一些差異,這些差異與 WCF 和 WF 層之間的幾個非同步點有關。由於這些差異的存在,某些與執行緒本機存放區 (TLS) 相關的方案遇到的工作流服務問題會多一些。例如,WIF 通過 Thread.CurrentPrincipal 公開傳入的身份資訊,並可以為基於代碼的服務正確設置此屬性。然而,工作流邏輯最終可能不會在原始 WCF 執行緒上執行。這樣,所有與 TLS 相關的資料(包括 Thread.CurrentPrincipal)都會變成無效資料,因此,建議不要在工作流中依賴 TLS。後面,我會介紹一些解決此問題的可能方法。

WF 4 還提供一個 Send 活動,用以從某個工作流中調用其他 Web 服務。可以為 Send 活動配置一個綁定,以在從工作流中調用其他服務時使用。在內部,Send 活動使用標準 WCF ChannelFactory/Channel API 發送消息,而配置的綁定則可以用來創建此內部通道工廠。Send 活動還內置一個緩存層,用來緩存 ChannelFactory/Channel。預設情況下,只有在通過 Send 活動的屬性直接指定了端點資訊,並且選擇了現有綁定後,才能使用該緩存層,如圖 1 所示。

圖 1 Send 活動屬性

通過 EndpointConfigurationName 屬性從設定檔載入端點資訊後,相應的安全緩存功能就會進入禁用狀態,並且您每次執行 Send 活動後,系統都會創建一個全新的 ChannelFactory。在通道工廠啟動階段,保護 wsHttpBinding、wsFederationHttpBinding 等綁定是很麻煩的事,而且為每條消息重建通道工廠的成本也很大。例如,預設的 WSHttpBinding 已經經過優化,可以提高性能和安全性。雖然這樣需要耗費前期成本來創建安全的會話,但卻可以確保後續消息的安全,大幅降低相應的成本。如果沒有 ChannelFactory 緩存功能,WSHttpBinding 的這一優勢就會變成劣勢,因為系統每發送一條業務消息就會多發四條基礎結構消息來協商服務憑據,然後創建安全的會話。如图 2 所示。

圖 2 為協商服務憑據而發出的基礎結構消息

https://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue
https://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue
https://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT
http://tempuri.org/IPingService/Ping
https://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT/Cancel

在 WF 4 中,對於任何從設定檔中載入的綁定配置(甚至是預設綁定),Send 活動都會將其視為“不安全的緩存方法”,並且系統會禁用 ChannelFactory 緩存功能。強制應用不安全的通道緩存功能可以覆蓋預設行為,讓您能夠重新使用 ChannelFactory 向同一端點發送消息。SendMessageChannelCache 服務行為不但可以啟用不安全的緩存功能,而且還可以配置各種 Channel 和 ChannelFactory 緩存設置,如下所示:

<serviceBehaviors>
  <behavior>
    <sendMessageChannelCache allowUnsafeCaching="true">
      <channelSettings idleTimeout="1:0:0" maxItemsInCache="60"/>
      <factorySettings idleTimeout="1:0:0" maxItemsInCache="60"/>
    </sendMessageChannelCache>
  </behavior>
</serviceBehaviors>

OperationContext 在哪裡?

在基於代碼的傳統服務中,可以通過 OperationContext.Current 提供傳入調用的安全資訊。 由於 WCF 調度程式運行時會在您調用該服務方法前在相應執行緒上設置 OperationContext,因此,您在該服務方法中可以通過 OperationContext.Current 來訪問安全資訊。

工作流服務會增加複雜性,因為,在 WCF 調度程式和工作流執行之間存在大量非同步點。 這些非同步點中的任何一個都可以切換執行緒,如果發生執行緒切換,那麼工作流邏輯(活動)就會在非 WCF 執行緒上執行,並且將無法通過 OperationContext.Current 方法來訪問 OperationContext。 在 WF 4 中,通過基於 IReceiveMessageCallback 和 ISendMessageCallback 的回檔機制,可以不受執行緒影響訪問 OperationContext。 IReceiveMessageCallback 和 ISendMessageCallback 分別讓您可以在伺服器端和用戶端訪問 OperationContext。 在伺服器端,在 Receive 活動收到消息後立即調用 IReceiveMessageCallback。 要將這些回檔添加到 Send 和 Receive 活動中,必須確保它們可以在 Send/Receive 執行時用作執行屬性。 將執行屬性添加到活動的常用方法是創建父級範圍活動,然後將執行屬性添加到父級活動的執行環境中,如圖 3 所示。

圖 3 要添加執行屬性的簡單範圍活動

[ContentProperty("Body")]
public sealed class OperationContextScope : NativeActivity
{
  public Activity Body { get; set; }
  protected override void Execute(NativeActivityContext context)
  {
    if (this.Body != null)
    {
      // Adding an execution property to handle OperationContext
      context.Properties.Add(OperationContextScopeProperty.Name,
        new OperationContextScopeProperty());
      context.ScheduleActivity(this.Body);
    }
  }
}

圖 3 的程式碼片段中,OperationContextScope 活動在執行時只是將執行屬性添加到相應的環境中,以使所有子級活動都可以訪問該執行屬性。 Send 和 Receive 活動會查找上述回檔屬性,如果發現其中任何一個屬性,就會在消息處理的適當階段調用相應的屬性,這樣,您就可以訪問 OperationContext 了。 在此示例中,任何將屬同一範圍的 Receive 活動都可以訪問 OperationContextScopeProperty,並可以執行回檔,傳入 OperationContext 值(參見圖 4)。

圖 4 實現為執行屬性的 ReceiveMessageCallback

[DataContract]
class OperationContextScopeProperty : IReceiveMessageCallback,  IExecutionProperty
{
  private OperationContext current;
  private OperationContext orignal;

  public static readonly string Name = 
    typeof(OperationContextScopeProperty).FullName;
  public void OnReceiveMessage(OperationContext operationContext, 
    ExecutionProperties activityExecutionProperties)
  {
    current = operationContext;
    operationContext.OperationCompleted 
      += delegate(object sender, EventArgs e)
    {
      current = null;
    };
  }

  public void CleanupWorkflowThread()
  {
    OperationContext.Current = orignal;
  }
  public void SetupWorkflowThread()
  {
    orignal = OperationContext.Current;
    OperationContext.Current = current;
    }
}

OperationContextScopeProperty 只是捕獲和存儲當前活動的 OperationContext,然後通過 WF TLS 機制將其設置到適當的 WF 執行緒上。IExecutionProperty 介面提供了 Setup/CleanUpWorkflowThread 方法,這些方法可以在每個 WF 工作項(活動)執行前後接受調用,而且還可以在選定的 WF 執行緒上設置各種與 TLS 相關的屬性(如本例中的 OperationContext)。

OperationContextScope 是一個自訂的活動,它通過 WF 4 擴展功能可以在不受執行緒影響的情況下訪問所有範圍內子級活動的 WCF OperationContext。

工作流服務和 WIF

WIF 提供了大量用來聲明啟用 WCF 服務和 ASP.NET 應用程式的 API 和物件模型。WIF 在宿主級別集成 WCF,因此,大部分 WIF 功能也能支援工作流服務。有關如何集成 WIF 和工作流服務的詳細資訊,請參見我的博文 bit.ly/a6pWgA。這一現成的集成功能適合用來支援基本方案,而對於其他高級方案,可以通過 WFSP 中的活動來實現。

WFSP CTP 1 簡介

WFSP Community Technology Preview (CTP) 1 提供了各種用來幫您在 WF 4 中實現主要安全方案的活動和相關 WCF 行為。WFSP 利用 ISend/IReceiveMessageCallback 擴展模型來實現它的很多功能。WFSP CTP 1 已于 2010 年 7 月在 CodePlex 上發佈,可以從 wf.codeplex.com/releases/view/48114 下載。

WFSP 活動(如圖 5 所示)可以有效地與 WF 的其他功能集成,並且還提供能在工作流解決方案中應用集成安全方案的強大結構。

圖 5 工作流安全包活動

工作流中的授權

在工作流服務中,您可以使用標準 WCF ServiceAuthorizationManager 擴展功能來實現授權,並且該功能的運行方式與基於代碼的服務完全相同。但是,在某些情況下(如授權資料位於工作流中時),您可能要到工作流實際執行前才做出授權決策。PrincipalPermissionScope 是一個可以用來在工作流中添加 CLR PrincipalPermission 授權功能的伺服器端活動。位於該範圍之內的任何子級活動只有滿足了相應的許可權要求後才能執行。該活動負責查找在 OperationContext 中傳入 WCF 安全上下文的身份資訊。PrincipalPermissionScope 通過本文上述的同一 IReceiveMessageCallback 機制來實現。

實際的 PrincipalPermission 要求是根據 ServiceAuthorizationBehavior.PrincipalPermissionMode 的值來約束 IPrincipal 物件的。該擴展功能可以讓 PrincipalPermissionScope 支援 ASP.NET 角色提供程式以及自訂 IAuthorizationPolicy 所生成的自訂 IPrincipal 實現。有關如何配置用於 WCF 服務的 ASP.NET 角色提供程式的方法,請參見 msdn.microsoft.com/library/aa702542

消息傳送活動和經驗證的消息傳送

Send 活動提供了在工作流中使用 Web 服務的主要方法。在大多數實際應用中,這些後端服務都是受保護的,它們在執行任何操作之前都會要求進行身份驗證。在基於代碼的標準服務中,憑據資訊通過 ChannelFactory 和 ClientBase<T>派生代理類公開的 ClientCredential 屬性來指定。使用該屬性,用戶端可以指定其在調用服務前要使用的憑據。遺憾的是,包裝 ChannelFactory 的 Send 活動在 WF 4 中未公開 ClientCredential,因此,通過現成的 Send 活動無法實現某些需要顯式指定憑據的方案。注意,Send 活動卻可以從設定檔中選取端點行為配置,因此,您可以通過創建自訂端點行為來指定這些憑據。

在要求顯式提供憑據的操作中,調用要求提供使用者名/密碼的服務就是常見的一種。由於 Send 活動不公開 ClientCredential 屬性,因此,您無法指定使用者名/密碼。下麵,我們來瞭解一下 WFSP 中的 GetUserNameSecurityToken 活動是如何解決這類問題及其他相關問題的。

圖 6 中,“添加服務引用”嚮導生成 Ping 活動,該活動配置為調用一個要求進行 UserName 身份驗證的受保護服務,如下麵的綁定配置所示:

<wsHttpBinding>
  <binding name="singleShotUserName">
    <security mode="Message">
      <message clientCredentialType="UserName" establishSecurityContext
        ="false" />
    </security>
  </binding>
</wsHttpBinding>

圖 6 使用 UserName 權杖進行經驗證的消息傳送

在上述工作流中,GetUserNameSecurityToken 首先會根據提供的 UserName/Password 來創建一個 UserNameSecurityToken,然後將該權杖登記到 TokenFlowScope 活動提供的環境 SecurityTokenHandle 之中。“工作流安全包”與 ChannelFactory/ClientBase<T>方法應用安全功能的方式不同,前者在 SecurityToken 級別應用,而後者在憑據級別應用。在標準 WCF 中,憑據是用來創建安全權杖的,但 WFSP 會直接在權杖級別(而非憑據級別)將安全權杖應用到 WCF 安全層。

TokenFlowScope 是確保消息傳送操作和其他相關操作通過驗證的重要活動。該活動和 WorkflowClientCredentials 端點行為都可以將登記的權杖從工作流層應用到 WCF 安全層,這樣,您就能根據端點的綁定要求為傳出消息附加這些權杖了。TokenFlowScope 要求您配置自訂的 ClientCredential 行為 (WorkflowClientCredentials),配置代碼如下所示:

<behavior>
   <!--This custom clientCredentials enables the credential flow from 
     workflow data model into WCF security layer.
-->
   <clientCredentials
     type="Microsoft.Security.Activities.WorkflowClientCredentials, 
     Microsoft.Security.Activities, Version=1.0.0.0, Culture=neutral, 
     PublicKeyToken=31bf3856ad364e35">
   </clientCredentials>
</behavior>

WFSP 在調用要求提供安全權杖服務 (STS) 權杖的服務時完全依照此模型操作,如圖 7 所示。

圖 7 精細控制 SAML 權杖的獲取和使用

圖 7 中,GetSamlSecurityToken 先聯繫頒發者獲取一個 SAML 權杖,然後將該權杖登記到 TokenFlowScope 活動所提供的環境控制碼。通過登記,可以讓所有位於同一範圍並要求提供 SAML 權杖的 Send 活動訪問該權杖。這個模型是可擴展的,並且 GetSamlSecurityToken 自身也可以在獲取 SAML 權杖時使用已登記的權杖(例如,如果 STS 要求提供 UserName 權杖才能返回 SAML 權杖,而同一範圍內已存在有效的 UserName 權杖)。GetSamlSecurityToken 在配置了 WorkflowClientCredentials 行為的情況下也會在請求 SAML 權杖時使用此權杖。

現成的 WFSP 只支援 UserName 和 SAML 權杖類型;不過,您可以通過從 GetSecurityToken 類繼承的方式實現其他權杖類型。如圖 8 中的程式碼片段所示,該程式碼片段通過實現一個活動創建基於 X509 的權杖。

圖 8 WFSP 擴展功能:實現其他權杖類型

[Designer(typeof(GetX509SecurityTokenDesigner))]
public class GetX509SecurityToken : GetSecurityToken
{
  public GetX509SecurityToken()
  {
    FindType = X509FindType.FindBySubjectName;
    StoreLocation = StoreLocation.CurrentUser;
    StoreName = StoreName.My;
  }

  public InArgument<X509Certificate2> Certificate { get; set; }
  public X509FindType FindType { get; set; }
  public StoreLocation StoreLocation { get; set; }
  public InArgument<string> FindValue { get; set; }
  public StoreName StoreName { get; set; }

  protected override void Execute(NativeActivityContext context)
  {
    X509Certificate2 targetCert = null;
    if (this.Certificate != null)
      targetCert = this.Certificate.Get(context);
    if (targetCert == null)
    {
      var store = new X509Store(StoreName, StoreLocation);
      try
      {
        store.Open(OpenFlags.ReadOnly);
        var col = store.Certificates.Find(FindType, FindValue.Get(context), false);
        if (col.Count > 0)
          targetCert = col[0];//Use first certificate mathing the search criteria
      }
      finally
      {
        if (store != null)
          store.Close();
      }
    }
    if (targetCert == null)
      throw new InvalidOperationException(
        "No certificate found using the specified find criteria.");
        // Enlist the token as a flow token
      base.EnlistSecurityToken(context, new X509SecurityToken(targetCert));
  }
}

GetX509SecurityToken 首先根據證書創建一個 X509Security 權杖,然後將其登記到 SecurityTokenHandle 作為一個流權杖,該權杖隨後可用於調用要求提供驗證證書的服務。圖 9 顯示的是用於自訂活動設計器的 GetX509SecurityToken。

圖 9 包含自訂 GetToken 活動的 TokenFlowScope

基於聲明的委派

基於聲明的委派是通過 WFSP 實現的另外一種有用功能。通常,基於聲明的委派功能在工作流服務中具有更重要的地位,因為這些服務主要用來實現調用多個後端服務的業務流程/業務過程。另外,訪問調用方在這些後端服務中的身份資訊通常需要做出精細的授權決定。WFSP 可以通過 WS-Trust 1.4 的 ActAs 功能將任何類型的權杖轉換成 ActAs 權杖。預設情況下,所有 GetToken 活動都可以創建權杖並將其登記為流權杖。但這些活動都有一個 IsActAsToken 標記,如圖 10 所示。

圖 10 創建 ActAs 權杖

選中該標記後,權杖創建邏輯沒有變化,但創建的權杖 T1 會登記為 ActAs 權杖,而不是流權杖。每個範圍只能有一個 ActAs 權杖,供 GetSamlSecurityToken 活動在請求 SAML 權杖時使用。GetSamlSecurityToken 執行時,系統會選擇活動的 ActAs 權杖,並將其作為 GetSamlSecurityToken 活動所生成的權杖頒發請求中的內容發送。返回的權杖 T2 會包含身份驗證權杖和 ActAs 權杖的聲明。最後,對於任何在此範圍內執行的 Send 活動,在調用可在其安全上下文中同時看到這兩個身份資訊的後端服務時,都可以使用此 T2 權杖。

GetBootstrapToken 活動用在中間層方案中,用以實現端到端的基於聲明的委派。與 GetToken 活動不同,該活動只負責讀取傳入的權杖,然後將其登記為 ActAs 權杖,而不創建和登記新權杖。在調用後端服務時,GetBootstrapToken 活動讓工作流服務除了使用自身的身份以外,還可以使用傳入調用方的身份,如圖 11 所示。

圖 11 基於聲明的端到端委派流

圖 11 的步驟 3 中,工作流服務通過 WFSP 活動讀取傳入的引導權杖,獲取充當引導權杖身份的新權杖,並將兩個身份都傳給後端伺服器。圖 12 顯示的是此工作流服務使用的工作流。

圖 12 基於聲明的委派工作流

在圖 12 所示的工作流中,GetBootstrap 活動放在了 OperationContextScope 內部,這是為了確保該活動在執行時可以不受執行緒的影響訪問 OperationContext。GetSamlSecurityToken 使用 GetBootstrapToken 活動在上一步驟生成的 ActAs 權杖。最後,Echo 活動通過 GetSamlSecurityToken 活動生成的最終 SAML 權杖調用後端服務。

Windows 類比/委派

ImpersonatingReceiveScope 是又一個可以將 Windows 類比和委派功能添加到工作流環境中的伺服器端活動。該活動在執行時查找傳入安全上下文中的 WindowsIdentity。如果傳入消息生成了 WindowsIdentity,那麼,該活動的所有子級活動都在這一類比範圍內部執行。ImpersonatingReceiveScope 會在執行工作項之前,通過本文前面所述 Workflow TLS 機制來類比 WF 執行緒身份。當 WF 工作項完成執行後,類比身份恢復成原有身份。

如果 ImpersonatingReceiveScope 在傳入安全上下文中未找到有效的 WindowsIdentity,它就會查找 WIF 身份 (Thread.CurrentPrincipal) 或傳統 WCF ClaimsSet 中的 UPN 聲明,然後通過 Kerberos 的 S4U 功能使用該聲明來創建 WindowsToken。要將 UPN 聲明轉換成 Windows 權杖,ImpersonatingReceiveScope 需要使用 WIF 運行時中的“聲明到 Windows 權杖轉換服務”。要確保聲明到權杖的轉換成功,必須安裝並運行該服務。

圖 13 顯示的是 ImpersonatingReceiveScope 活動的典型用法。

圖 13 運行中的 ImpersonatingRecieveScope

端到端安全性

從外部看來,工作流服務就是標準的 WCF 服務,因此大部分 WCF 安全功能同樣適用于工作流服務。WF 4 引入了幾項可以將安全範圍從工作流服務擴展到工作流層的重要擴展功能。WFSP 提供了一系列可以使用這些擴展功能為 WF 4 實現端到端安全性的活動。

Zulfiqar Ahmed 是高級開發支援團隊的高級顧問,也是工作流安全包專案的原創者。他經常在 zamd.net 上發表博文,內容涵蓋 Windows Communication Foundation、Windows Workflow Foundation、基於聲明的安全技術和常規 Microsoft .NET Framework 等方面的主題。

衷心感謝以下技術專家對本文的審閱: Dave Cliffe