spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.6k stars 5.81k forks source link

404 Errors for SP Metadata and IDP Initiated Login #14514

Open siddharth-78 opened 5 months ago

siddharth-78 commented 5 months ago

I'm currently upgrading an existing application from spring-security-saml2-core 1.0.10.RELEASE (which has reached end-of-life) to spring-security-saml2-service-provider 5.6.9. As these versions have significant differences, I started by creating a sample Spring application based on the example provided at this GitHub repository. This test application worked correctly, allowing both IDP-initiated logins and the successful download of SP metadata using the URL http://{host-name}/saml2/service-provider-metadata/{registration-id}.

However, after integrating similar changes into my actual project, I'm encountering issues. Here's a brief overview of what I did:

Code Addition: I wrote a class (similar to the one in the sample application) in my codebase.


// Necessary Imports

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private ResourceLoader resourceLoader = new DefaultResourceLoader();

    @Autowired(required = false)
    private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        LOG.info("In the configure method");
        if (isSAMLEnabled()) {
            LOG.info("Inside IF");
            http
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .saml2Login(); 

            Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository);
            Saml2MetadataFilter filter = new Saml2MetadataFilter(relyingPartyRegistrationResolver, new OpenSamlMetadataResolver());
            http.addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class);
        }

        LOG.info("Configure method done.");
    }

    @Bean
    public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {

        LOG.info("Inside the bean method");
        if (isSAMLEnabled()) {
            return createRelyingPartyRegistrationRepository();
        } else {
            return null;
        }
    }

    private RelyingPartyRegistrationRepository createRelyingPartyRegistrationRepository() {

        LOG.info("Inside Repo method");

        String entityId = //Appropriate Value
        String ssoUrl = //Appropriate Value
        String certificatePath = //Appropriate Value

        try {
            Resource resource = resourceLoader.getResource(certificatePath);
            InputStream inputStream = resource.getInputStream();
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate certificate = (X509Certificate) cf.generateCertificate(inputStream);
            Saml2X509Credential credential = Saml2X509Credential.verification(certificate);

            RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration
                    .withRegistrationId("okta-idp")
                    .assertingPartyDetails(party -> party
                            .entityId(entityId)
                            .singleSignOnServiceLocation(ssoUrl)
                            .wantAuthnRequestsSigned(false)
                            .verificationX509Credentials(c -> c.add(credential))
                    ).build();

            LOG.info("repo method successful");
            return new InMemoryRelyingPartyRegistrationRepository(relyingPartyRegistration);

        } catch (Exception e) {
            LOG.info("Failed in repo method");
            throw new RuntimeException("Error configuring SAML ", e);
        }
    }

    private boolean isSAMLEnabled() {
        LOG.info("Checking if SAML is enabled...");
        // Logic to check if SAML is enabled, this part works fine.
    }
}

Observation in Logs: Upon restarting my server, the logs indicate that the relyingParty instance is being set up correctly.

2024-01-29 11:36:08,200 INFO Inside the bean method
2024-01-29 11:36:08,200 INFO Checking if SAML is enabled...
2024-01-29 11:36:08,200 INFO Inside Repo method
2024-01-29 11:36:08,212 INFO repo method successful
2024-01-29 11:36:08,324 INFO In the configure method
2024-01-29 11:36:08,324 INFO Checking if SAML is enabled...
2024-01-29 11:36:08,324 INFO Inside IF
2024-01-29 11:36:08,980 INFO Configure method done.

Issue Encountered: Despite the logs suggesting correct setup, accessing http://{host-name}/saml2/service-provider-metadata/{registration-id} results in a 404 Error. Similarly, I'm also receiving a 404 Error when attempting an IDP-initiated login.

HTTP ERROR 404 Not Found
URI: /saml2/service-provider-metadata/{registartion-id}
STATUS:  404
MESSAGE: Not Found
SERVLET: default

I am puzzled as to what could be causing these issues. It seems like my setup should work as it's based on a working example. Has anyone faced similar problems when upgrading to spring-security-saml2-service-provider 5.6.9? Any insights or suggestions on what might be going wrong or what I should check?

siddharth-78 commented 5 months ago

@jzheaux Requesting you to provide your insight on this matter

marcusdacoregio commented 5 months ago

Hi, @siddharth-78.

Can you please add logging.level.org.springframework.security=TRACE in your application.properties and verify the logs? There will probably be more information useful for us to debug what is happening. Try taking a look at the registered filters and check if the Saml2MetadataFilter is present.

marcusdacoregio commented 5 months ago

In addition to the above, Spring Security 5.6.x is not supported anymore therefore we won't be making any changes to that version. If you can, please update to 5.8.9, that may help fixing things. If that is still a problem in 5.8.9, then we could use more information to find the problem.

siddharth-78 commented 5 months ago

Thanks for your response @marcusdacoregio. I had a look at the logs & I see this

2024-02-05 05:05:21,128 INFO MainThread:org.springframework.security.web.DefaultSecurityFilterChain: Will secure any request with [org.springframework.security.web.context.request.async.WebAsynappanagerIntegrationFilter@c0ed7e0, org.springframework.security.web.context.SecurityContextPersistenceFilter@385cce30, org.springframework.security.web.header.HeaderWriterFilter@3971a0f1, org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter@168cf3ea, org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter@55e5f124, org.springframework.security.web.csrf.CsrfFilter@29623197, org.springframework.security.web.authentication.logout.LogoutFilter@4f5507a2, org.springframework.security.web.authentication.logout.LogoutFilter@1850dfee, org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter@248b258, org.springframework.security.saml2.provider.service.web.Saml2MetadataFilter@89f3976, org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter@2480fb3d, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@48eab9ef, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7da44e67, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@38b4f02, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@37d1baee, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1b6a647b, org.springframework.security.web.session.SessionManagementFilter@4d7957ff, org.springframework.security.web.access.ExceptionTranslationFilter@68fa72d3, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@242f6a9b]

Which indicates the presence of Saml2MetadataFilter in the chain.

Here are some additional SAML oriented lines I could find.

2024-02-05 05:05:20,339 TRACE MainThread:org.springframework.security.saml2.core.OpenSamlInitializationService: Initializing OpenSAML

2024-02-05 05:05:21,091 DEBUG MainThread:org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource: Adding web access control expression [authenticated] for any request

2024-02-05 05:05:21,098 TRACE MainThread:org.springframework.security.web.access.intercept.FilterSecurityInterceptor: Validated configuration attributes

2024-02-05 05:05:21,101 DEBUG MainThread:org.springframework.security.saml2.core.OpenSamlInitializationService: Refused to re-initialize OpenSAML
.
.
.
2024-02-05 05:05:22,063 INFO WebServer:org.springframework.security.config.http.FilterInvocationSecurityMetadataSourceParser: Using bean 'webSecurityExpressionHandler' as web SecurityExpressionHandler implementation

2024-02-05 05:05:22,068 INFO WebServer:org.springframework.security.config.http.HttpSecurityBeanDefinitionParser: Checking sorted filter chain: [Root bean: class [org.springframework.security.web.context.SecurityContextPersistenceFilter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, order = 200, <concurrencyFilter>, order = 300, Root bean: class 
.
.
.

2024-02-05 05:05:20,260 TRACE MainThread:org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$EnableGlobalAuthenticationAutowiredConfigurer: Eagerly initializing {myApplicationSAMLSecurityConfig=com.establishment.myApplication.security.components.myApplicationSAMLSecurityConfig$$EnhancerBySpringCGLIB$$ca7ac8b2@18acccde}

Here's the link to extended logs if required for your analysis: https://drive.google.com/file/d/1mkrUF7CuaO43PWwkq2_SAwlzUu1EIfBZ/view?usp=sharing

Let me know as to how I can proceed.

Additionally, I had some queries:

  1. According to my observation, I have set up the RelyingPartyRegistartionRepository correctly (confirmed this by remote debugging & via application logs), wanted to confirm if at all it is possible to download the SP metadata prior to performing an Okta Login or a Okta initiated SP login.
  2. Is my configure method written correctly to allow access to the SP metadata download URL prior to authenticating via IDP/SP initated flow?
  3. Can there be a case where my URL access to metadata endpoint is being blocked by some other spring element from my application?

Thank you for your support 🙂

marcusdacoregio commented 5 months ago

@siddharth-78, before we jump into the analysis, have you tried to upgrade your application to 5.8.9 to check if the problem is solved? If it only happens in 5.6.x we won't be fixing it unfortunately.

siddharth-78 commented 5 months ago

Hey @marcusdacoregio, I followed your suggestion and upgraded my application spring security 5.8.9 and I can confirm that I still face the same issues. Requesting for your guidance on my queries posted earlier. Thank you 🙂

marcusdacoregio commented 5 months ago

Hi, @siddharth-78. I do not see any request to the SAML Metadata endpoint in your application logs. Can you please show us how you are performing such requests, if there is credentials, etc? In addition to that, please provide updated logs with the Spring Security version updated to 5.8.9.

I noticed the following line in your logs 2024-02-05 05:05:22,061 INFO WebServer:org.springframework.security.config.http.FilterInvocationSecurityMetadataSourceParser: Creating access control expression attribute 'hasRole('ROLE_USER')' for /**. If you are not providing credentials to access the URL, it is probably that you will won't be allowed to access the endpoint.

siddharth-78 commented 5 months ago

Sure @marcusdacoregio , I will provide the updated spring 5.8.9 logs & also try to capture the log when I try to download the metadata by hitting “http://{BASE_URL{:{PORT_NUMBER}/saml2/service-provider-metadata/okta-idp” [okta-idp is my registartionId].

As for ’hasRole(‘ROLE_USER’)' for /** is concerned, I see that it is defined in my spring-security.xml configuration file as:

<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />

along with other patterns such as


<intercept-url pattern="/trial1/*/example1" access="authenticated"/>
<intercept-url pattern="/trial2/*/example2" access="authenticated"/>
                                             .
                                             .

I have changed that part now to: <intercept-url pattern="/**" access="permitAll()" />

so that there is nothing blocking the metadata endpoint.

Note that my application with the older SAML version i.e saml2-core 1.0.10.RELEASE, I was able to download the SP metadata by hitting http://{BASE_URL}:{PORT_NUMBER}/saml/metadata prior to logging in to my SP/IDP.

Following your observation, I noticed this definition in the same xml file, allowing access to saml requests [This was for the older SAML, I had to remove this for the newer implementation]

<http pattern="/saml/**" use-expressions="true" entry-point-ref="samlEntryPoint">
        <expression-handler ref="DefaultWebSecurityExpressionHandler" />
        <custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>

        <csrf disabled="true" />
    </http>

<beans:bean id="samlFilter" class="org.springframework.security.web.FilterChainProxy">
        <filter-chain-map request-matcher="ant">
            <filter-chain pattern="/saml/login/**" filters="samlEntryPoint"/>
            <filter-chain pattern="/saml/logout/**" filters="samlLogoutFilter"/>
            <filter-chain pattern="/saml/metadata/**" filters="metadataDisplayFilter"/>
            <filter-chain pattern="/saml/SSO/**" filters="samlWebSSOProcessingFilter"/>
            <filter-chain pattern="/saml/SSOHoK/**" filters="samlWebSSOHoKProcessingFilter"/>
            <filter-chain pattern="/saml/SingleLogout/**" filters="samlLogoutProcessingFilter"/>
            <filter-chain pattern="/saml/discovery/**" filters="samlIDPDiscovery"/>
        </filter-chain-map>
    </beans:bean>

How can I implement a similar access with the newer version? I am guessing it has to be in the configure method. Where I implement something like

http
                        .authorizeRequests(authorize -> authorize
                                .antMatchers("/saml2/**").permitAll()
                                .anyRequest().authenticated())
                                .saml2Login();

Answering this question will help me code and then generate the logs appropriately and augment them, else if you feel you need see the logs first, I will do so. 🙂

marcusdacoregio commented 5 months ago

If you want to change the URI for the Metadata filter, you can check this section of the documentation.

You can also take a look at the SAML2 Federation sample, note that the sample uses the latest Spring Security version.

siddharth-78 commented 5 months ago

Hey @marcusdacoregio , I do not intend on changing the URI for the metadata filter, my main goal is to first be able to download the SP metadata via the default URI i.e http://{BASE_URL{:{PORT_NUMBER}/saml2/service-provider-metadata/{REGISTARTION-ID}

Just wanted to highlight an example from my older SAML (saml2-core 1.01.0.RELEASE) as to provide an understanding as to how my application worked with the older SAML.

My query is whether I need to alter my configure method to provide access to /saml2/* the way my previous SAML setup did [code snippet below] with, as I suspect that it might be the reason for the 404.

<http pattern="/saml/**" use-expressions="true" entry-point-ref="samlEntryPoint">
        <expression-handler ref="DefaultWebSecurityExpressionHandler" />
        <custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>

        <csrf disabled="true" />
    </http>

I will provide you with the logs, but if you can clarify as to how I need to re-write my configure method, it would help me.

I am guessing that the code below is aligned to the .xml config I posted above:

http
                        .authorizeRequests(authorize -> authorize
                                .antMatchers("/saml2/**").permitAll()
                                .anyRequest().authenticated())
                                .saml2Login();
siddharth-78 commented 5 months ago

Hey @marcusdacoregio , here's the link to the logs that you had asked for with spring security at 5.8.9 setting-up-saml2-5.8.9.log -> Relevant logs generated during spring application setup metadata-request-saml2-5.8.9.log -> Relevant logs generated when a request is made to download SP metadata

Here's how my configure method is written:

@Override
    protected void configure(HttpSecurity http) throws Exception {

        if (isSAMLEnabled()) {
            http
                    .authorizeRequests(authorize -> authorize
                                .antMatchers("/saml2/**").permitAll()
                                .anyRequest().authenticated())
                        .saml2Login();

            Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository);
            Saml2MetadataFilter filter = new Saml2MetadataFilter(relyingPartyRegistrationResolver, new OpenSamlMetadataResolver());
            http.addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class);
        }

    }
marcusdacoregio commented 3 months ago

Unfortunately, I cannot deduce exactly what is happening just from the logs. Are you able provide a minimal, reproducible sample where I can just run it on my side?

siddharth-78 commented 3 months ago

Well I have figured out what the issue was, my application has XML based filter configurations & the newer saml expects configurations from Java. So my Java configs creates a FilterChainProxy first and then the XML based FilterChainProxy bean gets created which overwrites the Java FIlterChainProxy, thus it is as though my SAML configs don' exist