如何在 SharePoint 2010 於宣告增強時間取得所有使用者宣告
英文原文已於 2011 年 3 月 30 日星期三發佈
在 SharePoint 2010 進行宣告增強時,一直有個障礙無法理解,就是叫用您的自訂宣告提供者以執行宣告增強時,使用者擁有哪些宣告。 舉例來說,您要為某人增強的宣告可能取決於使用者所擁有其他宣告的值,例如,假設某人屬於 “Domain Admins” 角色,請新增角色宣告 “Uber User”,否則新增宣告 “Standard User”。 在為此受挫一段很長的時間之後,我的好友 Israel V. 和 Matt Long 終於想出解決這個麻煩問題的解決方案 (找出這個解決方案全歸功於他們兩位,謝啦朋友!)。
除了在叫用您的宣告提供者進行增強時所提供的參數,嘗試取得這項資訊的其中一個基本問題在於,您沒有 HttpContext 的任何存取權限,無法查看宣告的集合。 Israel 和 Matt 正確地想通這個難題並找出替代方法,就是 OperationContext。 那是什麼呢?
這裡提供 OperationContext 的詳細概觀: https://msdn.microsoft.com/zh-tw/library/system.servicemodel.operationcontext(VS.90).aspx. 簡單地說,就是我們重視的是它可讓您存取內送郵件標頭和內容 (適用於 SAML 使用者) 和安全性內容 (適用於 Windows 使用者)。 那麼,它是如何幫助我們呢? 在叫用您的自訂宣告提供者進行增強時,您可以取得 SAML 使用者的這項內送郵件資訊,如下所示:
<s:Envelope xmlns:s="https://www.w3.org/2003/05/soap-envelope" xmlns:a="https://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">https://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action>
<a:MessageID>urn:uuid:85a0daaa-2288-4d0a-bda8-5fac05ea61cf</a:MessageID>
<a:ReplyTo>
<a:Address>https://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">https://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc</a:To>
</s:Header>
<s:Body>
<trust:RequestSecurityToken xmlns:trust="https://docs.oasis-open.org/ws-sx/ws-trust/200512">
<wsp:AppliesTo xmlns:wsp="https://schemas.xmlsoap.org/ws/2004/09/policy">
<a:EndpointReference>
<a:Address>https://fc1/</a:Address>
</a:EndpointReference>
</wsp:AppliesTo>
<trust:KeyType>https://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType>
<trust:OnBehalfOf>
<saml:Assertion MajorVersion="1" MinorVersion="1" AssertionID="_8f1d7b46-2b71-4263-859b-c3e358d7ea84" Issuer="https://myadfsserver/adfs/services/trust" IssueInstant="2011-03-26T18:51:54.671Z" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion">
<saml:Conditions NotBefore="2011-03-26T18:51:33.198Z" NotOnOrAfter="2011-03-26T19:51:33.198Z">
<saml:AudienceRestrictionCondition>
<saml:Audience>urn:sharepoint:fc1</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="https://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>testuser@vbtoys.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="role" AttributeNamespace="https://schemas.microsoft.com/ws/2008/06/identity/claims">
<saml:AttributeValue>pOregon Marketing</saml:AttributeValue>
<saml:AttributeValue>Domain Users</saml:AttributeValue>
<saml:AttributeValue>pSales</saml:AttributeValue>
<saml:AttributeValue>Portal People</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="windowsaccountname" AttributeNamespace="https://schemas.microsoft.com/ws/2008/06/identity/claims">
<saml:AttributeValue>testuser</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="primarysid" AttributeNamespace="https://schemas.microsoft.com/ws/2008/06/identity/claims">
<saml:AttributeValue>testuser@vbtoys.com</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<saml:AuthenticationStatement AuthenticationMethod="urn:federation:authentication:windows" AuthenticationInstant="2011-03-26T18:51:33.069Z">
<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="https://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="https://www.w3.org/2001/10/xml-exc-c14n#">
</ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="https://www.w3.org/2001/04/xmldsig-more#rsa-sha256">
</ds:SignatureMethod>
<ds:Reference URI="#_8f1d7b46-2b71-4263-859b-c3e358d7ea84">
<ds:Transforms>
<ds:Transform Algorithm="https://www.w3.org/2000/09/xmldsig#enveloped-signature">
</ds:Transform>
<ds:Transform Algorithm="https://www.w3.org/2001/10/xml-exc-c14n#">
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="https://www.w3.org/2001/04/xmlenc#sha256">
</ds:DigestMethod>
<ds:DigestValue>5Qvu+blahblah=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>VUSrynYjN8NOcUexqJOCblahblah</ds:SignatureValue>
<KeyInfo xmlns="https://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>MIIFlzCCBH+gAwIBAgIKHmblahblahblah</X509Certificate>
</X509Data>
</KeyInfo>
</ds:Signature>
</saml:Assertion>
</trust:OnBehalfOf>
<trust:RequestType>https://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
</trust:RequestSecurityToken>
</s:Body>
</s:Envelope>
<<不好意思,我現在才發現這段冗長的區塊變成醜陋的 XML,但我還是希望您看看>>
現在,由於我們的 XML 區塊已內嵌了宣告,因此,可以輕易地將它們解除包裝,也可以在增強期間使用它們。 下面是我撰寫來使用的一段快速範例:
using System.Xml;
…
private class IncomingClaim
{
public string claimType { get; set; }
public string claimValue { get; set; }
public IncomingClaim(string claimType, string claimValue)
{
this.claimType = claimType;
this.claimValue = claimValue;
}
}
protected override void FillClaimsForEntity(Uri context, SPClaim entity,
List<SPClaim> claims)
{
//get the request envelope with the claims information
string rqst =
System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.ToString();
//create a list to store the results
List<IncomingClaim> incomingClaims = new List<IncomingClaim>();
//create an Xml document for parsing the results and load the data
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(rqst);
//create a new namespace table so we can query
XmlNamespaceManager xNS = new XmlNamespaceManager(xDoc.NameTable);
//add the namespaces we'll be using
xNS.AddNamespace("s", "https://www.w3.org/2003/05/soap-envelope");
xNS.AddNamespace("trust", "https://docs.oasis-open.org/ws-sx/ws-trust/200512");
xNS.AddNamespace("saml", "urn:oasis:names:tc:SAML:1.0:assertion");
//get the list of claim nodes
XmlNodeList xList =
xDoc.SelectNodes("s:Envelope/s:Body/trust:RequestSecurityToken/trust:OnBehalfOf/saml:Assertion/saml:AttributeStatement/saml:Attribute", xNS);
//get the matches
if ((xList != null) && (xList.Count > 0))
{
//enumerate through the matches to get the claim list
foreach (XmlNode xNode in xList)
{
//get the claim type first
string currentClaimType =
xNode.Attributes["AttributeNamespace"].Value +
"/" + xNode.Attributes["AttributeName"].Value;
//each claim type will have one to many child nodes with the values
foreach (XmlNode claimNode in xNode.ChildNodes)
{
incomingClaims.Add(new IncomingClaim(currentClaimType,
claimNode.InnerText));
}
}
}
//now you can do whatever you want with the list of claims and finish
//your augmentation
…
}
大概就是這樣了。 程式碼夠簡單,而還囉嗦地加上註解,我相信若您看了,然後將 XML 貼到不錯的 XML 編輯器 (就像 Visual Studio .NET 隨附的編輯器),應該就會清楚它的運作方式。 到目前都不難,只是 XML 而已。
此外,有個有趣的副作用,您可以根據部落格訪者 Luis A. 指出的這個模式加以實作。 您可在 RequestMessage 中取得的宣告集合包括來自 IP-STS 的「所有項目」,其中包括沒有對應的宣告時,任何 SharePoint STS 將捨棄的宣告。 因此,如果您知道 SharePoint 將捨棄哪個宣告,而您無論如何都保留它們,可以自行再次向自訂宣告提供者增強它們。 只要提取 RequestMessage 以外的宣告值和類型,再將它們加回即可。
Windows 宣告使用者不會收到與他們的要求關聯的這個訊息,但是您可以使用 OperationContext 的 SecurityContext 為他們取得 WindowsIdentity。 如此一來,您就可以執行 WindowsIdentity 適用的所有作業,例如,取得使用者所屬群組的清單等。 下列是範例:
//do windows
WindowsIdentity wid =
System.ServiceModel.OperationContext.Current.ServiceSecurityContext.WindowsIdentity;
//get a reference to the groups to which the user belongs
IdentityReferenceCollection grps = wid.Groups;
//enumerate each group (which will be represented as a SID)
//NOTE that this includes ALL groups - builtin, local, domain, etc.
foreach (IdentityReference grp in grps)
{
//get the plain name of the group
SecurityIdentifier sid = new SecurityIdentifier(grp.Value);
NTAccount groupAccount = (NTAccount)sid.Translate(typeof(NTAccount));
string groupName = groupAccount.ToString();
//for domain groups remove the domain\ prefix from the group
//name in order to have feature parity with SAML users
if (groupName.Contains("\\"))
groupName = groupName.Substring(groupName.IndexOf("\\") + 1);
//add the claim
incomingClaims.Add(new
IncomingClaim("https://schemas.microsoft.com/ws/2008/06/identity/claims/role",
groupName));
}
您可以確認是否新增了 System.Security.Principal 的 using 陳述式,以正確地解析 WindowsIdentity、IdentityReference、NTAccount 等。 用其他方法的話,程式碼應該會又變得簡單。 我正在為使用者取得群組清單,並將他們拉進我的宣告自訂集合,做為 標準角色宣告。
再次感謝 Israel 和 Matt 提供這個「超級」有用的資訊。
這是翻譯後的部落格文章。英文原文請參閱 How to Get All User Claims at Claims Augmentation Time in SharePoint 2010