按照您的示例 Idp 代码创建 SamlResponse 时发出问题 - 在 LoginResponse 方法中

Issue creating SamlResponse when following your example Idp code - within the LoginResponse method

我使用 https://github.com/ITfoxtec/ITfoxtec.Identity.Saml2/blob/master/test/TestIdPCore/Controllers/AuthController.cs

中包含的代码创建了一个 IDP

当我尝试使用以下代码绑定 authNResponse 时出现错误:

var responsebinding = new Saml2PostBinding();
responsebinding.Bind(saml2AuthnResponse).XmlDocument.OuterXml;

这与 PostContent 方法中的代码相同,但我选择直接使用此代码,因为我只需要 SamlResponse。

错误是:

Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenWriteException: 'IDX13129: The SAML2:AttributeStatement must contain at least one SAML2:Attribute.'

具有以下简化的堆栈跟踪:

   at Microsoft.IdentityModel.Tokens.Saml2.Saml2Serializer.WriteAttributeStatement(XmlWriter writer, Saml2AttributeStatement statement)
   at Microsoft.IdentityModel.Tokens.Saml2.Saml2Serializer.WriteStatement(XmlWriter writer, Saml2Statement statement)
   at Microsoft.IdentityModel.Tokens.Saml2.Saml2Serializer.WriteAssertion(XmlWriter writer, Saml2Assertion assertion)
   at Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.WriteToken(XmlWriter writer, SecurityToken securityToken)
   at ITfoxtec.Identity.Saml2.Tokens.Saml2ResponseSecurityTokenHandler.WriteToken(SecurityToken token)
   at ITfoxtec.Identity.Saml2.Saml2AuthnResponse.ToXml()

我几乎完全使用了您的示例代码,请问其中是否存在问题,或者我遗漏了什么?

非常感谢

也许您遗漏了向令牌添加声明和创建令牌的部分?

saml2AuthnResponse.SessionIndex = sessionIndex;

var claimsIdentity = new ClaimsIdentity(claims);
saml2AuthnResponse.NameId = new Saml2NameIdentifier(claimsIdentity.Claims.Where(c => c.Type == ClaimTypes.NameIdentifier).Select(c => c.Value).Single(), NameIdentifierFormats.Persistent);
saml2AuthnResponse.ClaimsIdentity = claimsIdentity;

var token = saml2AuthnResponse.CreateSecurityToken(relyingParty.Issuer, subjectConfirmationLifetime: 5, issuedTokenLifetime: 60);

https://github.com/ITfoxtec/ITfoxtec.Identity.Saml2/blob/master/test/TestIdPCore/Controllers/AuthController.cs#L110

我发现您需要 ClaimTypes.NameIdentifier 和 ClaimTypes.Email 声明才能成功生成令牌.

这里有更深入的分析和两种可能solutions/workarounds。

情况:为只有一个声明的 ClaimsIdentity 创建 ITfoxtec.Identity.Saml2.Saml2AuthnResponse:nameidentifier。 相关代码片段(不完整,只有相关部分,但 ITFoxttec 示例具有完整代码)

var response = new Saml2AuthnResponse(config);
response.ClaimsIdentity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "someone@somewhere.com") });
response.NameId = new Saml2NameIdentifier(....etc...);
var token = response.CreateSecurityToken(appliesToAddress);
//so far all is well, but the problem has been sneakily introduced!
//which is why the next line will give the error: Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenWriteException: 'IDX13129: The SAML2:AttributeStatement must contain at least one SAML2:Attribute

return binding.Bind(response).ToActionResult();

解释:

ITfoxtec 代码:创建令牌时,nameidentifier 声明已从声明中删除。这是有道理的,因为它应该在 NameId 属性 中。其余声明在 SecurityTokenDescriptor 中设置为 Subject,该 SecurityTokenDescriptor 被提供给 Saml2SecurityTokenHandler,这是 Microsoft 代码。

var tokenDescriptor = new SecurityTokenDescriptor();
tokenDescriptor.Subject = new ClaimsIdentity(claims.Where(c => c.Type != ClaimTypes.NameIdentifier));

此令牌描述符中的声明最终作为生成的 Saml2SecurityToken 中的 AttributeStatement 中的属性(通过 Saml2SecurityTokenHandler.CreateToken(tokendescriptor) 调用)。

不幸的是,如果 nameidentifier 是您拥有的唯一声明,那么您最终会得到一个没有 Attributes 的 AttributeStatement。随后 运行 进入问题,当 binding.Bind(响应)在肠子深处做它的 XML 事情.. 除非你应该总是有一个 AttributeStatement,否则它在我看来就像 Microsoft.IdentityModel.Tokens.Saml 库中的错误/边缘情况。

有两种方案可以解决:

  1. 防止以无声明结束:只需向身份添加另一个声明,不必是电子邮件,可以是任何内容:

    response.ClaimsIdentity.AddClaim(新声明("x", "y"))

  2. 在 CreateSecurityToken 调用之后但在调用 Bind 之前,检查 AttributeStatement 是否为空,如果是,则将其删除。一个快速而肮脏的例子:

    var x = (Saml2AttributeStatement)token.Assertion.Statements.FirstOrDefault(a => a.GetType() == typeof(Saml2AttributeStatement)); 如果 (x?.Attributes.Count == 0) { token.Assertion.Statements.Remove(x); }

就个人而言,我更喜欢选项 1,因为它通常使用起来更安全且代码更少。另外,我确信总是可以 'something' 进一步归因于身份...