javaspringspring-bootspring-mvcspring-security

Why default User still creating even added the EnableWebSecurity annotation and InMemoryUserDetailsManager


I am trying to make an application that uses custom User and Admin credentials for Authentication. However, the custom user credential gets an unauthorized exception. Only I can log in using the system-generated credentials user and generated password.

SecurityConfig.java

     import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

     @Configuration
     @EnableWebSecurity
     public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(requests -> requests.anyRequest().authenticated())
                .formLogin(withDefaults())
                .httpBasic(withDefaults());

        return http.build();
    }

    @Bean
    public InMemoryUserDetailsManager userDetailsService() {
        System.out.println("--------------------------------In UserDetailsService");
        UserDetails user = User.withUsername("user")
                .password("{noop}password")
                .roles("USER")
                .build();

        UserDetails admin = User.withUsername("admin")
                .password("{noop}password")
                .roles("USER", "ADMIN")
                .build();

        return new InMemoryUserDetailsManager(user, admin);
    }}

ApiGatewayApplication,java

@SpringBootApplication
public class ApiGatewayApplication {

public static void main(String[] args) {
    SpringApplication.run(ApiGatewayApplication.class, args);
}
}

application.properties

spring.application.name=ApiGateway
server.port=8083
spring.main.allow-bean-definition-overriding=true
logging.level.org.springframework.security=DEBUG

spring.cloud.gateway.routes[0].id=QuestionService
spring.cloud.gateway.routes[0].uri=lb://QuestionService
spring.cloud.gateway.routes[0].predicates[0]=Path=/question/**

spring.cloud.gateway.routes[1].id=QuizService
spring.cloud.gateway.routes[1].uri=lb://QuizService
spring.cloud.gateway.routes[1].predicates[0]=Path=/quiz/**, /quiz-test/**

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.gateway</groupId>
    <artifactId>ApiGateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ApiGateway</name>
    <description>This is a API Gateway Server</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>23</java.version>
        <spring-cloud.version>2023.0.3</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

API Gateway log

2024-10-10T15:00:11.167-04:00  INFO 21580 --- [ApiGateway] [           main] ctiveUserDetailsServiceAutoConfiguration : 

Using generated security password: da26512d-fcb0-49c0-a8e3-5814686ed7a5

enter image description here


Solution

  • After rigorous testing and research, I found out where is the problem:

    The problem is that when spring boot application is enabled with spring-cloud-starter-gateway and spring-boot-starter-security, then the application is trying to use ReactiveUserDetailsServiceAutoConfiguration class from security.reactive package present in the spring-boot-autoconfigure library to configure reactive based user details service to do the authentication.

    That means that your application is enabled with Spring WebFlux by default when spring-cloud-starter-gateway is used and it has to use that auto-configuration class which is specially made to configure and used to authenticate webflux based application.


    Let me demonstrate what I mean:

    Scenario 1: When I remove spring-cloud-starter-gateway & jakarta.servlet-api dependencies and added the spring-boot-starter-web dependency, then your configuration works. You can test it out. I was able to login via both user and admin. Note: This detail is added just to prove that your configuration is correct.

    Scenario 2: I took the same configuration and dependencies as it is that you have provided, then the error that I faced.

    org.springframework.security.authentication.BadCredentialsException: Invalid Credentials
        at org.springframework.security.authentication.AbstractUserDetailsReactiveAuthenticationManager.lambda$authenticate$1(AbstractUserDetailsReactiveAuthenticationManager.java:108) ~[spring-security-core-6.3.3.jar:6.3.3]
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:45) ~[reactor-core-3.6.10.jar:3.6.10]
        at reactor.core.publisher.Mono.subscribe(Mono.java:4576) ~[reactor-core-3.6.10.jar:3.6.10]
        at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:82) ~[reactor-core-3.6.10.jar:3.6.10]
        at reactor.core.publisher.FluxFilter$FilterSubscriber.onComplete(FluxFilter.java:166) ~[reactor-core-3.6.10.jar:3.6.10]
        at reactor.core.publisher.MonoPublishOn$PublishOnSubscriber.run(MonoPublishOn.java:182) ~[reactor-core-3.6.10.jar:3.6.10]
        at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68) ~[reactor-core-3.6.10.jar:3.6.10]
        at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28) ~[reactor-core-3.6.10.jar:3.6.10]
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[na:na]
        at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) ~[na:na]
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[na:na]
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[na:na]
        at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
    
    2024-10-13T12:30:27.577+05:30 DEBUG 6790 --- [so-security-demo] [oundedElastic-2] o.s.s.w.s.DefaultServerRedirectStrategy  : Redirecting to '/login?error'
    

    Screenshot:

    enter image description here

    See the log above, it's using flux libraries behind the scenes. This means that your application is enabled with flux when spring-cloud-starter-gateway and spring-boot-starter-security library is used.

    How to fix this:

    Update SecurityConfig to this:

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.ReactiveAuthenticationManager;
    import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
    import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
    import org.springframework.security.config.web.server.ServerHttpSecurity;
    import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.web.server.SecurityWebFilterChain;
    
    import static org.springframework.security.config.Customizer.withDefaults;
    
    @Configuration
    @EnableWebFluxSecurity
    public class SecurityConfig {
    
        @Bean
        public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) throws Exception {
            http.authorizeExchange(requests -> requests.anyExchange().authenticated())
                    .authenticationManager(reactiveAuthenticationManager())
                    .formLogin(withDefaults())
                    .httpBasic(withDefaults());
            return http.build();
        }
    
        @Bean
        public ReactiveAuthenticationManager reactiveAuthenticationManager() {
            System.out.println("--------------------------------In UserDetailsService");
            UserDetails user = User.withUsername("user")
                    .password("{noop}password")
                    .roles("USER")
                    .build();
    
            UserDetails admin = User.withUsername("admin")
                    .password("{noop}password")
                    .roles("USER", "ADMIN")
                    .build();
            return new UserDetailsRepositoryReactiveAuthenticationManager(new MapReactiveUserDetailsService(user, admin));
        }
    }
    

    Points to be noted:

    Then it will work.

    Logs:

    2024-10-13T12:40:05.073+05:30 DEBUG 6981 --- [so-security-demo] [ctor-http-nio-2] a.DelegatingReactiveAuthorizationManager : Checking authorization on '/' using org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager@70d82571
    2024-10-13T12:40:05.073+05:30 DEBUG 6981 --- [so-security-demo] [ctor-http-nio-2] ebSessionServerSecurityContextRepository : Found SecurityContext 'SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=user, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, CredentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER]]]' in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@4d1672fd'
    2024-10-13T12:40:05.074+05:30 DEBUG 6981 --- [so-security-demo] [ctor-http-nio-2] o.s.s.w.s.a.AuthorizationWebFilter       : Authorization successful
    

    Also, your system generated password thing will be solved as well. It won't generate at all.

    See if this helps.