Home > Software engineering >  Reactive spring security authentication using R2DBC mysql connection
Reactive spring security authentication using R2DBC mysql connection

Time:04-16

After about 2 weeks of reading countless tutorials/javadocs and trying to get Spring security to work using a webflux, R2DBC and mysql combo to work, I'm finally ready to admit that I'm stuck :(

Every login request is being blocked, even though the details are correct (matched using online BCrypt verifier).

Is there a gap in my understanding? have I missed something?

Any pointers would be greatly appreciated.

ReactiveAuthenticationManager

@Bean
protected ReactiveAuthenticationManager reactiveAuthenticationManager() {

    log.info("Received authentication request");

    return authentication -> {

        UserDetailsRepositoryReactiveAuthenticationManager authenticator = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
        authenticator.setPasswordEncoder(passwordEncoder);

        return authenticator.authenticate(authentication);
    };
}

UserDetailsService

@Component
public class UserDetailsService implements ReactiveUserDetailsService {

    @Autowired
    public UserRepository userRepository;

    @Override
    public Mono<UserDetails> findByUsername(String username) {

        return userRepository.findByUsername(username).map(CustomUser::new);
    };
}

Filters

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {


    http
            //require that all
            .authorizeExchange(
                exchanges ->exchanges.anyExchange().authenticated()
            )

            .httpBasic(withDefaults())

            //this allows js to read cookie
            .csrf(csrf -> csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()))

            .formLogin(
                withDefaults()
            );


    return http.build();

}

CustomUser

public class CustomUser implements UserDetails {

    private String username;
    private String password;
    private int enabled;

    public CustomUser(){
    }

    public CustomUser(UserDetails user){

        this.setUsername(user.getUsername());
        this.setPassword(user.getPassword());
        this.setEnabled(user.isEnabled() == true?1:0);

        log.info("Match found : "   this.toString());
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return this.enabled == 1;
    }

}

CodePudding user response:

Answering my own question, just in case anybody is having the same problem. AuthenticationManager interface and the AuthenticationProvider interface both have the authenticate() method. I belive the correct one to use would be one from class <? extends AuthenticationProvider>

However, in the absence of a ready-made AuthenticationProvider for databases, I simply did the following:

@Bean
protected ReactiveAuthenticationManager reactiveAuthenticationManager() {

    return authentication -> { 
                userDetailsService.findByUsername( authentication.getPrincipal().toString() )

                .switchIfEmpty( Mono.error( new UsernameNotFoundException("User not found")))

                .flatMap(user->{

                    final String username           =   authentication.getPrincipal().toString();
                    final CharSequence rawPassword  =   authentication.getCredentials().toString();

                    if( passwordEncoder.matches(rawPassword, user.getPassword())){

                        log.info("User has been authenticated {}", username);
                        return Mono.just( new UsernamePasswordAuthenticationToken(username, user.getPassword(), user.getAuthorities()) );
                    }

                    //This constructor can be safely used by any code that wishes to create a UsernamePasswordAuthenticationToken, as the isAuthenticated() will return false.
                    return Mono.just( new UsernamePasswordAuthenticationToken(username, authentication.getCredentials()) );
                });
    };
}

Hope this helps somebody.

  • Related