Сопоставление утверждений SAML с использованием ITfoxtec.Identity.Saml2

#c# #asp.net-core #saml-2.0 #itfoxtec-identity-saml2

#c# #asp.net-core #saml-2.0 #itfoxtec-identity-saml2

Вопрос:

Я использую проект TestWebAppCore для тестирования интеграции SAML для ASP.NET Core web app, и я думал, что у меня получилось, но утверждения, связанные с сеансом пользователя, не являются утверждениями, возвращаемыми IdP в ответе SAML, и я не уверен, какая дополнительная конфигурация требуется для сопоставления возвращенныхутверждения.

После нажатия кнопки входа в систему я перенаправляюсь на свой 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
 

В этот список не входят утверждения, которые я хочу использовать из ответа SAML, который я получаю от IdP, поэтому я попытался добавить утверждения в 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;
}
 

But this change to the code results in an error from the Claim class:

 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);
}
 

Комментарии:

1. Если вы поместите точку останова в преобразование утверждений, incomingPrincipal содержит ли оно утверждения до преобразования? Кроме того, <saml:Attribute Name="GivenName"> не отправляется с пространством имен в вашем входящем значении утверждения, поэтому оно не будет соответствовать строке с пространством имен, которую вы проверяете в предоставленном коде.

2. Привет @AdamG, входящий участник не содержит утверждений до преобразования, потому что я обнаружил, что код генерирует свой собственный новый ответ SAML без данных из IdP. Если вы знаете, как изменить код, чтобы включить ответ от Idp, я был бы очень признателен.

3. Я бы предложил попробовать инициированный IdP вход в систему из заглушки IdP Sustainsys по адресу stubidp.sustainsys.com — выберите одного из поддельных пользователей или укажите свой собственный идентификатор имени, индекс сеанса и атрибуты, а также введите конечную точку URL-адреса ACS. Я смог успешно внедрить утверждения в TestWebAppCore проект без каких-либо изменений, так что это может быть вопросом обратной работы из этого заведомо исправного состояния.

4. Хм, я смог передать утверждения с помощью stubidp, но затем я заметил, что все XML-элементы в ответе от stubidp были <saml2:xxxx> , в то время как мой собственный IdP передает XML обратно как <saml:xxx> , интересно, вызывает ли это проблемы. Я бы так не подумал, поскольку оба ответа являются SAML версии 2.0, но <saml:NameId> для получения идентификатора NameID из исходного ответа пришлось бы прочитать элемент.

Ответ №1:

Я не пытался читать атрибуты, которые выглядят так, как вы описываете. Но я думаю, что библиотека должна иметь возможность считывать атрибуты.

Обычно атрибуты выглядят так:

 <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname">
   <AttributeValue>Peter</AttributeValue>
</Attribute>
 

С полным пространством имен. Однако также должна быть возможность прочитать утверждение с именем, подобным givenname .

Пакет ITfoxtec Identity SAML поддерживает только SAML 2.0. В SAML 2.0 NameID имеет пространство имен SAML 2.0:

 <NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">edde16f1-9fee-4e44-9c4d-3810a3a6f73a</NameID>
 

Возможно, в XML есть другие проблемы, которые не делают SAML совместимым с 2.0.