使用 R2DBC mysql 连接的反应式 spring 安全认证

Reactive spring security authentication using R2DBC mysql connection

经过大约 2 周的阅读无数 tutorials/javadocs 并尝试使用 webflux、R2DBC 和 mysql 组合来使 Spring 安全工作,我终于准备好了承认我被卡住了:(

每个登录请求都被阻止,即使详细信息正确(使用在线 BCrypt 验证器匹配)。

我的理解有差距吗?我错过了什么吗?

任何指点将不胜感激。

ReactiveAuthenticationManager

@Bean
protected ReactiveAuthenticationManager reactiveAuthenticationManager() {

    log.info("Received authentication request");

    return authentication -> {

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

        return authenticator.authenticate(authentication);
    };
}

UserDetailsS​​ervice

@Component
public class UserDetailsService implements ReactiveUserDetailsService {

    @Autowired
    public UserRepository userRepository;

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

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

过滤器

@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();

}

自定义用户

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;
    }

}

回答我自己的问题,以防万一有人遇到同样的问题。 AuthenticationManager接口和AuthenticationProvider接口都有authenticate()方法。我相信正确使用的是 class <? extends AuthenticationProvider>

但是,在没有 ready-made 反应性 AuthenticationProvider 数据库的情况下,我只是做了以下操作:

@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()) );
                });
    };
}

希望这对某人有所帮助。