[英]Parse and verify a WS Trust XML Token
我有一個用c#/。NET編寫的Web服務,該服務將未經身份驗證的用戶重定向到WS Federation身份提供程序,然后使用具有該用戶角色的SAML令牌將其重定向回到我的Web服務。 根據被動WS聯合身份驗證規范-http://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.html#_Toc223175008
有了這個,我得到一個請求,該請求的結果設置為令牌。 在我的代碼中,我有一個字符串 wresult,它是xml文檔的字符串。 我所知道的是領域,身份提供者的指紋,wctx(如果已發送)。
安全令牌是此處描述的標准WS-Trust令牌: http : //specs.xmlsoap.org/ws/2005/02/trust/WS-Trust.pdf
我想從該字符串 (即XML文檔/安全令牌)中獲取該用戶的SecurityToken和最終的IPrincipal。
字符串的一個示例是(混淆了一些內容)。
<?xml version="1.0"?>
<t:RequestSecurityTokenResponse xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<t:Lifetime>
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2018-09-14T13:40:25.164Z</wsu:Created>
<wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2018-09-14T14:40:25.164Z</wsu:Expires>
</t:Lifetime>
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>https://localhost:44366/</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<t:RequestedSecurityToken>
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" MajorVersion="1" MinorVersion="1" AssertionID="_e1580903-02ac-453d-a157-ae27c8614cc9" Issuer="http://adfs.ORGANISATION.com/adfs/services/trust" IssueInstant="2018-09-14T13:40:25.164Z">
<saml:Conditions NotBefore="2018-09-14T13:40:25.164Z" NotOnOrAfter="2018-09-14T14:40:25.164Z">
<saml:AudienceRestrictionCondition>
<saml:Audience>https://localhost:44366/</saml:Audience>
</saml:AudienceRestrictionCondition>
</saml:Conditions>
<saml:AttributeStatement>
<saml:Subject>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Attribute AttributeName="emailaddress" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>person@stuff.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="givenname" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>Jeff</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="surname" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>Mandelson</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="windowsaccountname" AttributeNamespace="http://schemas.microsoft.com/ws/2008/06/identity/claims">
<saml:AttributeValue>jeff.mandelson</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="role" AttributeNamespace="http://schemas.microsoft.com/ws/2008/06/identity/claims">
<saml:AttributeValue>Stuff\Domain Users</saml:AttributeValue>
<saml:AttributeValue>Stuff\DevTeam</saml:AttributeValue>
<saml:AttributeValue>Stuff\RDS-MSSQLDEV-RW</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="upn" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>stuff@local.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="name" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>Jeff Mandelson</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" AuthenticationInstant="2018-09-14T11:59:16.147Z">
<saml:Subject>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
</saml:Subject>
</saml:AuthenticationStatement>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#_e1580903-02ac-453d-a157-ae27c8614cc9">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>a_digest_value_removed</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>signature</ds:SignatureValue>
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>certificate</X509Certificate>
</X509Data>
</KeyInfo>
</ds:Signature>
</saml:Assertion>
</t:RequestedSecurityToken>
<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
</t:RequestSecurityTokenResponse>
我嘗試使用WSFederationAuthenticationModule之類的內置方法,但是,除非您使用System.Web.Request,否則這似乎有問題。 內置的.NET / C#函數將是更好的選擇!
解決方案是將令牌視為常規XMLDsig簽名的XML-聲明節點已簽名,並且簽名的引用指向該令牌。 代碼很簡單,但是有趣的是,必須繼承SignedXml
類,以使簽名驗證器遵循AssertionID
屬性(默認約定是,已簽名節點的id屬性僅稱為ID
,而默認驗證器將SignedXml
。找不到ID屬性不同的節點)。
public class SamlSignedXml : SignedXml
{
public SamlSignedXml(XmlElement e) : base(e) { }
public override XmlElement GetIdElement(XmlDocument document, string idValue)
{
XmlNamespaceManager mgr = new XmlNamespaceManager(document.NameTable);
mgr.AddNamespace("trust", "http://docs.oasis-open.org/ws-sx/ws-trust/200512");
mgr.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
mgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:1.0:assertion");
XmlElement assertionNode =
(XmlElement)document.SelectSingleNode("//trust:RequestSecurityTokenResponseCollection/trust:RequestSecurityTokenResponse/"+
"trust:RequestedSecurityToken/saml:Assertion", mgr);
if (assertionNode.Attributes["AssertionID"] != null &&
string.Equals(assertionNode.Attributes["AssertionID"].Value, idValue, StringComparison.InvariantCultureIgnoreCase)
)
return assertionNode;
return null;
}
}
請注意,XPath假設令牌的根中具有RequestSecurityTokenResponseCollection
,請確保您的令牌遵循此約定(如果是單個令牌,則可能會丟失集合節點,並且令牌的根可能只是RequestSecurityTokenResponse
,請相應地更新代碼)。
然后輸入驗證碼
// token is the string representation of the SAML1 token
// expectedCertThumb is the expected certificate's thumbprint
protected bool ValidateToken( string token, string expectedCertThumb, out string userName )
{
userName = string.Empty;
if (string.IsNullOrEmpty(token)) return false;
var xd = new XmlDocument();
xd.PreserveWhitespace = true;
xd.LoadXml(token);
XmlNamespaceManager mgr = new XmlNamespaceManager(xd.NameTable);
mgr.AddNamespace("trust", "http://docs.oasis-open.org/ws-sx/ws-trust/200512");
mgr.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
mgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:1.0:assertion");
// assertion
XmlElement assertionNode = (XmlElement)xd.SelectSingleNode("//trust:RequestSecurityTokenResponseCollection/trust:RequestSecurityTokenResponse/trust:RequestedSecurityToken/saml:Assertion", mgr);
// signature
XmlElement signatureNode = (XmlElement)xd.GetElementsByTagName("Signature")[0];
var signedXml = new SamlSignedXml( assertionNode );
signedXml.LoadXml(signatureNode);
X509Certificate2 certificate = null;
foreach (KeyInfoClause clause in signedXml.KeyInfo)
{
if (clause is KeyInfoX509Data)
{
if (((KeyInfoX509Data)clause).Certificates.Count > 0)
{
certificate =
(X509Certificate2)((KeyInfoX509Data)clause).Certificates[0];
}
}
}
// cert node missing
if (certificate == null) return false;
// check the signature and return the result.
var signatureValidationResult = signedXml.CheckSignature(certificate, true);
if (signatureValidationResult == false) return false;
// validate cert thumb
if ( !string.IsNullOrEmpty( expectedCertThumb ) )
{
if ( !string.Equals( expectedCertThumb, certificate.Thumbprint ) )
return false;
}
// retrieve username
// expires =
var expNode = xd.SelectSingleNode("//trust:RequestSecurityTokenResponseCollection/trust:RequestSecurityTokenResponse/trust:Lifetime/wsu:Expires", mgr );
DateTime expireDate;
if (!DateTime.TryParse(expNode.InnerText, out expireDate)) return false; // wrong date
if (DateTime.UtcNow > expireDate) return false; // token too old
// claims
var claimNodes =
xd.SelectNodes("//trust:RequestSecurityTokenResponseCollection/trust:RequestSecurityTokenResponse/trust:RequestedSecurityToken/"+
"saml:Assertion/saml:AttributeStatement/saml:Attribute", mgr );
foreach ( XmlNode claimNode in claimNodes )
{
if ( claimNode.Attributes["AttributeName"] != null &&
claimNode.Attributes["AttributeNamespace"] != null &&
string.Equals( claimNode.Attributes["AttributeName"].Value, "name", StringComparison.InvariantCultureIgnoreCase ) &&
string.Equals( claimNode.Attributes["AttributeNamespace"].Value, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims", StringComparison.InvariantCultureIgnoreCase ) &&
claimNode.ChildNodes.Count == 1
)
{
userName = claimNode.ChildNodes[0].InnerText;
return true;
}
}
return false;
}
進行一些細微的調整,您應該可以做自己想做的事情。
BTW。 大部分答案是從我的博客條目中復制的
https://www.wiktorzychla.com/2018/09/parsing-saml-11-ws-federation-tokens.html
該文檔記錄了我們在其中一個應用程序內部使用的方法。 我計划輸入一段時間,而您的問題只是我需要的一個觸發條件。
另一種方法是使用IdentityModel SamlTokenVerifier和Parser:
public static bool AuthenticateXmlToken(String wresult)
{
String pstrXML = wresult;
// write it down
File.WriteAllText("C:\\Users\\USER\\Downloads\\asdf4.xml", wresult);
// extract the SAML Assertion
XmlReader reader = XmlReader.Create(new StringReader(pstrXML));
reader.ReadToFollowing("Assertion", "urn:oasis:names:tc:SAML:1.0:assertion");
// saml requirements
SamlSecurityTokenRequirement pRequirements = new SamlSecurityTokenRequirement();
pRequirements.CertificateValidator = new CertificateValidator();
SecurityTokenHandlerConfiguration pConfig = new SecurityTokenHandlerConfiguration();
pConfig.AudienceRestriction = new AudienceRestriction(AudienceUriMode.Never);
pConfig.IssuerNameRegistry = new IssuerNames();
//pRequirements.ValidateAudienceRestriction()
SamlSecurityTokenHandler pHandler = new SamlSecurityTokenHandler(pRequirements);
pHandler.Configuration = pConfig;
SecurityTokenHandlerCollection tokenHandlerCollection = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection();
SamlSecurityToken token = (SamlSecurityToken)pHandler.ReadToken(reader.ReadSubtree());
ReadOnlyCollection<ClaimsIdentity> pClaims = pHandler.ValidateToken(token);
return pClaims.Count > 0;
}
public class IssuerNames : IssuerNameRegistry
{
public override string GetIssuerName(SecurityToken securityToken)
{
return "Issuer";
throw new NotImplementedException();
}
}
public class CertificateValidator : X509CertificateValidator
{
public override void Validate(X509Certificate2 certificate)
{
if (certificate == null)
{
throw new Exception("certificate is null");
}
if (certificate.Thumbprint.ToLower() != "mythumprint")
{
throw new Exception("X509 certficate is signed with the wrong public key!");
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.