共用方式為


本文章是由機器翻譯。

Silverlight 安全性

保護您的 Silverlight 應用程式的安全

Josh Twist

作為一名 Microsoft 服務顧問,我定期與客戶和合作夥伴一起進行應用程式安全性討論。在本文中,我將介紹一些在這些討論中提出的主題。特別是,我將重點介紹程式設計人員在嘗試保護 Silverlight 應用程式的安全時所面臨的新挑戰,而且我將考慮開發團隊應該將其資源集中于哪些方面。

本文提到了許多技術概念,您可以在其他位置(包括本雜誌)找到這些概念的更多詳細資訊。因此,我就不在技術層面更加深入地討論這些主題。本文的目標是“理清頭緒”並介紹如何利用這些概念保護您的應用程式的安全。

當規劃應用程式的安全性時,考慮三個 A 非常有用:身份驗證 (Authentication)、授權 (Authorization) 和審核 (Audit)。

身份驗證是確認使用者身份的行為。我們通常使用使用者名和密碼執行此操作。

授權是指在進行身份驗證之後,確認使用者實際上具有執行特定操作或訪問特定資源的適當許可權的過程。

審核是維護活動記錄,以便使用者無法拒絕對系統執行的操作和請求的行為。

在 Silverlight 應用程式上下文中,我將重點介紹前兩項(身份驗證和授權)。由於這是一個富 Internet 應用程式 (RIA),因此本文中描述的大多數概念同樣適用于非同步 JavaScript 和 XML (AJAX) 或其他 RIA 方法。我還將討論如何防止對您的 Silverlight 應用程式檔案進行不必要的訪問。

拓撲

Silverlight 是一種跨流覽器外掛程式,其利用 Windows Presentation Foundation (WPF) 率先採用的許多圖形概念,使 Web 開發人員能夠創建豐富的使用者體驗,這些使用者體驗將超出僅使用 HTML 和 JavaScript 創建的體驗。

與 ASP.NET 不同的是,Silverlight 是一種用戶端技術,它在使用者的電腦上運行。因此,Silverlight 開發無疑與 Windows 表單或 WPF 有許多共同之處,而與 ASP.NET 的共同之處相對較少。在許多方面,這是 Silverlight 的最大優勢之一,因為它消除了 Web 應用程式的無狀態性所導致的許多問題。不過,由於所有 UI 代碼都是在用戶端電腦上運行的,因此您不能再相信它。

服務

與 Windows 表單不同的是,Silverlight 在流覽器沙箱內運行且擁有的功能減少,因此它所提供的安全程度提高(儘管在 Silverlight 4 中,使用者可以將某些應用程式標識為可信並將程式的許可權提升為允許 COM 交互操作)。正因為如此,Silverlight 不能直接連接到資料庫,您必須創建一個可提供對您的資料和業務邏輯的訪問的服務層。

例如,您通常會將這些服務承載于您的 Web 伺服器上,就像使用 ASP.NET Web 表單一樣。假定 Silverlight 代碼運行于伺服器與現實世界之間的信任邊界的可信度較差的一側(參見圖 1),您的團隊的工作重點應始終是保護服務的安全。


圖 1Silverlight 運行于信任邊界的可信度較差的一側

在您的 Silverlight 代碼內實現嚴格的安全檢查幾乎沒有意義。畢竟,攻擊者可以很容易就完全擺脫 Silverlight 應用程式並直接調用您的服務,從而避開您實現的任何安全措施。此外,惡意人員可以使用像 Silverlight Spy 或 Debugging Tools for Windows 這樣的實用程式更改您的應用程式在運行時的行為。

我們要認識到的重要一點是:服務無法確切地知道哪個應用程式正在調用它或者該應用程式在某些方面尚未被修改。因此,您的服務必須確保:

  • 調用方已經過適當的身份驗證
  • 調用方已獲授權執行所請求的操作

鑒於上述原因,本文的大部分內容重點介紹如何採用與 Silverlight 相容的方式保護服務的安全。特別是,我將考慮通過 ASP.NET 在 Microsoft IIS 中承載兩種不同類型的服務。第一種類型是使用 Windows Communication Foundation (WCF) 創建的服務,它為構建服務提供一種統一的程式設計模型。第二種類型是 WCF 資料服務(以前稱為“ADO.NET 資料服務”),其構建于 WCF 之上,允許您使用標準 HTTP 謂詞(一種稱為“具象狀態傳輸”(REST) 的方法)快速公開資料。

通常,如果擔心安全性,則加密用戶端和伺服器之間的任何通信始終是明智之舉。建議使用 HTTPS/SSL 加密,且本文內假定使用此加密方法。

目前,Web 開發人員在 Microsoft 平臺上最常用的兩種身份驗證方法是 Windows 身份驗證和表單身份驗證。

Windows 身份驗證

Windows 身份驗證利用本地安全機構或 Active Directory 驗證使用者憑據。這在許多方案中都是一大優勢;它意味著您可以使用系統管理員已經熟悉的工具集中管理使用者。Windows 身份驗證可以使用 IIS 支援的任何方案,包括基本驗證、摘要式身份驗證、集成身份驗證 (NTLM/Kerberos) 和證書。

在使用 Windows 身份驗證時,通常都會選擇集成方案,因為使用者無需再次提供其使用者名和密碼。使用者在登錄到 Windows 之後,流覽器可採用用於確認個人身份的權杖或握手形式轉發憑據。但是由於用戶端和伺服器需要瞭解使用者的域,使用集成身份驗證有許多缺點。因此,集成身份驗證最適用于 Intranet 方案。此外,儘管它自動與 Microsoft Internet Explorer 一起使用,但其他流覽器(如 Mozilla Firefox)需要進行額外配置。

通常,基本驗證和摘要式身份驗證需要使用者在啟動與您的網站的會話時,重新輸入其使用者名和密碼。但是,由於這兩種身份驗證都屬於 HTTP 規範,因此它們在大多數流覽器中均可正常使用,即使是從組織外部進行訪問也是如此。

Silverlight 利用流覽器進行通信,因此使用剛才討論的任何 IIS 身份驗證方法,均可輕鬆實現 Windows 身份驗證。有關如何實現的詳細說明,建議閱讀分步指南“如何:在 Windows 表單中,使用 WCF 中的 basicHttpBinding 進行 Windows 身份驗證並使用 TransportCredentialOnly”(網址為:msdn.microsoft.com/library/cc949012)。此示例實際上使用 Windows 表單測試用戶端,但相同的方法也適用于 Silverlight。

表單身份驗證

表單身份驗證是一種為 ASP.NET 中的自訂身份驗證提供簡單支援的機制。因此,它特定于 HTTP,這意味著它也可在 Silverlight 中輕鬆使用。

使用者輸入使用者名和密碼組合,此資訊將提交給伺服器進行驗證。伺服器根據可信的資料來源(通常是使用者資料庫)檢查憑據,如果憑據正確,則返回一個 FormsAuthentication Cookie。然後,用戶端在隨後的請求中提供此 Cookie。Cookie 經過簽名和加密,因此只有伺服器才能解密,惡意使用者既無法解密,也無法篡改。

調用表單身份驗證的確切方式因登錄螢幕的實現方式而異。例如,如果在驗證了使用者的憑據後,使用重定向到您的 Silverlight 應用程式的 ASP.NET Web 表單,您可能不再需要執行身份驗證工作。Cookie 已發送到流覽器,且每當請求該域時,您的 Silverlight 應用程式都將繼續使用該 Cookie。

不過,如果您希望在 Silverlight 應用程式內實現登錄螢幕,您將需要創建一個公開您的身份驗證方法併發送相應 Cookie 的服務。但幸運的是,ASP.NET 已經提供了您所需要的身份驗證服務,您只需在您的應用程式中啟用它即可。有關詳細指南,建議閱讀“如何:使用 ASP.NET 身份驗證服務通過 Silverlight 應用程式登錄”(網址為:msdn.microsoft.com/library/dd560704(VS.96))。

ASP.NET 身份驗證的另一項強大的功能是其可擴展性。成員資格提供程式描述了用於驗證使用者名和密碼的機制。幸運的是,ASP.NET 附帶了許多成員資格提供程式,包括一個可使用 SQL Server 資料庫的成員資格提供程式,還有一個使用 Active Directory 的成員資格提供程式。然而,如果沒有符合您要求的提供程式,可直接創建一個自訂實現。

ASP.NET 授權

在您的使用者通過身份驗證後,請務必確保只有他們才能嘗試調用服務。在 ASP.NET 應用程式中,普通 WCF 服務和 WCF 資料服務均以 .svc 檔表示。本示例中,將在 IIS 中通過 ASP.NET 來承載服務,我將演示如何使用資料夾確保對服務的安全訪問。

採用這種方式保護 .svc 檔的安全有點令人迷惑不解,因為預設情況下,對此類檔的請求實際上會跳過大多數 ASP.NET 管道,從而繞過授權模組。因此,為了能夠利用許多 ASP.NET 功能,您必須啟用 ASP.NET 相容性模式。在任何情況下,WCF 資料服務都會強制要求您啟用它。在您的設定檔內進行簡單的切換即可完成任務:

<system.serviceModel> <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/></system.serviceModel><system.web> <authorization>  <deny users="?"/> </authorization></system.web>

啟用 ASP.NET 相容性後,可防止未經身份驗證的使用者通過使用 web.config 檔的授權部分(如上一程式碼片段所示)進行訪問。

當使用表單身份驗證時,開發人員必須認真考慮網站的哪些部分需要可訪問,即使是對於未經身份驗證的使用者也是如此。例如,如果所有部分均僅限於經過身份驗證的使用者訪問,那麼未經身份驗證的使用者將如何登錄?

通常,創建一個支援您的基本授權要求的資料夾結構是最簡單的方法。在本示例中,我已經創建了一個包含 MyWcfService.svc 和 MyWcfDataService.svc 檔的“Secured”資料夾,並且已經部署了一個 web.config 檔。在圖 2中,您可以看到資料夾結構,上一程式碼片段顯示了 web.config 檔的內容。


圖 2包含 Web.config 檔的 Secured 資料夾

請注意,應用程式的根必須允許匿名訪問,否則使用者無法到達登錄頁面。

對於使用 Windows 身份驗證的網站,在這一點上可能稍微簡單一些,因為身份驗證是在使用者到達應用程式內所含的資源之前進行的,因此不需要具體的登錄頁面。實際上,使用此方法可以採用更加詳細的方式限制對服務的訪問,從而只允許特定的使用者或角色組訪問資源。有關詳細資訊,請參閱“ASP.NET 授權”(msdn.microsoft.com/library/wce3kxhd)。

此示例在某種程度上實現了授權,但是對於大多數方案而言,單獨的資料夾級授權過於粗糙,難以依賴。

WCF 服務中的授權

使用 PrincipalPermission 屬性是要求 Microsoft .NET Framework 方法的調用程式限定于特定角色內的一種簡單方法。以下代碼示例演示了如何在 WCF 中將其應用於 ServiceOperation,其中調用使用者必須屬於“OrderApprovers”角色:

[PrincipalPermission(SecurityAction.Demand, Role = "OrderApprovers")]public void ApproveOrder(int orderId){  OrderManag-er.ApproveOrder(orderId);}

這在使用 Windows 身份驗證以利用現有設施創建 Active Directory 組以便組織使用者的應用程式中很容易實現。 借助使用表單身份驗證的應用程式,可以利用 ASP.NET 的另一項強大的基於提供程式的功能:RoleProviders。 此外,還有許多授權方法可用,如果這些方法均不適用,您可以實現自己的授權。

當然,即便是依據方法的授權也遠遠不足以滿足您的所有安全需求,您可以選擇在您的服務內編寫程式碼(如圖 3所示)。

圖 3使用程式碼實現特定授權

Public void CancelOrder(int orderId){  // retrieve order using Entity Framework ObjectContext  OrdersEntities entities = new OrdersEntities();  Order orderForProcessing = entities.Orders.Where(o => o.Id ==     orderId).First();  if (orderForProcessing.CreatedBy !=     Thread.CurrentPrincipal.Identity.Name)  {    throw new SecurityException(      "Orders can only be canceled by the user who created them");  }  OrderManager.CancelOrder(orderForProcessing);}

WCF 是一個具有高度可擴展性的平臺,隨著所有功能都集成到 WCF 中,有許多方法可以在您的服務中實現授權。 Dominick Baier 和 Christian Weyer 在 2008 年 10 月期的MSDN 雜誌中詳細討論了大量可能方案。 文章“基於 WCF 服務中的授權”(msdn.microsoft.com/magazine/cc948343) 甚至冒險嘗試了基於聲明的安全性(一種在您的應用程式中組織授權的結構化方法)。

WCF 資料服務中的授權

顧名思義,WCF 資料服務構建于 WCF 之上,以提供對資料來源(通常大多數情況下可能是 LINQ-to-SQL 或 LINQ-to-Entity Framework 資料來源)的基於 REST 的訪問。 簡而言之,這允許您使用映射到您的資料來源公開的實體集的 URL 訪問您的資料(實體集通常會映射到資料庫的表中)。 這些實體集的許可權可在服務代碼隱藏檔內配置。 圖 4顯示了 MyWcfDataService.svc.cs 檔的內容。

圖 4配置了實體集訪問規則的 WCF 資料服務代碼隱藏檔

Public class MyWcfDataService : DataService<SalesEntities>{  // This method is called only once to initialize service-wide policies.  Public static void InitializeService(IDataServiceConfiguration config)  {    config.SetEntitySetAccessRule("Orders", EntitySetRights.AllRead);    config.SetEntitySetAccessRule("Products", EntitySetRights.AllRead |       EntitySetRights.WriteAppend | EntitySetRights.WriteDelete);  }}

在這裡,我針對 Orders 實體集授予了“讀取”許可權,並且配置了 Products 實體集以允許完全讀取、插入新記錄和刪除現有記錄。

但是,由於 WCF 資料服務會自動基於此配置提供對您的資料的訪問,您無法直接存取碼,因此顯然無法實現任何特定授權邏輯。WCF 資料服務支援允許開發人員在用戶端和資料來源之間實現邏輯的偵聽器。例如,可以指定一個篩選特定實體集結果的查詢偵聽器。圖 5中的示例顯示了兩個添加到 MyWcfDataService 類的查詢偵聽器。

圖 5WCF 資料服務中的查詢偵聽器

[QueryInterceptor("Products")]Public Expression<Func<Product, bool>> OnQueryProducts(){  String userName =ServiceSecurityContext.Current.PrimaryIdentity.Name;  return product => product.CreatedBy == userName;}[QueryInterceptor("Orders")]Public Expression<Func<Comments, bool>> OnQueryOrders(){  bool userInPrivateOrdersRole =     Thread.CurrentPrincipal.IsInRole("PrivateOrders");  return order => !order.Private|| userInPowerUserRole;}

第一個偵聽器被應用於 Products 實體集並確保了使用者只能檢索其自己創建的產品。 第二個偵聽器確保了只有 PrivateOrders 角色的使用者才能讀取標記“Private”的訂單。

同樣,可以指定在插入、修改或刪除某個實體之前運行的更改偵聽器,如下所示:

[ChangeInterceptor("Products")]public void OnChangeProducts(Product product, UpdateOperations operations{  if (product.CreatedBy != Thread.CurrentPrincipal.Identity.Name)  {    throw new DataServiceException(      "Only products created by a user can be deleted by that user");  }}

乍一看,此代碼示例中的 OnChangeProducts 更改偵聽器似乎暴露了一個安全性漏洞,因為實現依賴于從外部資料來源(特別是“product”參數)傳遞的資料。 但是,當在 WCF 資料服務中刪除一個實體時,僅會將一個實體關鍵字從用戶端傳遞到伺服器。 這意味著必須再從資料庫中獲取一次實體(在本例為 Product),因此實體本身可以信任。

但是,對於現有實體更新的情況(例如,當指令引數等於 UpdateOperations.Change 時),產品參數為用戶端發送的反序列化實體,因此不可信。 用戶端應用程式可能已被修改,以將此特定產品的 CreatedBy 屬性指定為惡意使用者自己的身份,從而提升篡奪者的許可權。 這可能會允許不應修改某個產品的個人執行此操作。 為避免出現這種情況,建議您只基於實體關鍵字從受信任資料來源重新獲取原始實體,如圖 6所示。

圖 6防止未授權插入、更新和刪除操作的更改偵聽器

[ChangeInterceptor("Products")]Public void OnChangeProducts(Product product, UpdateOperations operations){  if (operations == UpdateOperations.Add)  {    product.CreatedBy = Thread.CurrentPrincipal.Identity.Name;  }  else if (operations == UpdateOperations.Change)  {    Product sourceProduct = this.CurrentDataSource.Products.Where(p =>      p.Id == product.Id).First();    if (sourceProduct.CreatedBy != Thread.CurrentPrincipal.Identity.Name)    {      throw new DataServiceException(        "Only records created by a user can be modified by that user");    }  }  else if (operations == UpdateOperations.Delete &&    product.CreatedBy != Thread.CurrentPrincipal.Identity.Name)  {    Throw new DataServiceException(      "Only records created by a user can be deleted by that user");  }}

由於此實現在很大程度上依賴于 Product 實體的 CreatedBy 屬性,因此從創建資料時起就以可靠的方式強制實施至關重要。圖 6還顯示了如何通過重寫用戶端為“添加”操作傳遞的任何值來實現此目標。

請注意,按照示例目前的情況,UpdateOperations.Change 類型的處理操作不是問題。圖 4中將服務配置為只允許對 Products 實體集執行 AllRead、WriteAppend(插入)和 WriteDelete 操作。因此,永遠無法為“更改”操作調用 ChangeInterceptor,因為服務會立即拒絕任何在此端點修改 Product 實體的請求。要啟用更新,圖 4中對 SetEntitySetAccessRule 的調用必須包括 WriteMerge、WriteReplace 或者兩者均包括。

跨域身份驗證

Silverlight 外掛程式可進行跨域 HTTP 請求。跨域調用是指對從其中下載 Silverlight 應用程式的域以外的其他域進行的 HTTP 請求。能夠進行此類調用在傳統上被視為一種安全性漏洞。它允許惡意開發人員向另一個網站(例如,您的網上銀行網站)發出請求,並自動轉發與該域關聯的任何 Cookie。這可能會使攻擊者能夠訪問相同流覽器進程內的另一個已登錄的會話。

為此,網站必須通過部署一個跨域策略檔選擇允許跨域調用。這是一個說明允許哪些類型的跨域調用(例如,從哪些域到哪些 URL)的 XML 檔。有關詳細資訊,請參閱“使服務跨域邊界可用”(msdn.microsoft.com/library/cc197955(VS.95))。

當決定向跨域調用公開任何敏感資訊時,您應該始終小心謹慎。但是,如果您決定這是一個需要隨身份驗證一起支援的方案,請務必注意,基於 Cookie 的身份驗證方法(如前面描述的表單驗證)不再適用。您可以考慮利用消息憑據,其中使用者名和密碼被傳遞給伺服器並在每次調用時進行驗證。WCF 通過 TransportWithMessageCredential 安全模式支援此操作。有關詳細資訊,請參閱“如何:使用消息憑據來保障用於 Silverlight 應用程式的服務的安全”(msdn.microsoft.com/library/dd833059(VS.95))。

當然,此方法從身份驗證過程中完全去除了 ASP.NET,因此難以一起利用 ASP.NET 授權(見上文討論)。

保護您的 Silverlight XAP 檔的安全

擔心 Silverlight 安全性的人常常會問:“怎樣才能保護我的 XAP 檔?”有時候,這一疑問背後的動機是為了保護代碼內所含的智慧財產權。在這種情況下,您需要進行模糊處理,以使人更加難以理解您的代碼。

另一個常見的動機是為了防止惡意使用者詢問代碼和瞭解 Silverlight 應用程式的工作原理,從而為他們提供侵入您的服務的可能性。

我通常從以下兩方面做出答覆。第一,儘管可以僅限經過身份驗證並且獲得授權的使用者下載您的 Silverlight 應用程式(.xap 檔),但是沒有任何理由相信這些使用者所懷的惡意比未經過身份驗證的使用者要少。在應用程式已被下載到用戶端之後,絕對沒有辦法再阻止使用者詢問代碼,以試圖提升其自己的許可權或將庫轉發給他人。進行模糊處理可以使此過程稍微更加困難一些,卻不足以確保您的應用程式安全。

第二,任何可以通過您的 Silverlight 應用程式合法調用服務的人也可以直接調用這些服務(例如,使用 Internet 流覽器和某些 JavaScript),記住這一點至關重要。您根本無法阻止這一情況的發生,因此將您的安全性工作的重點放在支援您的服務上極為重要。正確執行此操作,即使惡意使用者可以從您的 Silverlight 應用程式的代碼獲得什麼都無關緊要。儘管如此,有些人仍希望確保只有經過身份驗證的使用者才能訪問其 .xap 檔。這是可行的,但簡便程度取決於您所使用的 IIS 的版本以及您選擇的身份驗證方法。

如果您使用的是 Windows 身份驗證,那麼您可以使用 IIS 目錄安全性輕鬆地保護您的 .xap 檔。但如果您使用的是表單身份驗證,情況則會稍微複雜一些。在這種情況下,要由 FormsAuthenticationModule 截獲並驗證任何請求附帶的 Cookie,以及允許或拒絕訪問請求的資源。

因為 FormsAuthenticationModule 是一個 ASP.NET 模組,因此請求必須通過 ASP.NET 管道傳遞,以便執行此檢查。在 IIS6 (Windows Server 2003) 和先前的版本中,預設情況下,對 .xap 檔的請求將不會通過 ASP.NET 傳送。

儘管 IIS7 (Windows Server 2008) 引入了集成管道,其允許所有請求均通過 ASP.NET 管道傳送。如果您可以部署到 IIS7 並使用在集成管道模式下運行的應用程式池,則保護您的 .xap 檔安全的難度僅相當於保護您的 .svc 檔的安全(如前面“ASP.NET 授權”部分所述)。但是,如果您必須部署到 IIS6 或更早版本,您可能需要完成一些其他工作。

一種常用的方法涉及通過 ASP.NET 管道處理的另一個擴展流式處理組成您的 .xap 檔的位元組。典型方式是通過 IHttpHandler 實現(在 .ashx 檔中)。有關詳細資訊,請參閱“HTTP 處理常式簡介”(msdn.microsoft.com/library/ms227675(VS.80))。

另一種方法是更改 IIS 的配置,以便通過 ASP.NET 管道發送 .xap 檔。但是,由於這需要對您的 IIS 配置進行重大更改,因此前一種方法更常用。

使用表單身份驗證需要考慮的另一個問題是登錄螢幕。正如本文前面所建議,如果您選擇使用 ASP.NET Web 表單,則不存在任何問題。但是,如果您希望在 Silverlight 中編寫登錄螢幕,您將需要將應用程式拆分為幾部分。一部分(登錄模組)應可用於未經身份驗證的使用者,另一部分(受保護的應用程式)應僅可用於經過身份驗證的使用者。

您可以採用兩種方法:

  1. 擁有兩個單獨的 Silverlight 應用程式。第一個包含登錄對話方塊且位於網站未受保護的區域。成功登錄後,會重定向到指定網站受保護區域中的 .xap file 檔的頁面。
  2. 將您的應用程式拆分為兩個或多個模組。位於網站未受保護區域中的初始 .xap 將執行身份驗證過程。如果成功,該 .xap 檔隨後將從受保護區域請求一個檔,該檔可動態載入到 Silverlight 應用程式中。我最近發表了一篇如何實現此操作的博客文章(網址為:thejoyofcode.com/How_to_download_and_crack_a_Xap_in_Silverlight.aspx)。

Josh Twist* 是英國 Microsoft 應用程式開發諮詢團隊的首席顧問,可以在thejoyofcode.com上找到他撰寫的博客文章。*

衷心感謝以下技術專家對本文的審閱:Zulfiqar Ahmed、Chris Barker 和 Simon Ince