【发布时间】:2020-11-27 13:57:24
【问题描述】:
我正在使用 TestWebAppCore 项目来测试 ASP.NET Core Web 应用程序的 SAML 集成,我认为我可以正常工作,但与用户会话相关联的声明不是由SAML 响应中的 IdP,我不确定映射返回的声明需要什么额外配置。
点击登录后,我被重定向到我的 IdP,在我登录后,我的 IdP 以以下 SAML 响应进行响应(部分已删除以保持问题简短):
...
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
NameQualifier="samlsso"
SPNameQualifier="https://my.identity.provider"
>edde16f1-9fee-4e44-9c4d-3810a3a6f73a</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData InResponseTo="_01b18bfb2348b2d1dcc1df73bcdb88dc"
NotOnOrAfter="2020-11-27T13:20:41Z"
Recipient="https://my.identity.provider/samlsso"
/>
</saml:SubjectConfirmation>
</saml:Subject>
...
<saml:AttributeStatement>
<saml:Attribute Name="MiddleName">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="xs:string"
>Ben</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="email">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="xs:string"
>peter.parker@dailybugle.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="GivenName">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="xs:string"
>Peter</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="FamilyName">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="xs:string"
>Parker</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
...
登录后我被重定向到主页,我看到“嗨,edde16f1-9fee-4e44-9c4d-3810a3a6f73a”,所以我点击“SAML 声明”,页面显示:
The users Claims (Iteration on User.Claims)
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
Value: edde16f1-9fee-4e44-9c4d-3810a3a6f73a
http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod
Value: urn:oasis:names:tc:SAML:2.0:ac:classes:Password
http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant
Value: 2020-11-27T13:10:37.504Z
http://schemas.itfoxtec.com/ws/2014/02/identity/claims/saml2nameid
Value: edde16f1-9fee-4e44-9c4d-3810a3a6f73a
http://schemas.itfoxtec.com/ws/2014/02/identity/claims/saml2nameidformat
Value: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
此列表不包括我想从 IdP 获得的 SAML 响应中使用的声明,因此我尝试通过稍微修改代码将声明添加到 ClaimsTransform 类中:
private static ClaimsPrincipal CreateClaimsPrincipal(ClaimsPrincipal incomingPrincipal)
{
var claims = new List<Claim>();
// All claims
////claims.AddRange(incomingPrincipal.Claims);
var givenName = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname";
claims.Add(new Claim(givenName, GetClaimValue(incomingPrincipal, givenName)));
// Or custom claims
//claims.AddRange(GetSaml2LogoutClaims(incomingPrincipal));
//claims.Add(new Claim(ClaimTypes.NameIdentifier, GetClaimValue(incomingPrincipal, ClaimTypes.NameIdentifier)));
return new ClaimsPrincipal(new ClaimsIdentity(claims, incomingPrincipal.Identity.AuthenticationType, ClaimTypes.NameIdentifier, ClaimTypes.Role)
{
BootstrapContext = ((ClaimsIdentity)incomingPrincipal.Identity).BootstrapContext
});
}
private static Claim GetClaim(ClaimsPrincipal principal, string claimType)
{
return ((ClaimsIdentity)principal.Identity).Claims.FirstOrDefault(c => c.Type == claimType);
}
private static string GetClaimValue(ClaimsPrincipal principal, string claimType)
{
var claim = GetClaim(principal, claimType);
return claim?.Value;
}
但是对代码的这种更改会导致 Claim 类出错:
Value cannot be null.
无论我使用GivenName 或声明地址添加声明,值似乎都为空。我是否缺少其他配置,可以让我使用“AttributeStatement”部分中的声明?
更新
进一步阅读代码让我感到困惑,在AssertionConsumerService 路由中,测试代码正在创建一个全新的SAMLResponse?新响应不包含 IdP 响应中的任何属性,这可以解释为什么没有声明。
如果代码应该是这样工作的,是否可以将 IdP 响应中的声明包含在 ITfoxtec.identity.saml2 生成的新响应中?
<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
Destination="https://my.test.website/Auth/AssertionConsumerService"
ID="_9099f6ccf0b9ac7703d6b320df6357a0"
InResponseTo="_08e3a2b0-4ac8-4673-80bc-31460812738f"
IssueInstant="2020-11-28T01:13:15.726Z"
Version="2.0"
>
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity"
>https://my.test.provider</saml2:Issuer>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<Reference URI="#_9099f6ccf0b9ac7703d6b320df6357a0">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>IRYj+9sUoEsO5rEgEj+laMogGk0=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>...removed...</SignatureValue>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>...removed...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</Signature>
<saml2p:Status>
<saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
</saml2p:Status>
<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_fbe41ccdaa799fe0c3038d5d07edc18e"
IssueInstant="2020-11-28T01:13:15.726Z"
Version="2.0"
>
<saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://my.test.provider</saml2:Issuer>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<Reference URI="#_fbe41ccdaa799fe0c3038d5d07edc18e">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>FbSefxSL8LDE1pJdhScHaNijdEY=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>...removed...</SignatureValue>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>...removed...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</Signature>
<saml2:Subject>
<saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">edde16f1-9fee-4e44-9c4d-3810a3a6f73a</saml2:NameID>
<saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml2:SubjectConfirmationData InResponseTo="_08e3a2b0-4ac8-4673-80bc-31460812738f"
NotOnOrAfter="2020-11-28T01:18:15.726Z"
Recipient="https://my.test.website/Auth/AssertionConsumerService"
/>
</saml2:SubjectConfirmation>
</saml2:Subject>
<saml2:Conditions NotBefore="2020-11-28T01:13:15.726Z"
NotOnOrAfter="2020-11-28T01:18:15.726Z"
>
<saml2:AudienceRestriction>
<saml2:Audience>https://my.test.website</saml2:Audience>
</saml2:AudienceRestriction>
</saml2:Conditions>
<saml2:AuthnStatement AuthnInstant="2020-11-28T01:13:15.647Z">
<saml2:AuthnContext>
<saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
</saml2:AuthnContext>
</saml2:AuthnStatement>
</saml2:Assertion>
</saml2p:Response>
AssertionConsumerService 的代码:
[Route("AssertionConsumerService")]
public async Task<IActionResult> AssertionConsumerService()
{
var binding = new Saml2PostBinding();
var saml2AuthnResponse = new Saml2AuthnResponse(config);
binding.ReadSamlResponse(Request.ToGenericHttpRequest(), saml2AuthnResponse);
if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
{
throw new AuthenticationException($"SAML Response status: {saml2AuthnResponse.Status}");
}
binding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnResponse);
await saml2AuthnResponse.CreateSession(HttpContext, claimsTransform: (claimsPrincipal) => ClaimsTransform.Transform(claimsPrincipal));
var relayStateQuery = binding.GetRelayStateQuery();
var returnUrl = relayStateQuery.ContainsKey(relayStateReturnUrl) ? relayStateQuery[relayStateReturnUrl] : Url.Content("~/");
return Redirect(returnUrl);
}
【问题讨论】:
-
如果在声明转换上放置断点,
incomingPrincipal是否包含转换之前的声明?此外,<saml:Attribute Name="GivenName">未在您的入站声明值中使用命名空间发送,因此它与您在提供的代码中检查的命名空间字符串不匹配。 -
嗨@AdamG,传入的主体不包含转换之前的声明,因为我发现代码在没有来自 IdP 的数据的情况下生成它自己的全新 SAML 响应。如果您知道如何修改代码以包含 Idp 的响应,我将不胜感激。
-
我建议尝试从 stubidp.sustainsys.com 的 Sustainsys 存根 IdP 发起 IdP 登录 - 选择一个假用户或提供您自己的 NameId、会话索引和属性,然后输入您的 ACS URL 端点。我能够成功地将声明提取到
TestWebAppCore项目中而无需任何更改,因此这可能是从已知良好状态向后工作的问题。 -
嗯,我能够使用 stubidp 传递声明,但后来我注意到来自 stubidp 的响应中的所有 XML 元素都是
<saml2:xxxx>,而我自己的 IdP 将 XML 传递回为<saml:xxx>,我想知道这是否会引起问题。我不会这样认为,因为两个响应都是 SAML 2.0 版,但它必须读取<saml:NameId>元素才能从原始响应中获取 NameId。
标签: c# asp.net-core saml-2.0 itfoxtec-identity-saml2