Skip to content

Port in logout URL is not customizable in OIDC back channel logout handler #14679

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
aelillie opened this issue Mar 4, 2024 · 10 comments
Closed
Assignees
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: duplicate A duplicate of another issue type: bug A general bug
Milestone

Comments

@aelillie
Copy link

aelillie commented Mar 4, 2024

Describe the bug
In Spring Security 6.2.2 the OidcBackChannelLogoutHandler.java logout handler automatically replaces the logout URL endpoint hostname with localhost. However, in a Tomcat context, we also need to specify the port number, typically 8080. This is not possible in the current implementation.

To Reproduce
Initiate a back channel logout.

Expected behavior
Local session is invalidated through a POST request to http://localhost:<PORT>/logout, where in my case it should be http://localhost:8080/logout.

Actual behavior
A logout POST request is send to http://localhost/logout, with no effect.

	String computeLogoutEndpoint(HttpServletRequest request) {
		String url = request.getRequestURL().toString();
		return UriComponentsBuilder.fromHttpUrl(url)
			.host("localhost")
			.replacePath(this.logoutEndpointName)
			.build()
			.toUriString();
	}

Sample
This is my security config setup:

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, AccessDeniedHandler accessDeniedHandler,
                                                   AuthenticationEntryPoint authenticationEntryPoint,
                                                   GrantedAuthoritiesMapper grantedAuthoritiesMapper,
                                                   AuthenticationSuccessHandler authenticationSuccessHandler,
                                                   LogoutSuccessHandler logoutSuccessHandler,
                                                   ClientRegistrationRepository clientRegistrationRepository,
                                                   MvcRequestMatcher.Builder mvc,
                                                   OidcSessionRegistry oidcSessionRegistry) throws Exception {
        return http
                .csrf(csrf -> csrf.disable()
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                        .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
                )
                .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
                .authorizeHttpRequests(authorize -> authorize
                        ...
                        .anyRequest().authenticated()
                )
                .oauth2Login(oauth2 -> oauth2
                        .authorizationEndpoint(authorizationEndpointConfig -> {
                            var resolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, OAUTH2_REQUEST_BASE_URI);
                            resolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce());
                            authorizationEndpointConfig.authorizationRequestResolver(resolver);
                        })
                        .userInfoEndpoint(userInfo -> userInfo
                                .userAuthoritiesMapper(grantedAuthoritiesMapper))
                        .successHandler(authenticationSuccessHandler)
                        .oidcSessionRegistry(oidcSessionRegistry)
                )
                .logout(logout -> logout
                        .logoutSuccessHandler(logoutSuccessHandler)
                )
                .oidcLogout(oidcLogout -> oidcLogout
                        .backChannel(withDefaults())
                        .clientRegistrationRepository(clientRegistrationRepository)
                        .oidcSessionRegistry(oidcSessionRegistry)
                )
               ...
                .build();
    }
@aelillie aelillie added status: waiting-for-triage An issue we've not yet triaged type: bug A general bug labels Mar 4, 2024
@jzheaux
Copy link
Contributor

jzheaux commented Mar 4, 2024

Hi, @aelillie, thanks for the report. I'm not sure I understand completely just yet, though. The code you outlined does not remove any port from the resulting URI (see also) so it seems like I might be missing some details to be able to apply the appropriate fix.

Can you provide a minimum reproducer that demonstrates the issue you are having?

@jzheaux jzheaux added status: waiting-for-feedback We need additional information before we can continue in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) and removed status: waiting-for-triage An issue we've not yet triaged type: bug A general bug labels Mar 4, 2024
@aelillie
Copy link
Author

aelillie commented Mar 5, 2024

Hi @jzheaux , thank you for your quick respond. That is true, but the issue is that I do not have the possibility to add one.
If the request URL is e.g. http://server-one.com/logout/connect/back-channel/kc, the resulting logout URL will be http://localhost/logout.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Mar 5, 2024
@jzheaux jzheaux self-assigned this Mar 5, 2024
@jzheaux jzheaux added type: bug A general bug and removed status: feedback-provided Feedback has been provided labels Mar 5, 2024
@jzheaux jzheaux added this to the 6.2.3 milestone Mar 5, 2024
@marcusdacoregio marcusdacoregio modified the milestones: 6.2.3, 6.2.4 Mar 18, 2024
@jzheaux jzheaux added the status: duplicate A duplicate of another issue label Mar 22, 2024
@jzheaux
Copy link
Contributor

jzheaux commented Mar 22, 2024

Thanks for the feedback, @aelillie. I'll close this in favor of #14609, the fix should go out in the next maintenance release.

@dalbani
Copy link

dalbani commented May 25, 2024

In order to build a backchannel logout URI that points to localhost, I'm curious by the way how I can determine the port where the application is running.
I've tried the following but they basically all return null:

  • @Value("${server.port}")
  • Environment::getProperty("server.port")
  • ServerProperties::getPort()

As in:

    public SecurityFilterChain securityFilterChain(HttpSecurity http, ServerProperties serverProperties) throws Exception {
        return http
         ...
                .oidcLogout(oidcLogout -> oidcLogout
                        .backChannel(backChannel -> backChannel
                                .logoutUri(String.format("http://localhost:%d/logout", serverProperties.getPort()))))
                .build();
    }

In the end, I added server.port=8080 to application.properties to be able to look up the server.port property, but is there a more idiomatic / reliable way?

I even tried using ServletWebServerApplicationContext::getWebServer()::getPort() but the application couldn't start up anymore 🤔

@lmorocz
Copy link

lmorocz commented May 27, 2024

Hi @dalbani! You do not need to determinate the port in your configuration for this. You can use the prepared URI variables in the logoutUri from OidcBackChannelLogoutHandler#computeLogoutEndpoint since Spring Security 6.2.4, e.g. {basePort}.

This is a complex configuration with scheme, port and context path:

  oidcLogout.backChannel(backChannel -> backChannel.logoutUri("{baseScheme}://localhost{basePort}{basePath}/logout"))

Please note that the default URI template from Spring Security 6.2.4 should be just fine for your use-case:

final class OidcBackChannelLogoutHandler implements LogoutHandler {
[...]
  private String logoutUri = "{baseScheme}://localhost{basePort}/logout";

@dalbani
Copy link

dalbani commented May 27, 2024

Hi @lmorocz, thanks for your comment.
But I should have said where I'm coming from w.r.t. to this issue.
Basically, as it was raised in comments in #14553: if the requests to the backchannel logout endpoints is made to e.g. https://example.com/logout/connect/back-channel/my-registration then Spring Security uses https://localhost/logout for the subsequent call.
Which is not correct in my case, as it should rather be http://localhost:8080/logout.
That's why I cannot make use of {baseScheme} or {basePort}.

@lmorocz
Copy link

lmorocz commented May 27, 2024

Then use {baseHost} instead of localhost in the logoutUri (along with {baseScheme} and {basePort} as well). This way you will end up a request to https://example.com/logout (which is a revproxy to http://localhost:8080/logout I assume).

Another option is to, set up your IDP (Keycloak?) client BackChannel logout URI to the internal URI of your app (http://localhost:8080/logout/connect/back-channel/my-registration) - provided they are on the same host. Or set up the internal IP address or host name of the host of your app if they are on the same subnet (http://myapphost:8080/logout/connect/back-channel/my-registration). Then use the proper URI variables in the logoutUri() configuration at your discretion.

@dalbani
Copy link

dalbani commented May 27, 2024

Then use {baseHost} instead of localhost in the logoutUri (along with {baseScheme} and {basePort} as well). This way you will end up a request to https://example.com/logout (which is a revproxy to http://localhost:8080/logout I assume).

Indeed, that's an option I didn't consider. Although it involves making the request go through the reverse proxy... to eventually reach the application itself. Not a dealbreaker in the grand scheme of things, but still a little over complicated.

Another option is to, set up your IDP (Keycloak?) client BackChannel logout URI to the internal URI of your app

This is unfortunately not a option for me, as the IdP (Keycloak indeed) cannot access the app directly. It has to go through the reverse proxy.

@lmorocz
Copy link

lmorocz commented May 27, 2024

Yes, it is complicated, but please note that before 6.2.2 this was the only way as 6.2.1 only replaced the path of the original request to /logout and nothing else. We have more configuration options now which is always nice.

@dalbani
Copy link

dalbani commented May 27, 2024

I'm glad that 6.2.2 introduced an improvement indeed; kudos to the Spring Security developers 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: duplicate A duplicate of another issue type: bug A general bug
Projects
None yet
Development

No branches or pull requests

6 participants