Search code examples
javaspring-bootspring-security

Spring Boot 3 - How do I get permitAll() to work?


I am using Spring Boot 3. I am trying to permit all requests to a particular endpoint. I have the user's details in a mysql table (users), where I store the username, password and the role. The password is encrypted using Bcrypt. I used https://bcrypt-generator.com/ to generate the encrypted code and stored it in the database. Here's the sample password from the table - {bcrypt}$2a$12$2mERuojKpXO8N2qX2OXmBOAIuVM6pSImxlcrrJXHNyI3iowgHe5Zi

Here's the code

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class RestSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests( configurer -> 
            configurer
                .requestMatchers(HttpMethod.POST, "/api/auth/**").permitAll()
        );
        
        // use HTTP Basic Authentication
        http.httpBasic(Customizer.withDefaults());

        // disable Cross Site Request Forgery (CSRF)
        http.csrf(csrf -> csrf.disable());

        return http.build();
    }

    @Bean
    public UserDetailsManager userDetailsManager(DataSource dataSource){
        JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource);
        jdbcUserDetailsManager.setUsersByUsernameQuery(
            "select username, password, enabled from users where username=?"
        );
        jdbcUserDetailsManager.setAuthoritiesByUsernameQuery(
            "select username, role from users where username=?"
        );

        return jdbcUserDetailsManager;
    }

    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }
}

However, when I try to access this endpoint, I get a 401 Unauthorized. It seems like permitAll() is not working. It still requires me to provide the login credentials. How do I fix this?

Another thing I wanted to know was, how do I send the encrypted password for authentication on Postman?

Edit 1:

Here are the logs -

2023-12-21T17:01:31.411+05:30  WARN 22830 --- [nio-8080-exec-2] o.s.s.c.bcrypt.BCryptPasswordEncoder     : Encoded password does not look like BCrypt
2023-12-21T17:01:31.411+05:30 DEBUG 22830 --- [nio-8080-exec-2] o.s.s.a.dao.DaoAuthenticationProvider    : Failed to authenticate since password does not match stored value
2023-12-21T17:01:31.412+05:30 DEBUG 22830 --- [nio-8080-exec-2] o.s.s.w.a.www.BasicAuthenticationFilter  : Failed to process authentication request

Solution

  • Authentication is not authorization. permitAll is part of authorization and applies after authentication. Yes, authentication may leave the request without credentials anonymous which permitAll then accepts. But not when authentication process itself encounters an exception processing credentials, which is your case - your credentials are malformed. So, 401 happens because authentication process detects that request has credentials but still cannot make a decision. And yes, it is intended behavior.

    The solution is to fix credentials processing. And based on the example token you provided ({bcrypt}....) you should use delegating encoder. The difference is that BCryptPasswordEncoder handles tokens of only one type and does not expect any prefix, whereas DelegatingPasswordEncoder can handle tokens of various types and requires prefix to distinguish between them. There is a convinient method to construct such an encoder with reasonable set of possible prefixes:

      @Bean
      public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
      }