使用 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);
};
}
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);
};
}
过滤器
@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()) );
});
};
}
希望这对某人有所帮助。
经过大约 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);
};
}
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);
};
}
过滤器
@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()) );
});
};
}
希望这对某人有所帮助。