Hi Team,

We are using Spring Cloud Gateway for basic authentication and routing with the below dependencies spring-boot-starter-parent --> 2.7.0 spring-cloud-version --> 2021.0.1 java-version --> 11 spring-boot-starter-webflux --> 2.7.0 (managed version) spring-boot-starter-security --> 2.7.0 (managed version)

Problem description:

Basic authentications works fine with the credentials we pass and add it as a token in Authorization-header for positive scenarios but when we are passing the wrong credentials in the consequent request it picks the same Authorization-header as of the previous request and passes the authentication resulting in error scenario. We should be receiving 401 but instead gets 200 status code. The vice versa is also happening i.e when we pass the correct credentials but the previous request returned 401, our request gets failed as it expects 200 but we receive 401.

Solution Tried:

We tried to remove caching with the below ways but got no luck: 1. spring.cloud.loadbalancer.cache.enabled=false in application properties. 2. securityContextRepository(NoOpServerSecurityContextRepository.getInstance()) in the WebSecurityConfig class as below @Bean public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {

    return http.csrf().disable().authorizeExchange().pathMatchers("/ping").permitAll()
            .pathMatchers("/**").access(authorizationManager)
            .and()
            .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
            .httpBasic().and().formLogin().authenticationManager(authenticationManager)
            .and()
            .build();
}

Could you please help us in the same.

Comment From: sjohnr

Thanks for reaching out @kartik-kaushik!

Given the current priority on the 5.8 and 6.0 releases, it would be very helpful if you could provide a minimal, reproducible sample. The most important thing with the sample is steps to reproduce the issue. For example, I'm unclear how you are making requests and what your Spring Cloud configuration looks like. I am also unsure what your custom authentication or authorization managers look like.

Comment From: Priyap1997

@sjohnr We are facing issue with authorization, where the wrong credentials in the authorization header are getting cached, and even for correct credentials, we are getting the 401 error intermittently.

We tested by creating a postman collection of 5 endpoints where 2 have the wrong authorization value and 3 have the correct authorization value. And then we run this in postman runner with an iterations value of 20 and run that runner simultaneously in the 6 to 7 tab. We observe that the correct request also started failing with 401 error intermittently.

AuthenticationManager

    @Value("${security.user.name}")
    private String username;

    @Value("${security.user.password}")
    private String password;

    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {

        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        if (authentication.getCredentials() != null || !authentication.getCredentials().equals("")) {
            authToken = authentication.getCredentials().toString();
        }
        if (authentication.getPrincipal() != null || !authentication.getPrincipal().equals("")) {
            authName = authentication.getPrincipal().toString();
        }
        if (userDetailsService.findByUsername(authName) != null && authToken.equals(password) && authName.equals(username)) {
            return Mono.just(new UsernamePasswordAuthenticationToken(username, password, authorities));
        } else {
            logger.info("Credentials are not correct for gateway");
            return Mono.error(new LoginException("Bad credentials"));
        }
    }

AuthorizationManager

 @Override
    public Mono<AuthorizationDecision> check(Mono authentication, Object object) {
        return authentication
                .map(a -> {
                    return new AuthorizationDecision(true);
                }).switchIfEmpty(Mono.defer(() -> Mono.error(new LoginException("Full authentication is required to access this resource"))));
    }

    @Override
    public Mono<UserDetails> findByUsername(String username) {
        User.UserBuilder user = User.withUsername(username)
                .roles("ROLE_ADMIN")
                .password(encoder.encode(password));
        return  Mono.just(user.build());
    }

WebSecurityConfig

    @Bean
    public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {

        return http.csrf().disable().authorizeExchange().pathMatchers("/ping").permitAll()
                .pathMatchers("/**").access(authorizationManager)
                .and()
                .httpBasic().and().formLogin().authenticationManager(authenticationManager)
                .and().build();
    }

application.yaml

security:
  user:
    name: user
    password: pass
spring:
  cloud:
    gateway:
      routes:
        - id: serviceA
          uri: http://localhost:8080
          predicates:
            - Path=/api/**

spring-boot-starter-parent --> 2.7.0 spring-cloud-version --> 2021.0.1 java-version --> 11 spring-boot-starter-webflux --> 2.7.0 (managed version) spring-boot-starter-security --> 2.7.0 (managed version)

Comment From: sjohnr

Hi @Priyap1997 thanks for providing additional information!

However, the example you provided is still not minimal and does not include full steps for reproducing the issue, but instead simply describes a postman collection. I'm also not clear whether spring cloud gateway configuration calls another service or the same service recursively (on port 8080, which could very well be the issue) and therefore cannot test it. The Spring Cloud Gateway configuration should be removed from the example. Lastly, the custom AuthenticationManager and AuthorizationManager should be removed from the example as they don't seem pertinent to the issue you're describing.

With that in mind, I have built a simple example based on your description which uses the built-in MapReactiveUserDetailsService and standard authorization rules, and then stress tested with Postman in a way similar to how you described, and I cannot reproduce the issue. I'm going to close this issue for now. If you can provide a minimal sample and a request, set of requests, or postman collection that reproduces the issue, we can re-open and investigate further.

Comment From: Priyap1997

@sjohnr Can you try to reproduce the issue with these code and postman-collection

gateway: https://github.com/Priyap1997/spring-gateway backend-service: https://github.com/Priyap1997/spring-boot-hello-world postman-collection: https://github.com/Priyap1997/postman-collection

Please run postman-runner in more than one tab simultaneously to reproduce the issue

Comment From: sjohnr

Hi @Priyap1997, thanks for providing the sample.

However, the sample is not minimal, and contains custom code that I believe is the cause of the bug you are reporting. The use of a ThreadLocal in JsonExceptionHandler mixed with reactive code is not correct, and causes thread safety issues. This is not an issue with Spring Security.

In the future, please ensure your sample is truly minimal and seek to remove any custom code that could contain bugs. Often, this process will help you find bugs in your own code.