Skip to content

WebFlux security should not overwrite the default entry point with a delegating entry point #9266

@foo4u

Description

@foo4u

Describe the bug

When ServerHttpSecurity.build() creates a SecurityWebFilterChain, it replaces the default entry point with the last delegating entry point in this.defaultEntryPoints.

private ServerAuthenticationEntryPoint getAuthenticationEntryPoint() {
if (this.authenticationEntryPoint != null || this.defaultEntryPoints.isEmpty()) {
return this.authenticationEntryPoint;
}
if (this.defaultEntryPoints.size() == 1) {
return this.defaultEntryPoints.get(0).getEntryPoint();
}
DelegatingServerAuthenticationEntryPoint result = new DelegatingServerAuthenticationEntryPoint(
this.defaultEntryPoints);
result.setDefaultEntryPoint(this.defaultEntryPoints.get(this.defaultEntryPoints.size() - 1).getEntryPoint());
return result;
}

Specifically:

result.setDefaultEntryPoint(this.defaultEntryPoints.get(this.defaultEntryPoints.size() - 1).getEntryPoint());

Since delegating entry points should be applied conditionally, this breaks the contract for certain default entry points, such as OAuth2LoginSpec.setDefaultEntryponits, which are designed to be conditional. For example, the OAuth2LoginSpec is designed to not redirect on XHR requests but this behavior breaks that contract.

This cannot be worked around by configuring a new default entry point (e.g., .exceptionHandling().authenticationEntryPoint(...)) because doing so triggers another bug, which I'll open another issue about.

To Reproduce

The issue is reproducible most easily with OAuth2 login. However, the problem is not unique to this scenario.

  1. Configure Spring WebFlux security with OAuth2 login
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig
{
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http)
    {
        return http
                .authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
                .oauth2Login()
                .and()
                .build();
    }
}

https://github.com/foo4u/spring-security-bugs/blob/38ba4cd007eb3a7d75f5912f5a013b7ee4ac197d/src/main/java/com/example/demo/SecurityConfig.java#L9-L22

  1. Make an XHR request to an endpoint requiring authentication with XHR headers expecting an HTTP 401.
	@Test
	void respondsWithHttp401()
	{
		client
				.get()
				.accept(MediaType.APPLICATION_JSON)
				.header("X-Requested-With","XMLHttpRequest")
				.exchange()
				.expectStatus()
				.isUnauthorized();
	}

https://github.com/foo4u/spring-security-bugs/blob/38ba4cd007eb3a7d75f5912f5a013b7ee4ac197d/src/test/java/com/example/demo/DemoApplicationTests.java#L27-L37

  1. Note a 302 redirect to the login provider is sent as the response instead of a 401.

    [ERROR] Failures:
    [ERROR] DemoApplicationTests.respondsWithHttp401:36 Status expected:<401 UNAUTHORIZED> but was:<302 FOUND>

Expected behavior

The web filter chain should return an HTTP 401, not redirect.

Sample

GitHub repository with minimal, reproducible sample.

Two ways to reproduce with sample:

  1. Run the test suite
  2. Start the server and cURL any endpoint

Metadata

Metadata

Assignees

Labels

in: configAn issue in spring-security-configstatus: invalidAn issue that we don't feel is valid

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions