Search code examples
springspring-security

How to configure multiple oauth2 redirect uris in spring security


Given I have 3 oauth2 providers, how can I customise their client registration redirect-Uris.

They do not all fit the default pattern /login/oauth2/code/{registrationId}

  1. Azure AD B2C has custom: /openId
  2. Salesforce has default of /login/oauth2/code/sf
  3. Flow has default of /login/oauth2/code/flow

Here is the SecurityFilterChain

    @Bean
public SecurityFilterChain clientSecurityFilterChain(
        HttpSecurity http,
        OAuth2ClientProperties bootClientProperties,
        InMemoryClientRegistrationRepository clientRegistrationRepository,
        SpringAddonsOidcProperties springAddonsOidcProperties,
        LogoutProperties logoutProperties) throws Exception {

    http.csrf(AbstractHttpConfigurer::disable);
    http.oauth2Login(login -> login
            .successHandler(trapsAuthenticationSuccessHandler())
            .failureHandler(trapsAuthenticationFailureHandler())
            .authorizationEndpoint(auth -> auth
                    .authorizationRequestResolver(new SpringAddonsOAuth2AuthorizationRequestResolver(bootClientProperties, clientRegistrationRepository, springAddonsOidcProperties.getClient())))
            .redirectionEndpoint(endpoint -> endpoint
                    .baseUri("/openId/*"))
                    );
    http.exceptionHandling(ex -> ex
            .authenticationEntryPoint(trapsLoginUrlAuthenticationEntryPoint()));
    http.logout(logout ->
        logout.logoutSuccessHandler(new DelegatingOidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository, logoutProperties, "{baseUrl}")));
    http.authorizeHttpRequests(auth ->
            auth.anyRequest().permitAll());
    return http.build();
}

Here is the config:

### IDP: Azure AD B2C ###
spring.security.oauth2.client.provider.b2c.jwk-set-uri=${b2c-jwks-endpoint}
spring.security.oauth2.client.provider.b2c.authorization-uri=${b2c-authorization-endpoint}
spring.security.oauth2.client.provider.b2c.token-uri=${b2c-token-endpoint}

spring.security.oauth2.client.registration.b2c-retrieve.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.b2c-retrieve.client-id=${b2c-client-id}
spring.security.oauth2.client.registration.b2c-retrieve.client-secret=${b2c-client-secret}
spring.security.oauth2.client.registration.b2c-retrieve.provider=b2c
spring.security.oauth2.client.registration.b2c-retrieve.scope=${b2c-client-id},openid
spring.security.oauth2.client.registration.b2c-retrieve.redirect-uri={baseUrl}/openId

spring.security.oauth2.client.registration.b2c-save.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.b2c-save.client-id=${b2c-client-id}
spring.security.oauth2.client.registration.b2c-save.client-secret=${b2c-client-secret}
spring.security.oauth2.client.registration.b2c-save.provider=b2c
spring.security.oauth2.client.registration.b2c-save.scope=${b2c-client-id},openid
spring.security.oauth2.client.registration.b2c-save.redirect-uri={baseUrl}/openId
### IDP: Azure AD B2C ###

### IDP: Salesforce ###
sf-issuer=https://lv-direct--inttrn.sandbox.my.site.com/giportal
sf-secret=${secret-2}
spring.security.oauth2.client.provider.sf.issuer-uri=${sf-issuer}
spring.security.oauth2.client.registration.sf.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.sf.client-name=Salesforce (oidc)
spring.security.oauth2.client.registration.sf.client-id=${client-id-2}
spring.security.oauth2.client.registration.sf.client-secret=${client-secret-2}
spring.security.oauth2.client.registration.sf.redirect-uri={baseUrl}/login/oauth2/code/sf
spring.security.oauth2.client.registration.sf.provider=sf
spring.security.oauth2.client.registration.sf.scope=openid
### IDP: Salesforce IDP ###

### IDP: Flow ###
flow-issuer=https://id-lv-test.athenapaas.com/auth/realms/customer-portal
flow-secret=${secret-3}
spring.security.oauth2.client.provider.flow.issuer-uri=${flow-issuer}
spring.security.oauth2.client.registration.flow.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.flow.client-name=Flow (Keycloak - oidc)
spring.security.oauth2.client.registration.flow.client-id=${client-id-3}
spring.security.oauth2.client.registration.flow.client-secret=${client-secret-3}
spring.security.oauth2.client.registration.flow.redirect-uri={baseUrl}/login/oauth2/code/flow
spring.security.oauth2.client.registration.flow.provider=flow
spring.security.oauth2.client.registration.flow.scope=openid
### IDP: Flow ###

I can see the correct redirect-uri is passed to the provider, the oauth2 security filter is not invoked when the provider redirects back.

Note: I’m using spring boot 3.2.3 & spring security 6.2.2

Here are the debug logs for: logging.level.org.springframework.security=DEBUG

:: Spring Boot ::                (v3.2.3)

Standard Commons Logging discovery in action with spring-jcl: please remove commons-logging.jar from classpath in order to avoid potential conflicts
2024-03-21 11:28:15,214 [LAP***::] DEBUG org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder - No authenticationProviders and no parentAuthenticationManager defined. Returning null.
2024-03-21 11:28:16,924 [LAP***::] INFO  org.springframework.security.web.DefaultSecurityFilterChain - Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@61a7930e, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@60eacbbf, org.springframework.security.web.context.SecurityContextHolderFilter@7f129873, org.springframework.security.web.header.HeaderWriterFilter@40f7fb45, org.springframework.web.filter.CorsFilter@36e6e59f, org.springframework.security.web.authentication.logout.LogoutFilter@c521a79, org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter@5ce59833, org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter@22071f0, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@504f2bcd, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@51dd74a0, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@676beb9f, org.springframework.security.web.access.ExceptionTranslationFilter@36afd06f, org.springframework.security.web.access.intercept.AuthorizationFilter@2d3bb293]
2024-03-21 11:29:18,839 [LAP***::] DEBUG org.springframework.security.web.FilterChainProxy - Securing OPTIONS /config
2024-03-21 11:29:19,036 [LAP***::] DEBUG org.springframework.security.web.FilterChainProxy - Securing POST /config
2024-03-21 11:29:19,079 [LAP***::] DEBUG org.springframework.security.web.FilterChainProxy - Secured POST /config
2024-03-21 11:29:19,312 [LAP***:0502ff48-95de-42f0-8ef6-2a02a1aa9e4a:98910f08-2904-464b-801f-e69ae6d5e773] DEBUG org.springframework.security.web.authentication.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
2024-03-21 11:29:19,328 [LAP***::] DEBUG org.springframework.security.web.savedrequest.HttpSessionRequestCache - Saved request https://localhost:8444/config?continue to session
2024-03-21 11:29:19,331 [LAP***::] DEBUG org.springframework.security.web.savedrequest.HttpSessionRequestCache - Saved request https://localhost:8444/config?continue to session
2024-03-21 11:29:19,506 [LAP***::] DEBUG org.springframework.security.web.FilterChainProxy - Securing GET /oauth2/authorization/b2c-retrieve
2024-03-21 11:29:19,551 [LAP***::] DEBUG org.springframework.security.web.DefaultRedirectStrategy - Redirecting to https://sys-login.lvgig.co.uk/270***/oauth2/v2.0/authorize?response_type=code&client_id=f3b***&scope=f3b***%20openid&state=55ViNeTEVyXndja9l69-vTTTZgwPOj1qeSwnXBf_zhA%3D&redirect_uri=https://localhost:8444/openId&nonce=7XVzDwtfW3D-zgoioI1seJly7dtY4Woy5-XNxRKSTic&p=B2C_1A_RPRetrieveQuoteQA3&product=car&style=new&ujt=retrievequote&response_mode=form_post&code_challenge=O53oNMjGap2D3B1GBReVh7knG72A4ngN4vc9i5Vj5vc&code_challenge_method=S256
2024-03-21 11:30:02,729 [LAP***::] DEBUG org.springframework.security.web.FilterChainProxy - Securing POST /openId
2024-03-21 11:30:02,748 [LAP***::] DEBUG org.springframework.security.web.FilterChainProxy - Secured POST /openId
2024-03-21 11:30:02,809 [LAP***::] ERROR com.lv.gi.journeyengine.services.core.impl.DefaultWorkflowServiceImpl - No Workflow found given requestContext

Update 1: I tried changing the following, based on: https://docs.spring.io/spring-security/reference/servlet/oauth2/login/advanced.html#oauth2login-advanced-redirection-endpoint, still not working.

${je-url} --> {baseUrl} in the config redirect-uri's

.baseUri("/openId") --> .baseUri("/openId/*")

I have updated the original question to reflect this.

Update 2: If I comment out the following from the SecurityFilterChain, Salesforce and Flow providers again work:

//              .authorizationEndpoint(auth -> auth
//                      .authorizationRequestResolver(new SpringAddonsOAuth2AuthorizationRequestResolver(bootClientProperties, clientRegistrationRepository, springAddonsOidcProperties.getClient())))
//              .redirectionEndpoint(endpoint -> endpoint
//                      .baseUri("/openId/*"))

I now suspect, how I'm using SpringAddonsOAuth2AuthorizationRequestResolver (from https://github.com/ch4mpy/spring-addons) is causing the other providers to break, and likely adding to the confusion in this problem.


Solution

  • The calls to the redirect-uri are handled by Spring Security's OAuth2LoginAuthenticationFilter

    It is normally configured via the Spring Security DSL e.g:

    @Bean
    public SecurityFilterChain clientSecurityFilterChain(HttpSecurity http) throws Exception {
        http.oauth2Login(login -> login
                .redirectionEndpoint(endpoint -> endpoint
                        .baseUri("/openId"))
        );
        return http.build();
    }
    

    As I found, this allows limited scope should that redirect-uri need to comprise 2 patterns: /openId & /login/oauth2/code/*

    I've found however I could postProcess the OAuth2LoginAuthenticationFilter to allow custom redirect-uri matching as follows:

    private static ObjectPostProcessor<OAuth2LoginAuthenticationFilter> postProcessor = new ObjectPostProcessor<>() {
        @Override
        public <O extends OAuth2LoginAuthenticationFilter> O postProcess(O object) {
            object.setRequiresAuthenticationRequestMatcher(
                    new OrRequestMatcher(
                            AntPathRequestMatcher.antMatcher("/openId"),
                            AntPathRequestMatcher.antMatcher("/login/oauth2/code/*")));
            return object;
        }
    };
    
    @Bean
    public SecurityFilterChain clientSecurityFilterChain(HttpSecurity http) throws Exception {
        http.oauth2Login(login -> login
                .addObjectPostProcessor(postProcessor)
        );
        return http.build();
    }