spring-projects / spring-projects/spring-security

Ambiguity when multiple ReactiveAuthenticationManager beans are defined (WebFlux DSL)

Open
#17,971 1 comment 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: waiting-for-triage type: bug
Dominant language
Java
Stars
9.6k
Forks
6.3k
Avg merge
2d 11h
Merged PRs (30d)
52

Description

Describe the bug

When multiple ReactiveAuthenticationManager beans are defined in a WebFlux application (for example, one for opaque token introspection and another for basic authentication), the application fails to start.

Spring Security’s internal configuration (ServerHttpSecurityConfiguration) attempts to autowire a single ReactiveAuthenticationManager without considering qualifiers, which leads to an ambiguity error.

This happens even if the filter chains explicitly reference the correct managers using qualifiers in the DSL.

This seems very similar to issue #9256, where the same problem occurred in HttpSecurityConfiguration (the servlet-based configuration). In this case, the failure happens in ServerHttpSecurityConfiguration, which appears to be the reactive counterpart of HttpSecurityConfiguration.

Description:

Parameter 0 of method setAuthenticationManager in org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfiguration required a single bean, but 2 were found:
        - basicAuthenticationManager: defined by method 'basicAuthenticationManager' in class path resource [.../configuration/SecurityConfiguration.class]
        - opaqueAuthManager: defined by method 'opaqueAuthManager' in class path resource [.../configuration/SecurityConfiguration.class]

This may be due to missing parameter name information


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

At the moment, the only way I managed to get the application to start was by adding a fallback Primary ReactiveAuthenticationManager that simply rejects all authentication attempts. This ensures the context initializes correctly while the actual filter chains still use their explicitly configured managers.

It works, but it feels a bit hacky and not what I expected when using qualifiers

    @Bean
    @Primary
    fun fallbackReactiveAuthenticationManager(): ReactiveAuthenticationManager {
        return ReactiveAuthenticationManager { _ ->
            Mono.error(BadCredentialsException("No qualified ReactiveAuthenticationManager available"))
        }
    }

To Reproduce

  1. Create a new Spring Boot WebFlux application (we are currently using version 3.5.6) with Spring Security.
  2. Add two ReactiveAuthenticationManager beans (for example, one for opaque token introspection and one for basic authentication).
  3. Configure a SecurityWebFilterChain that explicitly wires these managers using qualifiers in the DSL.
  4. Start the application.

Expected behavior
Since I’m explicitly qualifying the managers inside my Configuration Class, Spring Security should be fine with both managers coexisting.

Sample

@Configuration
@EnableWebFluxSecurity
class SecurityConfiguration {

    @Bean
    @Qualifier("opaqueAuthManager")
    fun opaqueAuthManager(introspector: ReactiveOpaqueTokenIntrospector): OpaqueTokenReactiveAuthenticationManager =
        OpaqueTokenReactiveAuthenticationManager(introspector)

    @Bean
    @Qualifier("basicAuthManager")
    fun basicAuthManager(securityProperties: SecurityProperties): ReactiveAuthenticationManager {
        val user: SecurityProperties.User = securityProperties.user
        val userDetails = User.withUsername(user.name).password(user.password).build()
        return UserDetailsRepositoryReactiveAuthenticationManager(MapReactiveUserDetailsService(userDetails))
    }

    @Bean
    fun apiSecurity(http: ServerHttpSecurity,
    ): SecurityWebFilterChain {
        return http {
            securityMatcher(ServerWebExchangeMatchers.pathMatchers("/api/**"))
            authorizeExchange {
                authorize(anyExchange, authenticated)
            }
            oauth2ResourceServer { opaqueToken { } }
            httpBasic { }
            formLogin { disable() }
            csrf { disable() }
            cors { disable() }
            logout { disable() }
            exceptionHandling { authenticationEntryPoint = HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED) }
            authenticationManager = basicAuthManager
        }
    }
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with ServerHttpSecurityConfiguration, especially setAuthenticationManager, and compare the related servlet-side behavior in HttpSecurityConfiguration and issue #9256. Reproduce the startup failure with the two ReactiveAuthenticationManager beans and qualified WebFlux DSL configuration, then verify that the application starts while each filter chain still uses its configured manager.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
authentication, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.