Home > Enterprise >  Spring boot WebSecurityConfigurerAdapter basic authentication user password validation issue
Spring boot WebSecurityConfigurerAdapter basic authentication user password validation issue

Time:05-09

I'm writing a simple REST API using Spring Boot and I want to enable basic authentication. Therefore I have used the WebSecurityConfigurerAdapter as shown below. For simplicity, I just want to check only the password (pwd123) and allow any user to log in. Please refer to the code below.

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new AuthenticationProvider() {
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                if (authentication == null || authentication.getCredentials() == null) {
                    throw new BadCredentialsException("Bad credentials");
                }
                if (authentication.getCredentials().equals("pwd123")) {
                    return new UsernamePasswordAuthenticationToken(authentication.getName(),
                            authentication.getCredentials().toString(),
                            Collections.emptyList());
                }

                return null;
            }

            @Override
            public boolean supports(Class<?> authentication) {
                return authentication.equals(UsernamePasswordAuthenticationToken.class);
            }
        });
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
                .and().httpBasic();
    }
}

Assume user_A has accessed the REST API with a valid password, i.e pwd123, and then do the send API call with a wrong password. However the user is allowed to access the API which is the problem.

When I do the debugging I realized that authenticationIsRequired function in BasicAuthenticationFilter class which is in Spring Security, returns false in such scenario. Please refer that code.

    private boolean authenticationIsRequired(String username) {
    // Only reauthenticate if username doesn't match SecurityContextHolder and user
    // isn't authenticated (see SEC-53)
    Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
    if (existingAuth == null || !existingAuth.isAuthenticated()) {
        return true;
    }
    // Limit username comparison to providers which use usernames (ie
    // UsernamePasswordAuthenticationToken) (see SEC-348)
    if (existingAuth instanceof UsernamePasswordAuthenticationToken && !existingAuth.getName().equals(username)) {
        return true;
    }
    // Handle unusual condition where an AnonymousAuthenticationToken is already
    // present. This shouldn't happen very often, as BasicProcessingFitler is meant to
    // be earlier in the filter chain than AnonymousAuthenticationFilter.
    // Nevertheless, presence of both an AnonymousAuthenticationToken together with a
    // BASIC authentication request header should indicate reauthentication using the
    // BASIC protocol is desirable. This behaviour is also consistent with that
    // provided by form and digest, both of which force re-authentication if the
    // respective header is detected (and in doing so replace/ any existing
    // AnonymousAuthenticationToken). See SEC-610.
    return (existingAuth instanceof AnonymousAuthenticationToken);
}

Please let me know what is missing in my implementation

CodePudding user response:

As mentioned in the comments, instead of providing a custom AuthenticationProvider you can try providing a custom UserDetailsService. Here's the complete configuration:

@EnableWebSecurity
public class SecurityConfiguration {

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

        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return (username) -> new User(username, "{noop}pwd123", AuthorityUtils.createAuthorityList("ROLE_USER"));
    }

}

When you evolve to looking up the user via a third-party service, you can add the code to do this in the custom UserDetailsService (a lambda function or an actual class that implements the interface) and continue returning a org.springframework.security.core.userdetails.User.

Note: I don't actually recommend plain-text passwords in production. You would replace {noop}pwd123 with something like {bcrypt}<bcrypt encoded password here>.

CodePudding user response:

As suggested in the comments and answers, even if you use the InMemoryUserDetailsManager the problem does not get resolved, which means, once the user is authenticated with the correct user name and password, his password is not validated in the subsequent REST API calls,i.e. can use any password. This is because of the functionality in BasicAuthenticationFilter class where it skips users who are having a valid JSESSION cookie.

To fix the issue, we should configure http to create state-less sessions via http .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

in configure function of the WebSecurityConfigurerAdapter

Please refer Why BasicAuthenticationFilter in spring security matches only username and not the password

  • Related