Skip to content
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

Spring Boot 3.2 app that uses WebFlux, Security, and Actuator may fail to start due to a missing authentication manager #39096

Closed
razvdana opened this issue Jan 11, 2024 · 7 comments
Assignees
Labels
type: regression A regression from a previous release
Milestone

Comments

@razvdana
Copy link

razvdana commented Jan 11, 2024

After migration to spring boot 3.2.1 (from 3.1.2) I'm facing the following issue:

    Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate 
    [org.springframework.security.web.server.SecurityWebFilterChain]: Factory method 'springSecurityFilterChain' threw 
    exception with message: authenticationManager cannot be null
        	at 
    org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:177) ~ 
   [spring-beans-6.1.2.jar:6.1.2]
        	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:651) ~ 
   [spring-beans-6.1.2.jar:6.1.2]
        	... 143 common frames omitted
    Caused by: java.lang.IllegalArgumentException: authenticationManager cannot be null
        	at org.springframework.util.Assert.notNull(Assert.java:172) ~[spring-core-6.1.2.jar:6.1.2]
	        at org.springframework.security.web.server.authentication.AuthenticationWebFilter.<init> 
   (AuthenticationWebFilter.java:94) ~[spring-security-web-6.2.1.jar:6.2.1]
        	at 
    org.springframework.security.config.web.server.ServerHttpSecurity$HttpBasicSpec.configure(ServerHttpSecurity.java:2305) 
    ~[spring-security-config-6.2.1.jar:6.2.1]
        	at org.springframework.security.config.web.server.ServerHttpSecurity.build(ServerHttpSecurity.java:1545) ~[spring- 
   security-config-6.2.1.jar:6.2.1]
        	at 
org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration.sprin gSecurityFilterChain(ReactiveManagementWebSecurityAutoConfiguration.java:69) ~[spring-boot-actuator-autoconfigure- 3.2.1.jar:3.2.1]
 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
	at 
    org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:140) ~ 
    [spring-beans-6.1.2.jar:6.1.2]
	... 144 common frames omitted

Relevant dependencies:

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
		<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>${jwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>${jwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-gson</artifactId>
            <version>${jwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign.form</groupId>
            <artifactId>feign-form</artifactId>
            <version>${feign-form.version}</version>
        </dependency>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>${caffeine.version}</version>
        </dependency>
        <dependency>
            <groupId>com.playtika.reactivefeign</groupId>
            <artifactId>feign-reactor-spring-cloud-starter</artifactId>
            <version>${reactive-feign.version}</version>
            <type>pom</type>
        </dependency>

Configuration file:

@Configuration
@ComponentScan(basePackageClasses = SecurityConfig.class)
@EnableReactiveMethodSecurity
@EnableWebFluxSecurity
@EnableConfigurationProperties({ ClientProperties.class, MultipleIssuersProperties.class })
class SecurityConfig {

    AuthenticationExceptionEntryPoint authenticationEntryPoint; // autowired
    Issuer mainIssuer;
    MultipleIssuersProperties issuers;
    List<String> whitelistedUrls;
    List<String> allowedOrigins;

   // Constructor omitted

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http
                .authorizeExchange(exchange -> exchange
                    .pathMatchers(whitelistedUrls.toArray(String[]::new))
                    .permitAll()
                    .anyExchange().authenticated())
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .csrf(CsrfSpec::disable)
                .exceptionHandling(exception -> exception.authenticationEntryPoint(authenticationEntryPoint))
                .oauth2ResourceServer(server -> server.authenticationManagerResolver(reactiveAuthenticationManagerResolver()))
                .build();
    }

    @Bean
    public JwtIssuerReactiveAuthenticationManagerResolver reactiveAuthenticationManagerResolver() {
        var managers = new HashMap<String, ReactiveAuthenticationManager>();
        managers.put(
            mainIssuer.issuerUri(),
            new JwtReactiveAuthenticationManager(reactiveJwtDecoder())
        );

        issuers.issuers().forEach(issuer -> {
            var jwkSource = reactiveRemoteJWKSource(issuer);
            var jwtDecoder = reactiveJwtDecoder(issuer, jwkSource);
            var manager = new JwtReactiveAuthenticationManager(jwtDecoder);
            managers.put(issuer.issuerUri(), manager);

        });
        return new JwtIssuerReactiveAuthenticationManagerResolver(issuer -> justOrEmpty(managers.get(issuer)));
    }

    @Bean
    public ReactiveJwtDecoder reactiveJwtDecoder() {
        return reactiveJwtDecoder(mainIssuer, reactiveRemoteJWKSource());
    }

    // other beans omitted

Codebase remains unchanged, aside from version upgrade.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jan 11, 2024
@wilkinsona
Copy link
Member

After migration to spring boot 3.2.1

What version were you using previously?

Relevant dependencies
I think there may be other dependencies that are relevant. For example, I can see ReactiveManagementWebSecurityAutoConfiguration which is part of Spring Boot's actuator but is not shown in your dependencies.

If you would like us to spend some more time investigating, please spend some time providing a complete yet minimal sample that reproduces the problem. You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.

@wilkinsona wilkinsona added the status: waiting-for-feedback We need additional information before we can continue label Jan 11, 2024
@razvdana
Copy link
Author

Edited*, will try to find some time tomorrow to set up something

@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 Jan 11, 2024
@philwebb philwebb added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Jan 11, 2024
@razvdana
Copy link
Author

Update: Somehow I missed some conditionals so the entire SecurityConfig is not loaded. Then through auto configuration it leads to

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'httpHandler' defined in class path resource [org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration$AnnotationConfig.class]: Failed to instantiate [org.springframework.http.server.reactive.HttpHandler]: Factory method 'httpHandler' threw exception with message: Error creating bean with name 'org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration': Unsatisfied dependency expressed through method 'setSecurityWebFilterChains' parameter 0: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.class]: Failed to instantiate [org.springframework.security.web.server.SecurityWebFilterChain]: Factory method 'springSecurityFilterChain' threw exception with message: authenticationManager cannot be null

What would be the recommended approach to conditionally disable security ? Define a web filter chain with permitAll, or disable some auto configurations ?

@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 Jan 12, 2024
@wilkinsona
Copy link
Member

wilkinsona commented Jan 12, 2024

It's hard to say. In your case, ReactiveManagementWebSecurityAutoConfiguration caused the problem. That's the auto-configuration for Actuator security in a WebFlux app. It could be that it should have backed off automatically. You could exclude it, but that may mask an underlying problem rather than solving it. I think we'll really need a sample that reproduces the problem to be able to offer any specific advice or identify if a fix needs to be made.

@wilkinsona wilkinsona added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Jan 12, 2024
@razvdana
Copy link
Author

Created this demo project.

@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 Jan 12, 2024
@wilkinsona wilkinsona self-assigned this Jan 12, 2024
@wilkinsona
Copy link
Member

wilkinsona commented Jan 12, 2024

Thank you.

It works in 3.1.x due to the auto-configuration of a MapReactiveUserDetailsService by ReactiveUserDetailsServiceAutoConfiguration. From this user details service, Spring Security is able to create the authentication manager that the basic auth configured in ReactiveManagementWebSecurityAutoConfiguration requires.

It does not work in 3.2.x due to #35338 which means that the MapReactiveUserDetailsService is no longer auto-configured as you have resource server on the classpath. The same problem occurs with 3.1.x if ReactiveUserDetailsServiceAutoConfiguration is excluded. This is another variant of #37504 that we'd overlooked previously and is the same as the problems that we attempted to fix in afad358.

This fix made in afad358 doesn't work here due to the auto-configuration ordering. The deny-all authentication manager is auto-configured by ReactiveSecurityAutoConfiguration in the absence of a ReactiveAuthenticationManager, a ReactiveUserDetailsService, and a SecurityWebFilterChain:

@Bean
@ConditionalOnMissingBean({ ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class,
SecurityWebFilterChain.class })
ReactiveAuthenticationManager denyAllAuthenticationManager() {
return (authentication) -> Mono.error(new UsernameNotFoundException(authentication.getName()));
}

ReactiveManagementWebSecurityAutoConfiguration is ordered before ReactiveSecurityAutoConfiguration and defines a SecurityWebFilterChain, causing the auto-configuration of the deny-all authentication manager to back off.

For the purposes of Actuator security, I think we need to auto-configure a deny-all authentication manager when there's no ReactiveAuthenticationManager and no ReactiveUserDetailsService. I'm not yet sure how the ordering will work out though.

@wilkinsona wilkinsona added type: regression A regression from a previous release and removed status: waiting-for-triage An issue we've not yet triaged status: feedback-provided Feedback has been provided labels Jan 12, 2024
@wilkinsona wilkinsona added this to the 3.2.x milestone Jan 12, 2024
@wilkinsona wilkinsona changed the title Spring boot 3.2.1 and webflux throw authentication manager cannot be null error when trying to instantiate SecurityWebFilterChain Spring Boot 3.2 app that uses WebFlux, Security, and Actuator may fail to start due to a missing authentication manager Jan 12, 2024
@wilkinsona
Copy link
Member

Closed by 6ec56da.

@wilkinsona wilkinsona modified the milestones: 3.2.x, 3.2.2 Jan 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: regression A regression from a previous release
Projects
None yet
Development

No branches or pull requests

4 participants