本文基於“Geneva”框架的預發布版本撰寫而成。所有信息均有可能發生變更。
本文使用以下技術:
Windows Communication Foundation
“Geneva”框架(以前稱為“Zermatt”)是用於構建基於聲明的應用程序和服務以及實現聯合安全方案的新框架代號。它的功能包括用於構建自定義安全令牌服務 (STS) 的探測功能、要求從 ASP.NET 應用程序進行聯合身份驗證的機制,以及簡化 ASP.NET 應用程序和 Windows Communication Foundation (WCF) 服務的基於聲明的授權的對象模型。
Geneva 框架還包括支持 Windows CardSpace 的功能(如托管信息卡頒發)和用於簡化 Windows CardSpace 登錄體驗創建過程的 ASP.NET 控件。(有關 Windows CardSpace 的詳細信息,請閱讀“身份標識:使用 Windows CardSpace 保證您的 ASP.NET 應用程序和 WCF 服務的安全”。)顯然,Geneva 框架包含多種安全功能,但其核心功能是基於聲明的安全性。
雖然 WCF 一直以來都提供了對基於聲明的安全模型的本機支持,但是 Geneva 框架簡化了運行時對聲明的訪問過程並提供支持基於聲明的授權的機制,使授權主體與已在 Microsoft .NET Framework 中提供的基於角色的授權主體一致,進而改善了此體驗。ASP.NET 應用程序利用 Geneva 框架獲得基於聲明的授權功能,該框架與增強基於角色的安全性的現有 ASP.NET 登錄控件兼容。在本文中,我將著重介紹實現基於聲明的安全模型的價值、描述如何使用 Geneva 框架獲得基於聲明的 WCF 服務,並將此方法與 WCF 在不使用 Geneva 框架的情況下處理基於聲明的安全性的方法進行比較。
在繼續閱讀本文之前,我建議您閱讀一下由 Keith Brown 和 Sesha Mani 合著的針對開發人員的 Geneva 框架白皮書。該白皮書概述了 Geneva 框架的功能以及有關基於聲明的安全性概念的一些背景,並介紹了如何在 ASP.NET 應用程序和 WCF 服務中啟用這些功能(但以前者為重點)。此外,您還可以在 Keith Brown 於 2007 年 9 月發布的“安全簡報”專欄中了解有關 WCF 和基於聲明的安全性的詳細信息。
為什麼要使用基於聲明的安全性?
您為什麼希望移至基於聲明的安全模型?在考慮使用 Geneva 框架實現解決方案之前,您必須知道此問題的答案。假定應用程序角色的定義從未發生過更改,並且只有一個身份驗證機制會將安全主體映射到這些角色,則基於角色的安全性就已經足夠了。但基於聲明的安全模型有助於應用程序和服務的設計,以便它們不會綁定到特定憑據類型或特定角色組。這是基於聲明的安全模型的價值主張之一。
從角色中分離應用程序和服務允許對角色名稱和意義進行更改,而不會影響系統。可以為經過身份驗證的用戶分配適用於授權的更精細的項目 — 聲明。聲明可以根據經過身份驗證的用戶進行分配,如圖 1 所示,也可以根據經過身份驗證的用戶的角色進行分配,如圖 2 所示。
圖 1 根據經過身份驗證的用戶分配聲明
圖 2 根據經過身份驗證的用戶的角色分配聲明
後者仍然支持根據聲明進行授權的模型,同時將角色用作在後台更輕松地對聲明進行分組的一種方法。從這個角度來看,雖然受信任的頒發者通過添加安全權益來保證聲明,但基於聲明的安全性具有的屬性類似於基於權限的安全性的屬性。(請注意,圖 1 和圖 2 說明了如何為經過身份驗證的用戶分配名稱聲明以及如何分配這幾個自定義權限聲明 — Create、Read、Update 和 Delete。)
當聲明和該聲明向其授予權限的功能或資源之間的關聯未發生更改(至少不經常發生更改)時,根據聲明授予訪問權限尤為有用。同時,規定如何為用戶和角色分配聲明的規則可以隨意更改,並且不會影響授權邏輯。例如,系統中的所有刪除操作可能都需要 Delete 聲明,然而,引入新的超級用戶角色時,管理員角色並不總是授予該聲明。
基於聲明的安全模型還可以幫助應用程序和服務支持多種憑據類型。一個簡單(且常見)的示例是依靠同一域中內部用戶的 Windows 憑據的應用程序和此域外的外部用戶的自定義用戶名和密碼帳戶。支持多種憑據類型可能會使身份驗證過程和相關授權代碼變得復雜。支持多種憑據類型的 ASP.NET 應用程序增加了每種憑據類型角色的登錄支持、配置和初始化的復雜程度。
WCF 服務還需要其他工作,才能支持不同的憑據類型,包括配置不同憑據的行為和適當初始化 AuthorizationContext。如果 ASP.NET 應用程序和 WCF 服務可以接收包含所需聲明(角色或其他內容)的憑據,則可以將授權工作規范化為通用規則集,並且不受對用戶進行身份驗證所采用的方式的限制。
理想情況下,STS 可以處理不同憑據類型的身份驗證,並生成包含這些聲明的安全令牌。此令牌很可能是 SAML(安全聲明標記語言)令牌,可用於根據服務的數字簽名對服務進行身份驗證,並且該令牌生成的聲明可用於授予訪問權限。
聯合安全方案本身也適用於基於聲明的安全性。在聯合安全方案中,可以使用用戶自己的安全域對用戶進行身份驗證,而且由此域頒發的令牌可用於對另一個域進行身份驗證。
安全令牌服務
STS 在基於聲明的安全方案(無論該方案是否為聯合方案)中起著重要作用。要根據聲明授予訪問權限,必須存在應用程序可以使用的受信任聲明集。對用戶進行身份驗證後,STS 可以頒發包含請求聲明的安全令牌。應用程序只需要信任安全令牌即可,這通常意味著信任其數字簽名。圖 3 描述了兩個應用程序(依賴方),即 WCF 服務和 ASP.NET 應用程序,這兩個應用程序都信任 STS 頒發的 SAML 令牌。通信的高級流程如下所示:
圖 3 依賴方信任 STS 頒發的令牌
用戶對 STS 進行身份驗證。
STS 為經過身份驗證的用戶分配聲明,並構建 SAML 令牌以包含這些聲明(如 SAML 屬性)。STS 使用其私鑰 (IPKey) 對令牌進行簽名,並對令牌進行加密以用於使用根據請求提供的公鑰 (RPKey) 的應用程序。
客戶端應用程序或浏覽器向應用程序(依賴方)出示令牌。此令牌在 WCF 方案中隨消息一起傳遞,而在 ASP.NET 方案中作為 Cookie 進行傳遞。如果令牌簽名可信 (IPKey),則其包含的聲明對於授權也是可信的。
如果某一應用程序或服務支持多種憑據類型,則 STS 可以處理每種憑據類型的身份驗證步驟,並頒發安全令牌,該令牌包含適合每個經過身份驗證的用戶的聲明。同樣,應用程序只需信任安全令牌即可,根本不用考慮 STS 所支持的對每個用戶進行身份驗證的憑據類型。圖 4 說明了接受使用 Windows、用戶名和密碼、證書或 SAML 令牌進行身份驗證的 STS,並為經過身份驗證的用戶生成適當的聲明。
圖 4 STS 對多種憑據類型進行身份驗證
圖 3 和圖 4 說明了在相同安全域中使用 STS 的方案。在聯合方案中,會在兩個或多個安全域之間建立信任關系,以便用戶可以對管理其憑據的域進行身份驗證,同時仍可以獲得另一個域中的資源的訪問權限。
聯合身份驗證降低了與身份管理相關的許多風險。使用聯合身份驗證,就不必再跨多個應用程序或域維護用戶憑據,這有助於降低與跨域配置和解除配置帳戶相關的風險,如忘記刪除多個位置的帳戶。當無需管理帳戶的多個副本時,自然也就不存在密碼同步的問題了。除了上述優點,聯合身份驗證還有助於實施單一登錄 (SSO) 方案,因為用戶登錄到一個應用程序後,就會授予該用戶另一個應用程序(可能位於另一安全域中)的訪問權限,不需要再次進行身份驗證。圖 5 說明了適用於 Web 應用程序的聯合方案。流程如下所示:
用戶浏覽至域 B 中的 Web 應用程序,然後域 B 中的 STS 對該用戶進行身份驗證。
STS 為該用戶頒發 SAML 令牌,然後此令牌作為 Cookie 返回到浏覽器。
用戶浏覽至域 A 中的 Web 應用程序,將 SAML 令牌作為 Cookie 進行傳遞。
域 A 中的 STS 信任令牌簽名,這是因為它與域 B 中的 STS 存在信任關系。
域 A 中的 STS 為該用戶頒發新的 SAML 令牌(包含與域 A 相關的聲明)。此令牌作為 Cookie 返回到浏覽器。
圖 5 描述域之間信任關系的聯合方案
域 A 中的 STS 和域 B 中的 STS 之間的信任關系是指,前者接受後者頒發的令牌並以此作為身份驗證的憑據。域 B 頒發的聲明可能包括用戶信息(用戶名和電子郵件地址等)和域 A(即 DomainAReadOnly)所理解的權限。域 A 可以將這些合作伙伴聲明轉換為對域 A 托管的應用程序有意義的聲明,例如,授予 Read 聲明。
STS 頒發的聲明是公開的,這意味著這些方案具有許多潛在的配置(如委派身份驗證和聯合驗證),但這使您可以基本了解基於聲明的安全性的用處所在以及這些聲明的頒發方式。接下來,我將介紹實現 WCF 服務和 ASP.NET 應用程序的一些細節。
基於聲明的安全性支持
先前,我在白皮書中曾講過,Geneva 框架具有許多功能。您可以使用 Geneva 框架構建自定義 STS,而無需編寫用於公開 WS-Trust 終結點或構建包含聲明的 SAML 令牌的所有探測功能。通過此框架,您還可以頒發托管信息卡,以支持身份信息選擇器,如 Windows 平台上的 Windows CardSpace。您可以更輕松地支持 ASP.NET 應用程序中的 Windows CardSpace 登錄,並利用對 WCF 服務和 ASP.NET 應用程序的集成的、基於聲明的支持。
Geneva 框架的主要目標是簡化基於聲明的應用程序的構建過程。要實現此目標,必須對所有基於 .NET 的應用程序通用的基於角色的現有安全模型進行基於聲明的擴展、使用處理安全令牌的掛接功能提取各自在運行時交互的聲明;在 ASP.NET 環境下,使用觸發調用的機制,以調用可以頒發包含此應用程序所需聲明的令牌的 STS。
Geneva 框架之前的 WCF 服務
WCF 服務旨在從一開始就支持基於聲明的安全性。將消息發送給服務操作後,隨該消息一起提供的每個安全令牌都將轉換為一組聲明,這些聲明可通過執行操作的 ServiceSecurityContext 進行訪問。如果安全令牌是 Windows 或 UserName 令牌,則開發人員通常會依賴附加到請求線程的安全主體,使用基於角色的經典安全性來授權調用。如果安全主體是一份證書或 SAML 令牌,則這些聲明會更有意義,並且開發人員很可能會訪問 AuthorizationContext 以評估聲明。
假如有這樣一個示例:WCF 服務公開“創建”、“讀取”、“更新”和“刪除”(CRUD) 操作,並希望根據用戶對應用程序所擁有的 CRUD 權限來授權調用。DeleteSomething 操作要求經過身份驗證的調用方擁有刪除權限,這由自定義權限聲明(如圖 1 和圖 2 中所示)表示。圖 6 顯示了用於在 AuthorizationContext 中搜索 Delete 聲明的代碼。該代碼遍歷 AuthorizationContext 中的聲明集以查找此聲明。如果在所有受信任的聲明集中都找不到此聲明,則將引發 SecurityException。但至少可以說,用這種方法搜索 AuthorizationContext 的代碼非常復雜。Geneva 框架提供了評估聲明的替代方法,大大簡化了 WCF 中基於聲明的授權。
圖 6 用於權限聲明的 WCF AuthorizationContext
public string DeleteSomething() { AuthorizationContext authContext = ServiceSecurityContext.Current.AuthorizationContext; bool foundClaim = false; foreach (ClaimSet cs in authContext.ClaimSets) { Claim claim = new Claim("http://schemas.contoso.com/samples/ 2008/09/claims/permission","Delete", Rights.PossessProperty); if (cs.ContainsClaim(claim)) { foundClaim = true; break; } } if (!foundClaim) throw new SecurityException( "Access is denied. Required claims not satisfied."); return String.Format("DeleteSomething() called by user {0}", System.Threading.Thread.CurrentPrincipal.Identity.Name); }
頒發的令牌和 WSFederationHttpBinding
WCF 提供聯合綁定,以支持 WCF 服務希望獲得由 STS 頒發的令牌的方案。這通常是指 SAML 令牌,但這種要求並不嚴格。WSFederationHttpBinding 是原始標准綁定,支持頒發的令牌,而 WS2007FederationHttpBinding 是對此綁定的更新,支持此綁定采用的 WS-* 協議的最終版本。鑒於本討論的目的,我將使用 WSFederationHttpBinding 配置 WCF 服務終結點,這些終結點需要由通過 Geneva 框架構建的自定義 STS 所頒發的 SAML 1.1 令牌。
在本示例中,服務約定 ICrudService 包含四個服務操作,如圖 7 所示。這些操作表示 CRUD 操作,並且服務實現將依靠 CRUD 權限聲明(如圖 1 和圖 2 中所示的聲明)來授予對每個操作的訪問權限。
圖 7 公開 CRUD 操作的 WCF 服務約定
[ServiceContract(Namespace="http://www.contoso.com/samples/2008/09")] public interface ICrudService { [OperationContract] string CreateSomething(); [OperationContract] string ReadSomething(); [OperationContract] string UpdateSomething(); [OperationContract] string DeleteSomething(); }
圖 8 說明了一個示例 WCF 配置,其中一個服務公開了 WSFederationHttpBinding 終結點及其相關的行為配置。綁定配置包括指明所需令牌格式 (SAML 1.1)、所需的 Claim 類型(名稱和權限聲明)以及 STS WS-Trust 終結點地址和元數據交換地址的設置。此配置提供的信息足以讓客戶端生成一個代理,此代理能夠對 STS 進行身份驗證以檢索 SAML 令牌,然後使用此令牌對 WCF 服務進行身份驗證。
圖 8 具有 WSFederationHttpBinding 終結點的 WCF 配置
<system.serviceModel> <services> <service name="ClaimsBasedServices.CrudService" behaviorConfiguration ="serviceBehavior"> <endpoint contract="ClaimsBasedServices.ICrudService" binding= "wsFederationHttpBinding" bindingConfiguration="wsFed"/> <endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex"/> </service> </services> <bindings> <wsFederationHttpBinding> <binding name="wsFed" > <security mode="Message"> <message issuedTokenType="http://docs.oasis-open.org/wss/ oasis-wss-saml-token-profile-1.1#SAMLV1.1" > <claimTypeRequirements> <add claimType="http://schemas.xmlsoap.org/ws/2005/05/ identity/claims/name" isOptional="false"/> <add claimType="http://schemas.contoso.com/samples/2008/09/ claims/permission" isOptional="false"/> </claimTypeRequirements> <issuer address="http://localhost:51213/TokenIssuer/Service.svc" /> <issuerMetadata address="http://localhost:51213/TokenIssuer/ Service.svc/mex" /> </message> </security> </binding> </wsFederationHttpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior name="serviceBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceCredentials> <issuedTokenAuthentication allowUntrustedRsaIssuers="false"> <knownCertificates> <add findValue="IPKey" storeLocation ="LocalMachine" storeName="TrustedPeople" x509FindType "FindBySubjectName"/> </knownCertificates> </issuedTokenAuthentication> <serviceCertificate findValue="RPKey" storeLocation= "LocalMachine" storeName="My" x509FindType="FindBySubjectName"/> </serviceCredentials> <serviceAuthorization principalPermissionMode="None" /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
服務行為配置包含的一些設置值得說明一下。在 <serviceCredentials> 中,<issuedTokenAuthentication> 部分指示如何對 SAML 令牌進行身份驗證。此配置指示只允許受信任的頒發者,也就是說,SAML 令牌必須由已知證書簽署。此方案中的已知證書具有使用者名稱 IPKey。STS 將使用 IPKey 簽署 SAML 令牌,因此,SAML 令牌中包含的所有聲明都將受到信任。雖然 Geneva 框架也具有指定受信任頒發者的配置,但啟用 Geneva 框架時,本部分的內容仍適用。
<serviceAuthorization> 部分會影響進行身份驗證後附加到請求線程的安全主體的類型。通常,對於已頒發的令牌方案來說,如果將此令牌方案配置為“自定義”,則需要您指定自定義授權策略(實現 IAuthorizationPolicy 的類型)。授權策略負責為請求線程創建安全主體(IPrincipal 類型),並將所有相關聲明附加到 AuthorizationContext。在這種情況下,會將此類型設置為“無”,因為 Geneva 框架啟用 WCF 服務後,此類型將不再有用。
為 WCF 服務啟用 Geneva 框架
以 Geneva 框架功能為基礎的基於聲明的安全模型只需通過幾個簡單的步驟即可實現。第一步是添加對 Microsoft.Identity 程序集(屬於 Geneva 框架的核心程序集)的引用。然後,必須針對 Geneva 框架運行時功能初始化每個 WCF 服務的 ServiceHost,其中包括向 ServiceHost 注冊受信任的令牌頒發者列表。配置此框架的最終結果是,將安全主體附加到每個調用的請求線程中 — 獲得受信任頒發者提供的聲明集合以進行授權。
FederatedServiceCredentials 從 ServiceCredentials 類型派生而來,此類型公開一個靜態方法,用於為 Geneva 框架初始化 ServiceHost 實例。圖 9 說明了初始化 ServiceHost 的代碼在初始化環境中的適當位置。
圖 9 為 Geneva 框架初始化 ServiceHost
ServiceHost host = new ServiceHost(typeof(Services.RelyingParty)); try { FederatedServiceCredentials.ConfigureServiceHost(host, new TrustedIssuerNameRegistry()); host.Open(); Console.ReadLine(); } finally { if (host.State != CommunicationState.Faulted) host.Close(); else host.Abort(); } public class TrustedIssuerNameRegistry : IssuerNameRegistry { public override string GetIssuerName(SecurityToken securityToken) { X509SecurityToken x509Token = securityToken as X509SecurityToken; if (x509Token != null) { if (x509Token.Certificate.SubjectName.Name == "CN=IPKey") { return x509Token.Certificate.SubjectName.Name; } } throw new SecurityTokenException("Token signature is not trusted by the issuer name registry. "); } }
打開 ServiceHost 實例之前,先調用 ConfigureServiceHost 方法。此方法執行下列主要操作:讀取 <microsoft.identityModel> 配置部分以初始化運行時的各個方面,如令牌處理程序、受信任的頒發者以及令牌有效性的最大時鐘偏差。然後,將 principalPermissionMode(請參見圖 8)設置為“自定義”,並添加名為 ServiceAuthorizationPolicy 的自定義授權策略(IAuthorizationPolicy 類型)。ServiceAuthorizationPolicy 負責創建 ClaimsPrincipal 類型和初始化 Thread.CurrentPrincipal。它還將刪除其他授權策略和清除為請求添加到 AuthorizationContext 中的聲明集。這是因為 Geneva 框架希望您繼續依靠 ClaimsPrincipal 來評估聲明,而不是使用 AuthorizationContext 進行評估。
圖 9 中的代碼將 IssuerNameRegistry 類型的實現傳遞到 ConfigureServiceHost,這樣就不必在 <microsoft.identityModel> 配置部分中指定此類型了。對 GetIssuerName 的覆蓋負責根據受信任的頒發者列表檢查簽署令牌。本示例使用硬編碼的受信任頒發者名稱 IPKey。稍後,我將討論 <microsoft.identityModel> 部分的元素,因為它與為 WCF 服務配置 Geneva 框架相關。
請求聲明
如果服務已啟用 Geneva 框架,則 ClaimsPrincipal 實例將附加到每個請求線程,附帶經過身份驗證的用戶的聲明。要授權對應用程序代碼和資源的訪問權限,您可以遍歷聲明集合以查找特定 Claim 類型和值。以下代碼可以訪問 ClaimsIdentity 類型公開的“聲明”集合,使用管理員角色的 Uri 值搜索角色聲明:
ClaimsIdentity identity = Thread.CurrentPrincipal.Identity as ClaimsIdentity; if (!identity.Claims.Exists(c=> c.ClaimType=="http://schemas.microsoft.com/ws/2006/04/identity/ claims/role" && c.Value=="http://schemas.contoso.com/ samples/2008/09/roles/administrators")) throw new SecurityException("Access is denied.");
“聲明”集合可能包含來自不同頒發者的聲明,因此,可以檢查特定頒發者的聲明。但是,此步驟通常可有可無,因為既然已建立了對頒發者的信任關系,那麼在授權訪問時究竟是誰頒發了聲明就不重要了。
雖然通過 ClaimsIdentity 實例直接訪問“聲明”集合非常便利,但 ClaimsPrincipal 提供了可用於請求角色聲明的 IsInRole 實現,如下所示:
if !Thread.CurrentPrincipal.IsInRole
("http://schemas.contoso.com/samples/2008/09/roles/administrators"))
throw new SecurityException("Access is denied.");
此方法假定 STS 頒發具有與應用程序相關的適當值的角色聲明。默認情況下,Geneva 框架中的角色聲明由以下 Uri 表示:“http://schemas.microsoft.com/ws/2006/04/identity/claims/role”。實際角色可以是任何字符串,但是在本例中,它是指示應用程序角色的自定義 Uri:“http://schemas.contoso.com/samples/2008/09/roles/administrators”。
雖然 IsInRole 是執行基於聲明的安全檢查的一種便利方法,但是也可以使用 PrincipalPermission 和 PrincipalPermissionAttribute 執行經典權限請求。這兩項都依靠附加到請求線程的 IPrincipal 類型,在本例中,ClaimsPrincipal 執行此任務。
編程請求可以通過動態方式執行:
PrincipalPermission p = new PrincipalPermission(null,
"http://schemas.contoso.com/samples/2008/09/roles/administrators",
true);
p.Demand();
請求將根據線程的 ClaimsPrincipal 執行 IsInRole 檢查;如果無法滿足此請求,也可以說,如果找不到指定的角色聲明,則將引發異常。PrincipalPermission 實例的集合可以組合為一個 PermissionSet,以查看符合條件的幾個角色之一。
當然,更好的解決方案是將 PrincipalPermissionAttribute 應用於服務操作,如下所示:
[PrincipalPermission(SecurityAction.Demand, Role =
"http://schemas.contoso.com/samples/2008/09/roles/administrators")]
public string CreateSomething()
此屬性構建 PrincipalPermission 類型並執行相同的請求,但這要在執行應用此屬性的操作之前完成。此模型的優點在於它的聲明性特性,開發人員可以查看其服務操作所需的角色。還可以堆疊此屬性,以便多個角色可以滿足權限請求。
配置 <microsoft.identityModel>
Geneva 框架依賴新的配置部分初始化其環境:<microsoft.identityModel>。正如先前在白皮書中討論的一樣,Geneva 框架功能使用此配置部分配置 ASP.NET 和 WCF 應用程序,但在本部分中,我將著重介紹與啟用 Geneva 框架的 WCF 服務相關的核心元素。
要啟用對該新部分的支持,您的 app.config 或 web.config 中必須包含以下部分(具體取決於 WCF 服務的托管模型):
<configSections>
<section name="microsoft.identityModel"
type="Microsoft.IdentityModel.Configuration.
MicrosoftIdentityModelSection,
Microsoft.IdentityModel,
Version=0.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</configSections>
<microsoft.identityModel> 配置部分並不包括任何所需的元素,但是您可以使用任何核心設置提供圖 10 中列出的元素的聲明性初始化。
圖 10 為 Geneva 框架初始化 ServiceHost
圖 11 中顯示了初始化這些值的配置部分的一個示例。在配置中指定 issuerNameRegistry 類型時,圖 9 中所示的服務主機的初始化將發生更改,如下所示:
FederatedServiceCredentials.ConfigureServiceHost(host);
圖 11 WCF 的核心 Geneva 框架功能
<microsoft.identityModel>
<issuerNameRegistry type="Services.TrustedIssuerNameRegistry,
Services"/>
<SecurityTokenHandlers>
<remove type="Microsoft.IdentityModel.Tokens.Saml11.
Saml11SecurityTokenHandler, Microsoft.IdentityModel,Version=0.4.1.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add type="Microsoft.IdentityModel.Tokens.Saml11.
Saml11SecurityTokenHandler, Microsoft.IdentityModel,
Version=0.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<samlSecurityTokenRequirement audienceUriMode="Never">
<nameClaimType value="http://schemas.contoso.com/ws/2005/05/
claims/name"/>
<roleClaimTypes>
<add value="http://schemas.contoso.com/samples/2008/09/claims/
permission"/>
</roleClaimTypes>
</samlSecurityTokenRequirement>
</add>
</SecurityTokenHandlers>
</microsoft.identityModel>
對於令牌處理程序,您可以創建自定義 securityTokenHandler,也可以使用本部分初始化現有令牌處理程序的功能,圖 11 中顯示的是後者。在本示例中,使用自定義名稱和角色聲明類型對 SAML 1.1 令牌處理程序進行初始化(請回憶一下先前白皮書講述的內容,角色聲明類型集合在 Geneva 框架的未來版本中可能會成為單一值,而不是集合)。此處指定的角色聲明類型是“schemas.contoso.com/samples/2008/09/claims/permission”,這意味著 STS 可以頒發類似於圖 1 或圖 2 中所示的權限聲明,並且 WCF 服務可以使用先前討論的基於角色的經典安全方法來執行指定權限的權限請求。
使用 IIS 托管掛接 Geneva 框架
根據您對 WCF 的體驗級別,當在 IIS 中托管服務時,您可能熟悉或不熟悉自定義 ServiceHost 初始化的機制,但這是您應該熟悉的,因為只有熟悉此機制,您才可以為 ServiceHost 啟用 Geneva 框架。要掛接 ServiceHost 初始化,您應該在 @ServiceHost 指令中提供自定義 ServiceHostFactory 類型,如下所示:
<%@ ServiceHost Factory="ClaimsBasedServices.
ClaimsBasedServiceHostFactory"
Service="ClaimsBasedServices.CrudService" %>
在本示例中,ClaimsBasedServiceHostFactory 類型負責構建指定服務類型的 ServiceHost 實例。您可以直接在工廠中構建 ServiceHost 類型,然後在將該實例傳遞到運行時之前為 Geneva 框架服務初始化此類型,也可以創建自定義 ServiceHost 類型,然後覆蓋 InitializeRuntime 方法以在打開服務通道之前提供自定義代碼,圖 12 中顯示的是後者。
圖 12 對 IIS 托管的 WCF 服務啟用 Geneva
public class ClaimsBasedServiceHostFactory: ServiceHostFactory
{
public ClaimsBasedServiceHostFactory()
{
}
public override System.ServiceModel.ServiceHostBase CreateServiceHost(
string constructorString, Uri[] baseAddresses)
{
Type t = Type.GetType(string.Format("{0}, {1}", constructorString,
constructorString.Substring(0, constructorString.IndexOf("."))));
return new ClaimsBasedServiceHost(t, baseAddresses);
}
protected override System.ServiceModel.ServiceHost CreateServiceHost(
Type serviceType, Uri[] baseAddresses)
{
return new ClaimsBasedServiceHost(serviceType, baseAddresses);
}
}
public class ClaimsBasedServiceHost: ServiceHost
{
public ClaimsBasedServiceHost(object singletonInstance, params Uri[]
baseAddresses): base(singletonInstance, baseAddresses)
{
}
public ClaimsBasedServiceHost(Type serviceType, params
Uri[] baseAddresses) : base(serviceType, baseAddresses)
{
}
protected override void InitializeRuntime()
{
FederatedServiceCredentials.ConfigureServiceHost(this, new
TrustedIssuerNameRegistry());
base.InitializeRuntime();
}
}
從 WCF 到 Geneva 框架
當您為 WCF 服務啟用 Geneva 框架時,尤其是,如果此時的您是一位經驗豐富的 WCF 開發人員,並且已經使用 Geneva 框架之前的技術實現了基於聲明的解決方案,可能會出現一些常見問題。您可能遇到的一個問題與 Geneva 框架不使用 System.IdentityModel.Claims 命名空間中的 ClaimSet 和 Claim 類型的原因有關。答案很簡單。在經典的 WCF 中,要根據調用方的聲明來授權調用方,您必須遍歷 AuthorizationContext(一個復雜的編程模型,如上文所述)中的 ClaimSet 實例集合。Geneva 框架消除了與 ClaimSet 相關的一些復雜性並向 Claim 類型添加必要功能,從而改進了此模型。
ClaimSet 類型根據頒發者對聲明進行分組。此模型具有一些限制,例如,如果頒發者不同,則無法合並兩個 ClaimSet 實例。無法檢查所有聲明的單一集合。另一方面,Geneva 框架通過可在授權期間檢查的 ClaimsIdentity 公開了單一的聲明集合。運行時確定某一特定頒發者提供的聲明可信後(因為頒發者可信),對授權代碼造成影響的就是 Claim 類型和值,而不是頒發者。
Geneva 框架生成的聲明集合使用 Microsoft.IdentityModel.Claims 命名空間中的新 Claim 類型。此 Claim 類型具有 Issuer 屬性,因此,您仍然可以通過聲明集合執行 LINQ 查詢來收集來自特定頒發者的所有聲明(如果您確實希望這樣做)。
您很可能還希望了解為什麼聲明的 Issuer 屬性是一個簡單的 String 類型,而不是 ClaimSet。WCF 中的 Issuer 屬性是一個遞歸屬性,其中每個頒發者都由 ClaimSet 定義,而該 ClaimSet 可能包含由另一個 ClaimSet 定義的頒發者,依次類推,直至遞歸到根(自簽名)頒發者。這不僅是一個處理起來比較困難的編程模型,而且在確定您信任令牌身份驗證期間的臨時頒發者後,此鏈條在運行時就變得沒那麼重要了。鑒於此原因,Geneva 框架將 Issuer 屬性更改為頒發者的字符串表示形式,但仍然會使用令牌簽名建立信任關系,字符串僅僅是以友好方式描述頒發者的一種方式。
看到啟用 Geneva 框架後便會清除 AuthorizationContext 後,您也許也會感到驚訝。WCF 基於聲明的安全功能特別依賴於 AuthorizationContext,因為 AuthorizationContext 是從受信任的安全令牌中提取而來的,供訪問請求的可用聲明集時使用。不幸的是,AuthorizationContext 在 WCF 服務之外沒有任何意義,這限制了聲明編程模型的應用。
Geneva 框架的目標是:更輕松地支持所有基於 .NET 的應用程序中的基於聲明的安全性,同時利用 .NET 安全現有的各個方面。通過將 ClaimsPrincipal 分配給請求線程,便可輕松地將基於角色的現有安全性實現遷移到基於聲明的安全性。此外,這為 WCF 服務和 ASP.NET 應用程序提供了一致的體驗。使用安全主體的另一個優點是,創建新線程時,將使用與原始線程相同的安全主體來初始化這些線程。另一方面,AuthorizationContext 不會自動傳遞到新線程。
因此,假如您為 WCF 服務啟用 Geneva 框架,則 Geneva 框架會清除 AuthorizationContext,而您也就不再依靠 AuthorizationContext 中的聲明集來執行基於聲明的安全檢查。所以,當啟用 Geneva 框架時,必須遷移 WCF 服務,該服務當前利用自定義授權策略填充 AuthorizationContext 並為請求線程創建安全主體。
將應用程序遷移至 Geneva 框架
閱讀了面向開發人員的上述白皮書和本文後,您應該對使用 Geneva 框架構建基於聲明的 WCF 服務和 ASP.NET 應用程序的要求有了很好的了解。讓我來總結一下這些要求對 WCF 和 ASP.NET 的影響。
要為 WCF 服務啟用 Geneva 框架,要求您至少執行下列步驟:使用一個聯合安全綁定公開服務的終結點;為 Geneva 框架初始化 ServiceHost;提供受信任的頒發者列表;根據基於聲明的安全模型配置角色聲明類型;將權限請求應用到操作以根據角色聲明授權調用。已經基於聲明的 WCF 服務將需要進行一些更改,因為直接通過附加到請求線程的安全主體就可以訪問聲明,而不再需要通過 AuthorizationContext 了。
ASP.NET 應用程序可以利用 Geneva 框架實現聯合安全方案,將身份驗證委派給 STS,並使用受信任的頒發者(可通過 ClaimsPrincipal 和 ClaimsIdentity 實例訪問)提供的聲明來初始化請求線程。已基於角色的應用程序希望獲得與移至基於聲明的方案中的角色相匹配的聲明,但新的應用程序可以選擇使用更精細的聲明,如本文中討論的權限。從任一方面來講,ASP.NET 登錄控件和其他基於角色的安全權限請求都將查看附加到請求線程的 ClaimsPrincipal 以授權調用。在後續文章中,我將進一步介紹 Geneva 框架,以探討如何為基於聲明的聯合安全方案構建自定義 STS。
Michele Leroux Bustamante 是 IDesign Inc. 的首席架構師、San Diego 的 Microsoft 區域總監和互聯系統的 Microsoft MVP。她的最新著作是《學習 WCF》。可通過 [email protected] 或訪問 idesign.net 與她取得聯系。Michele 的博客網址是 dasblonde.net。