jeffwils / grails-spring-security-saml

Grails Spring Security SAML2.0 Plugin for Grails 3
8 stars 25 forks source link

Missing IdP metadata in CachingMetadataManager.basicMetadataCache #68

Closed jrAtAustin closed 3 years ago

jrAtAustin commented 3 years ago

I created a simple test app to test integrating Grails 3.3.11 with Azure Active Directory SAML SSO using the Spring Security SAML plugin. After configuring Azure and my Grails app I was redirected to the Azure login page where I logged in successfully. Azure sent my Grails app a successful assertion. When my Grails app try's to lookup the Azure IdP metadata in CachingMetadataManager.basicMetadataCache it finds the IdP alias in the metadata map but the value is null. The SP alias is in the same map but contains a non null value. It seems like the keys are set for the SP and IdP in CachingMetadataManager.basicMetadataCache but the value is not set for the IdP. Does anyone know if this is a configuration issue on my part or a bug in the plugin or spring security libraries?


Configuring Spring Security Core ...
... finished configuring Spring Security Core
Configuring Spring Security SAML ...
Importing beans from classpath:security/springSecuritySamlBeans.xml...
Registering metadata key: azureIdP and value: security/idpAzure.xml
Loading the service provider metadata from security/spMyApp.xml...
...finished configuring Spring Security SAML
Grails application running at https://localhost:8443 in environment: development
2020-08-13 08:50:41.803 ERROR --- [io-8443-exec-10] .a.c.c.C.[.[.[.[grailsDispatcherServlet] : Servlet.service() for servlet [grailsDispatcherServlet] in context with path [] threw exception [org.opensaml.saml2.metadata.provider.MetadataProviderException: Metadata for entity azureIdP and role {urn:oasis:names:tc:SAML:2.0:metadata}IDPSSODescriptor wasn't found] with root cause
org.opensaml.saml2.metadata.provider.MetadataProviderException: Metadata for entity azureIdP and role {urn:oasis:names:tc:SAML:2.0:metadata}IDPSSODescriptor wasn't found
    at org.springframework.security.saml.context.SAMLContextProviderImpl.populatePeerContext(SAMLContextProviderImpl.java:188)```
valentingoebel commented 3 years ago

"IDPSSODescriptor wasn't found" usually means the metadata for the IDP could not be found. It's usually a sign that you are not close to a properly working configuration because an insufficient configuration will usually cause the initialization of the plugin to fail with a visible error message but in your logs the application has started successfully and all that's left is going through the SAML login process.

It looks to me like the IDP Metadata is empty or the plugin is looking for the wrong entityID. Check your idpAzure.xml file and set the entityID for the IDP with grails.plugins.springsecurity.saml.metadata.defaultIdp. There should be an EntityDescriptor tag with the entityID and below it there is the IDPSSODescriptor tag that the plugin is looking for.

I did a manual test and if I change the defaultIdp to a wrong value I get the same error message. I think you just need to override defaultIdp and set it to whatever entityID is in your IDP metadata and then it will work.

jrAtAustin commented 3 years ago

@valentingoebel below is the configuration info, I removed the certificate info in the idpAzure.xml. Azure has a one year free signup if you would like to reproduce/test SSO integration with this Grails plugin.

grails {

    plugin {
        springsecurity {

            // 5.4. Defining Secured Annotations
            // https://grails-plugins.github.io/grails-spring-security-core/3.2.x/index.html#securedAnnotations
            securityConfigType = "Annotation"

            // 7.3. Registering Callback Closures
            // https://grails-plugins.github.io/grails-spring-security-core/3.2.x/index.html#registeringCallbackClosures
            useSecurityEventListener = true
            onInteractiveAuthenticationSuccessEvent = { e, appCtx ->
                println("Event: ${e}")
            }

            // 10. Authentication Providers
            // See https://grails-plugins.github.io/grails-spring-security-core/3.2.x/index.html#authentication
            // for a description of creating a custom authentication provider
            providerNames = [ 'samlAuthenticationProvider', 'daoAuthenticationProvider', 'anonymousAuthenticationProvider' ]

            // 11. Custom UserDetailsService
            // https://grails-plugins.github.io/grails-spring-security-core/3.2.x/index.html#userDetailsService
            // TODO: We many need to create a custom UserDetailsService to extend login options.

            // 16.1. Default Approach to Configuring Filter Chains
            // https://grails-plugins.github.io/grails-spring-security-core/3.2.x/index.html#default-approach-to-configuring-filter-chains
            //

            // Added based on the Spring Security SAML plugin
            // https://jeffwils.github.io/grails-spring-security-saml/
            // 1. General Settings
            active = true
            afterLoginUrl = 'https://localhost:8443/'
            afterLogoutUrl = 'http://localhost:8080/'
            responseSkew = 300
            metadata.url = '/saml/metadata'

            // 2. Auto created settings
            //saml.autoCreate.active = true
            //saml.autoCreate.assignAuthorities=true

            //
            saml.metadata.sp.file = 'security/spMyApp.xml'
            saml.metadata.providers = [ 'azureIdP': 'security/idpAzure.xml' ]
            saml.metadata.defaultIdp = 'azureIdP'
            saml.metadata.sp.defaults = [
                    local: true,
                    alias: 'test',
            ]

            // Added by the Spring Security Core plugin:
            userLookup.userDomainClassName = 'com.foo.gsscd.User'
            userLookup.authorityJoinClassName = 'com.foo.gsscd.UserRole'
            authority.className = 'com.foo.gsscd.Role'

            controllerAnnotations.staticRules = [
                    [pattern: '/', access: ['permitAll']],
                    [pattern: '/error', access: ['permitAll']],
                    [pattern: '/index', access: ['permitAll']],
                    [pattern: '/index.gsp', access: ['permitAll']],
                    [pattern: '/shutdown', access: ['permitAll']],
                    [pattern: '/assets/**', access: ['permitAll']],
                    [pattern: '/**/js/**', access: ['permitAll']],
                    [pattern: '/**/css/**', access: ['permitAll']],
                    [pattern: '/**/images/**', access: ['permitAll']],
                    [pattern: '/**/favicon.ico', access: ['permitAll']]
            ]

            filterChain.chainMap = [
                    [pattern: '/assets/**', filters: 'none'],
                    [pattern: '/**/js/**', filters: 'none'],
                    [pattern: '/**/css/**', filters: 'none'],
                    [pattern: '/**/images/**', filters: 'none'],
                    [pattern: '/**/favicon.ico', filters: 'none'],
                    [pattern: '/**', filters: 'JOINED_FILTERS']
            ]
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<EntityDescriptor ID="_229a2785-bd3f-48c3-a742-7c328c1a384b"
                  entityID="https://sts.windows.net/aXaXXXXe-b0XX-XXab-bXXX-XeXfXeXXfXcX/"
                  xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
    <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="#_229a2785-bd3f-48c3-a742-7c328c1a384b">
                <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/2001/04/xmlenc#sha256"/>
                <DigestValue>bUuQs4yXgu3sGz3FxtSdX32LAzCVsZxIGle2LiRYU2A=</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>
            ...
        </SignatureValue>
        <KeyInfo>
            <X509Data>
                <X509Certificate>
                    ...
                </X509Certificate>
            </X509Data>
        </KeyInfo>
    </Signature>
    <RoleDescriptor xsi:type="fed:SecurityTokenServiceType"
                    protocolSupportEnumeration="http://docs.oasis-open.org/wsfed/federation/200706"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xmlns:fed="http://docs.oasis-open.org/wsfed/federation/200706">
        <KeyDescriptor use="signing">
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <X509Data>
                    <X509Certificate>
                        ...
                    </X509Certificate>
                </X509Data>
            </KeyInfo>
        </KeyDescriptor>
        <fed:ClaimTypesOffered>
            <auth:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>Name</auth:DisplayName>
                <auth:Description>The mutable display name of the user.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>Subject</auth:DisplayName>
                <auth:Description>An immutable, globally unique, non-reusable identifier of the user that is unique to
                    the application for which a token is issued.
                </auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>Given Name</auth:DisplayName>
                <auth:Description>First name of the user.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>Surname</auth:DisplayName>
                <auth:Description>Last name of the user.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/identity/claims/displayname"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>Display Name</auth:DisplayName>
                <auth:Description>Display name of the user.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/identity/claims/nickname"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>Nick Name</auth:DisplayName>
                <auth:Description>Nick name of the user.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>Authentication Instant</auth:DisplayName>
                <auth:Description>The time (UTC) when the user is authenticated to Windows Azure Active Directory.
                </auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>Authentication Method</auth:DisplayName>
                <auth:Description>The method that Windows Azure Active Directory uses to authenticate users.
                </auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/identity/claims/objectidentifier"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>ObjectIdentifier</auth:DisplayName>
                <auth:Description>Primary identifier for the user in the directory. Immutable, globally unique,
                    non-reusable.
                </auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/identity/claims/tenantid"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>TenantId</auth:DisplayName>
                <auth:Description>Identifier for the user's tenant.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/identity/claims/identityprovider"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>IdentityProvider</auth:DisplayName>
                <auth:Description>Identity provider for the user.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>Email</auth:DisplayName>
                <auth:Description>Email address of the user.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/ws/2008/06/identity/claims/groups"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>Groups</auth:DisplayName>
                <auth:Description>Groups of the user.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/identity/claims/accesstoken"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>External Access Token</auth:DisplayName>
                <auth:Description>Access token issued by external identity provider.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/ws/2008/06/identity/claims/expiration"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>External Access Token Expiration</auth:DisplayName>
                <auth:Description>UTC expiration time of access token issued by external identity provider.
                </auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/identity/claims/openid2_id"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>External OpenID 2.0 Identifier</auth:DisplayName>
                <auth:Description>OpenID 2.0 identifier issued by external identity provider.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/claims/groups.link"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>GroupsOverageClaim</auth:DisplayName>
                <auth:Description>Issued when number of user's group claims exceeds return limit.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>Role Claim</auth:DisplayName>
                <auth:Description>Roles that the user or Service Principal is attached to</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType Uri="http://schemas.microsoft.com/ws/2008/06/identity/claims/wids"
                            xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
                <auth:DisplayName>RoleTemplate Id Claim</auth:DisplayName>
                <auth:Description>Role template id of the Built-in Directory Roles that the user is a member of
                </auth:Description>
            </auth:ClaimType>
        </fed:ClaimTypesOffered>
        <fed:SecurityTokenServiceEndpoint>
            <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
                <wsa:Address>https://login.microsoftonline.com/aXaXXXXe-b0XX-XXab-bXXX-XeXfXeXXfXcX/wsfed</wsa:Address>
            </wsa:EndpointReference>
        </fed:SecurityTokenServiceEndpoint>
        <fed:PassiveRequestorEndpoint>
            <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
                <wsa:Address>https://login.microsoftonline.com/aXaXXXXe-b0XX-XXab-bXXX-XeXfXeXXfXcX/wsfed</wsa:Address>
            </wsa:EndpointReference>
        </fed:PassiveRequestorEndpoint>
    </RoleDescriptor>
    <RoleDescriptor xsi:type="fed:ApplicationServiceType"
                    protocolSupportEnumeration="http://docs.oasis-open.org/wsfed/federation/200706"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xmlns:fed="http://docs.oasis-open.org/wsfed/federation/200706">
        <KeyDescriptor use="signing">
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <X509Data>
                    <X509Certificate>
                        ...
                </X509Data>
            </KeyInfo>
        </KeyDescriptor>
        <fed:TargetScopes>
            <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
                <wsa:Address>https://sts.windows.net/aXaXXXXe-b0XX-XXab-bXXX-XeXfXeXXfXcX/</wsa:Address>
            </wsa:EndpointReference>
        </fed:TargetScopes>
        <fed:ApplicationServiceEndpoint>
            <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
                <wsa:Address>https://login.microsoftonline.com/aXaXXXXe-b0XX-XXab-bXXX-XeXfXeXXfXcX/wsfed</wsa:Address>
            </wsa:EndpointReference>
        </fed:ApplicationServiceEndpoint>
        <fed:PassiveRequestorEndpoint>
            <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
                <wsa:Address>https://login.microsoftonline.com/aXaXXXXe-b0XX-XXab-bXXX-XeXfXeXXfXcX/wsfed</wsa:Address>
            </wsa:EndpointReference>
        </fed:PassiveRequestorEndpoint>
    </RoleDescriptor>
    <IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <KeyDescriptor use="signing">
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <X509Data>
                    <X509Certificate>
                        ...
                    </X509Certificate>
                </X509Data>
            </KeyInfo>
        </KeyDescriptor>
        <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
                             Location="https://login.microsoftonline.com/aXaXXXXe-b0XX-XXab-bXXX-XeXfXeXXfXcX/saml2"/>
        <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
                             Location="https://login.microsoftonline.com/aXaXXXXe-b0XX-XXab-bXXX-XeXfXeXXfXcX/saml2"/>
        <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
                             Location="https://login.microsoftonline.com/aXaXXXXe-b0XX-XXab-bXXX-XeXfXeXXfXcX/saml2"/>
    </IDPSSODescriptor>
</EntityDescriptor>
jrAtAustin commented 3 years ago

@valentingoebel I changed saml.metadata.defaultIdp = 'azureIdP' to the entityID in my IDP metadata and that got me past this issue. The examples I have seen show the providers key (saml.metadata.providers = [ 'azureIdP': 'security/idpAzure.xml' ]) as the defaultIdp value.