SAML 權杖提供者
這個範例會示範如何實作自訂的用戶端 SAML 權杖提供者。 Windows Communication Foundation (WCF) 中的權杖提供者是用於向安全性基礎結構提供認證。 一般而言,權杖提供者會檢查目標並發行適當的認證,讓安全性基礎結構能夠保護訊息的安全。 WCF 會隨附預設的認證管理員權杖提供者。 WCF 還會隨附 CardSpace 權杖提供者。 自訂權杖提供者適用於下列情況:
如果您有這些權杖提供者無法使用的認證存放區。
如果您要提供自有的自訂機制,以將認證從使用者提供詳細資料的時間點,轉換成當 WCF 用戶端架構使用認證時。
如果您要建置自訂權杖。
此範例會示範如何建置自訂權杖提供者,其會允許使用從 WCF 用戶端架構外部取得的 SAML 權杖。
簡而言之,這個範例示範下面的情形:
如何使用自訂權杖提供者設定用戶端。
如何將 SAML 權杖傳遞至自訂用戶端認證。
如何將 SAML 權杖提供給 WCF 用戶端架構。
用戶端如何使用伺服器的 X.509 憑證來驗證伺服器。
服務會公開兩個端點,以便與使用設定檔 App.config 定義的服務進行通訊。每個端點是由位址、繫結及合約所組成。 繫結已設定成會使用訊息安全性的標準 wsFederationHttpBinding
。 其中一個端點會預期用戶端經由使用對稱式證明金鑰的 SAML 權杖驗證,而另一個端點則預期用戶端經由使用非對稱式證明金鑰的 SAML 權杖驗證。 服務也會使用 serviceCredentials
行為來設定服務憑證。 serviceCredentials
行為可以讓您設定服務憑證。 服務憑證是由用戶端用來驗證服務並提供訊息保護。 下列組態會參考在安裝範例期間所安裝的 localhost 憑證,如本主題結尾處的安裝指示所述。 serviceCredentials
行為也會允許您設定受信任可簽署 SAML 權杖的憑證。 下列組態會參考在範例期間安裝的 'Alice' 憑證。
<system.serviceModel>
<services>
<service
name="Microsoft.ServiceModel.Samples.CalculatorService"
behaviorConfiguration="CalculatorServiceBehavior">
<host>
<baseAddresses>
<!-- configure base address provided by host -->
<add
baseAddress="http://localhost:8000/servicemodelsamples/service/" />
</baseAddresses>
</host>
<!-- use base address provided by host -->
<!-- Endpoint that expect SAML tokens with Symmetric proof keys -->
<endpoint address="calc/symm"
binding="wsFederationHttpBinding"
bindingConfiguration="Binding1"
contract="Microsoft.ServiceModel.Samples.ICalculator" />
<!-- Endpoint that expect SAML tokens with Asymmetric proof keys -->
<endpoint address="calc/asymm"
binding="wsFederationHttpBinding"
bindingConfiguration="Binding2"
contract="Microsoft.ServiceModel.Samples.ICalculator" />
</service>
</services>
<bindings>
<wsFederationHttpBinding>
<!-- Binding that expect SAML tokens with Symmetric proof keys -->
<binding name="Binding1">
<security mode="Message">
<message negotiateServiceCredential ="false"
issuedKeyType="SymmetricKey"
issuedTokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1" />
</security>
</binding>
<!-- Binding that expect SAML tokens with Asymmetric proof keys -->
<binding name="Binding2">
<security mode="Message">
<message negotiateServiceCredential ="false"
issuedKeyType="AsymmetricKey"
issuedTokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1" />
</security>
</binding>
</wsFederationHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="CalculatorServiceBehavior">
<!--
The serviceCredentials behavior allows one to define a service certificate.
A service certificate is used by a client to authenticate the service and provide message protection.
This configuration references the "localhost" certificate installed during the setup instructions.
-->
<serviceCredentials>
<!-- Set allowUntrustedRsaIssuers to true to allow self-signed, asymmetric key based SAML tokens -->
<issuedTokenAuthentication allowUntrustedRsaIssuers ="true" >
<!-- Add Alice to the list of certs trusted to issue SAML tokens -->
<knownCertificates>
<add storeLocation="LocalMachine"
storeName="TrustedPeople"
x509FindType="FindBySubjectName"
findValue="Alice"/>
</knownCertificates>
</issuedTokenAuthentication>
<serviceCertificate storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName"
findValue="localhost" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
下列步驟示範如何開發 SAML 自訂權杖提供者,並將其整合至 WCF 安全性架構:
撰寫自訂 SAML 權杖提供者。
此範例會實作自訂 SAML 權杖提供者,此權杖提供者會根據建構階段所提供的 SAML 判斷提示傳回安全性權杖。
為了執行這個工作,此自訂權杖提供者會衍生自 SecurityTokenProvider 類別,並覆寫 GetTokenCore 方法。 這個方法會建立並傳回新的
SecurityToken
。protected override SecurityToken GetTokenCore(TimeSpan timeout) { // Create a SamlSecurityToken from the provided assertion SamlSecurityToken samlToken = new SamlSecurityToken(assertion); // Create a SecurityTokenSerializer that will be used to // serialize the SamlSecurityToken WSSecurityTokenSerializer ser = new WSSecurityTokenSerializer(); // Create a memory stream to write the serialized token into // Use an initial size of 64Kb MemoryStream s = new MemoryStream(UInt16.MaxValue); // Create an XmlWriter over the stream XmlWriter xw = XmlWriter.Create(s); // Write the SamlSecurityToken into the stream ser.WriteToken(xw, samlToken); // Seek back to the beginning of the stream s.Seek(0, SeekOrigin.Begin); // Load the serialized token into a DOM XmlDocument dom = new XmlDocument(); dom.Load(s); // Create a KeyIdentifierClause for the SamlSecurityToken SamlAssertionKeyIdentifierClause samlKeyIdentifierClause = samlToken.CreateKeyIdentifierClause<SamlAssertionKeyIdentifierClause>(); // Return a GenericXmlToken from the XML for the // SamlSecurityToken, the proof token, the valid from and valid // until times from the assertion and the key identifier clause // created above return new GenericXmlSecurityToken(dom.DocumentElement, proofToken, assertion.Conditions.NotBefore, assertion.Conditions.NotOnOrAfter, samlKeyIdentifierClause, samlKeyIdentifierClause, null); }
撰寫自訂安全性權杖管理員。
SecurityTokenManager 類別會用來建立特定 SecurityTokenProvider 物件的 SecurityTokenRequirement,而該提供者會透過
CreateSecurityTokenProvider
方法傳遞給該管理員。 安全性權杖管理員也會用來建立權杖驗證器與權杖序列化程式,但是這些不在本範例的討論範圍。 在這個範例中,自訂安全性權杖管理員繼承自 ClientCredentialsSecurityTokenManager 類別,並且會覆寫CreateSecurityTokenProvider
方法,以便在傳遞的權杖需求表示要求 SAML 權杖時傳回自訂 SAML 權杖提供者。 如果用戶端認證類別 (請參閱步驟 3) 尚未指定判斷提示,安全性權杖管理員就會建立適當的執行個體。public class SamlSecurityTokenManager : ClientCredentialsSecurityTokenManager { SamlClientCredentials samlClientCredentials; public SamlSecurityTokenManager ( SamlClientCredentials samlClientCredentials) : base(samlClientCredentials) { // Store the creating client credentials this.samlClientCredentials = samlClientCredentials; } public override SecurityTokenProvider CreateSecurityTokenProvider ( SecurityTokenRequirement tokenRequirement ) { // If token requirement matches SAML token return the // custom SAML token provider if (tokenRequirement.TokenType == SecurityTokenTypes.Saml || tokenRequirement.TokenType == "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1") { // Retrieve the SAML assertion and proof token from the // client credentials SamlAssertion assertion = this.samlClientCredentials.Assertion; SecurityToken prooftoken = this.samlClientCredentials.ProofToken; // If either the assertion of proof token is null... if (assertion == null || prooftoken == null) { // ...get the SecurityBindingElement and then the // specified algorithm suite SecurityBindingElement sbe = null; SecurityAlgorithmSuite sas = null; if ( tokenRequirement.TryGetProperty<SecurityBindingElement> ( "http://schemas.microsoft.com/ws/2006/05/servicemodel/securitytokenrequirement/SecurityBindingElement", out sbe)) { sas = sbe.DefaultAlgorithmSuite; } // If the token requirement is for a SymmetricKey based token.. if (tokenRequirement.KeyType == SecurityKeyType.SymmetricKey) { // Create a symmetric proof token prooftoken = SamlUtilities.CreateSymmetricProofToken ( tokenRequirement.KeySize ); // and a corresponding assertion based on the claims specified in the client credentials assertion = SamlUtilities.CreateSymmetricKeyBasedAssertion ( this.samlClientCredentials.Claims, new X509SecurityToken ( samlClientCredentials.ClientCertificate.Certificate ), new X509SecurityToken ( samlClientCredentials.ServiceCertificate.DefaultCertificate ), (BinarySecretSecurityToken)prooftoken, sas); } // otherwise... else { // Create an asymmetric proof token prooftoken = SamlUtilities.CreateAsymmetricProofToken(); // and a corresponding assertion based on the claims // specified in the client credentials assertion = SamlUtilities.CreateAsymmetricKeyBasedAssertion ( this.samlClientCredentials.Claims, prooftoken, sas ); } } // Create a SamlSecurityTokenProvider based on the assertion and proof token return new SamlSecurityTokenProvider(assertion, prooftoken); } // otherwise use base implementation else { return base.CreateSecurityTokenProvider(tokenRequirement); } }
撰寫自訂用戶端憑證。
用戶端認證類別會用來代表針對用戶端 Proxy 設定的認證,並且會建立用來取得權杖驗證器、權杖提供者以及權杖序列化程式的安全性權杖管理員。
public class SamlClientCredentials : ClientCredentials { ClaimSet claims; SamlAssertion assertion; SecurityToken proofToken; public SamlClientCredentials() : base() { // Set SupportInteractive to false to suppress Cardspace UI base.SupportInteractive = false; } protected SamlClientCredentials(SamlClientCredentials other) : base ( other ) { // Just do reference copy given sample nature this.assertion = other.assertion; this.claims = other.claims; this.proofToken = other.proofToken; } public SamlAssertion Assertion { get { return assertion; } set { assertion = value; } } public SecurityToken ProofToken { get { return proofToken; } set { proofToken = value; } } public ClaimSet Claims { get { return claims; } set { claims = value; } } protected override ClientCredentials CloneCore() { return new SamlClientCredentials(this); } public override SecurityTokenManager CreateSecurityTokenManager() { // return custom security token manager return new SamlSecurityTokenManager(this); } }
將用戶端設定成使用自訂用戶端認證。
此範例會刪除預設用戶端認證類別並提供新的用戶端認證類別,以便讓用戶端能夠使用自訂用戶端認證。
// Create new credentials class SamlClientCredentials samlCC = new SamlClientCredentials(); // Set the client certificate. This is the cert that will be used to sign the SAML token in the symmetric proof key case samlCC.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, "Alice"); // Set the service certificate. This is the cert that will be used to encrypt the proof key in the symmetric proof key case samlCC.ServiceCertificate.SetDefaultCertificate(StoreLocation.CurrentUser, StoreName.TrustedPeople, X509FindType.FindBySubjectName, "localhost"); // Create some claims to put in the SAML assertion IList<Claim> claims = new List<Claim>(); claims.Add(Claim.CreateNameClaim(samlCC.ClientCertificate.Certificate.Subject)); ClaimSet claimset = new DefaultClaimSet(claims); samlCC.Claims = claimset; // set new credentials client.ChannelFactory.Endpoint.Behaviors.Remove(typeof(ClientCredentials)); client.ChannelFactory.Endpoint.Behaviors.Add(samlCC);
服務端上會顯示與呼叫者關聯的宣告。 當您執行範例時,作業要求和回應會顯示在用戶端主控台視窗中。 在用戶端視窗中按下 ENTER 鍵,即可關閉用戶端。
設定批次檔
這個範例中所包含的 Setup.bat 批次檔可讓您使用相關的憑證設定伺服器,以執行需要伺服器憑證安全性的自我裝載應用程式。 這個批次檔必須經過修改才能跨電腦運作,或在非裝載的情況下運作。
下面提供批次檔的各區段簡要概觀,讓將批次檔得以修改為在適當的組態下執行。
建立伺服器憑證:
下列 Setup.bat 批次檔中的程式行會建立要使用的伺服器憑證。
%SERVER_NAME%
變數會指定伺服器名稱。 您可以變更這個變數來指定自己的伺服器名稱。 這個批次檔中的預設值為 localhost。憑證會儲存在 LocalMachine 存放區位置下的 My (Personal) 存放區中。
echo ************ echo Server cert setup starting echo %SERVER_NAME% echo ************ echo making server cert echo ************ makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=%SERVER_NAME% -sky exchange -pe
將伺服器憑證安裝至用戶端的受信任憑證存放區中。
Setup.bat 批次檔中的下列程式行會將伺服器憑證複製到用戶端受信任人的存放區。 這是必要步驟,因為用戶端系統並未隱含信任 Makecert.exe 產生的憑證。 如果您已經有一個以用戶端信任的根憑證 (例如 Microsoft 所發行的憑證) 為基礎的憑證,就不需要這個將伺服器憑證填入用戶端憑證的步驟。
certmgr.exe -add -r LocalMachine -s My -c -n %SERVER_NAME% -r LocalMachine -s TrustedPeople
建立簽發者憑證。
下列 Setup.bat 批次檔中的程式行會建立要使用的簽發者憑證。
%USER_NAME%
變數會指定簽發者名稱。 您可以變更這個變數來指定自己的簽發者名稱。 這個批次檔中的預設值為 Alice。憑證會儲存在位於 CurrentUser 存放區位置下的 My 存放區中。
echo ************ echo Server cert setup starting echo %SERVER_NAME% echo ************ echo making server cert echo ************ makecert.exe -sr CurrentUser -ss My -a sha1 -n CN=%USER_NAME% -sky exchange -pe
將簽發者憑證安裝至伺服器的受信任憑證存放區中。
Setup.bat 批次檔中的下列程式行會將伺服器憑證複製到用戶端受信任人的存放區。 這是必要步驟,因為用戶端系統並未隱含信任 Makecert.exe 產生的憑證。 如果您已經有一個以用戶端信任的根憑證 (例如 Microsoft 所發行的憑證) 為基礎的憑證,就不需要這個將簽發者憑證填入伺服器憑證的步驟。
certmgr.exe -add -r CurrentUser -s My -c -n %USER_NAME% -r LocalMachine -s TrustedPeople
若要設定和建置範例
若要建置解決方案,請遵循建置 Windows Communication Foundation 範例中的指示進行。
注意
如果您使用 Svcutil.exe 重新產生這個範例的組態,請務必修改用戶端組態中的端點名稱,以符合用戶端程式碼。
若要在同一部電腦上執行範例
在使用系統管理員權限執行的 Visual Studio 命令提示字元內部,執行範例安裝資料夾中的 Setup.bat。 這會安裝執行範例所需的所有憑證。
注意
Setup.bat 批次檔案是設計用來從 Visual Studio 命令提示字元執行。 在 Visual Studio 命令提示字元中設定的 PATH 環境變數,會指向包含 Setup.bat 指令碼所需可執行檔的目錄。
從 service\bin 啟動 Service.exe。
從 \client\bin 啟動 Client.exe。 用戶端活動會顯示在用戶端主控台應用程式上。
如果用戶端和服務無法通訊,請參閱 WCF 範例的疑難排解提示。
若要跨電腦執行範例
在服務電腦上為服務二進位碼檔案建立一個目錄。
將服務程式檔複製到服務電腦上的服務目錄。 同時,將 Setup.bat 和 Cleanup.bat 檔案複製到服務電腦中。
您伺服器憑證的主體名稱必須包含電腦的完整網域名稱。 此 Service.exe.config 檔必須更新以反映這個新憑證名稱。 您可以修改 Setup.bat 批次檔來建立伺服器憑證。 請注意,您必須在使用系統管理員權限開啟的 Visual Studio 視窗中,於開發人員命令提示字元內執行 Setup.bat 檔案。 您必須將
%SERVER_NAME%
變數設定為用來裝載服務之電腦的完整主機名稱。將伺服器憑證複製到用戶端的 CurrentUser-TrustedPeople 存放區中。 當伺服器憑證是由用戶端信任的簽發者發行時,就不需要這個步驟。
在服務電腦的 Service.exe.config 檔中變更基底位址的值,以指定完整電腦名稱而不要指定 localhost。
在服務電腦上,從命令提示字元執行 Service.exe。
將語言特定資料夾下 \client\bin\ 資料夾中的用戶端程式檔案複製到用戶端電腦。
在用戶端電腦上的 Client.exe.config 檔案中,變更端點的位址值以符合服務的新位址。
在用戶端電腦上,從命令提示字元視窗啟動
Client.exe
。如果用戶端和服務無法通訊,請參閱 WCF 範例的疑難排解提示。
若要在使用範例之後進行清除
- 當您完成執行範例後,請執行範例資料夾中的 Cleanup.bat。