Issue
I've seen a lot of examples where a user creates a custom UserDetailsService
in order to override the loadUserByUsername
method and return a custom implementation of a UserDetails
object.
This was done previously with sth like this
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
Now with the new version I'm confused on how to do this
I created a Bean
and used the JdbcUserDetailsManager
, I can configure my custom queries for users and authorities tables
@Bean
public UserDetailsManager userDetailsManager(DataSource dataSource) {
String usersByUsernameQuery = "select username, password, enabled from tbl_users where username = ?";
String authsByUserQuery = "select username, authority from tbl_authorities where username = ?";
JdbcUserDetailsManager userDetailsManager = new JdbcUserDetailsManager(dataSource);
userDetailsManager.setUsersByUsernameQuery(usersByUsernameQuery);
userDetailsManager.setAuthoritiesByUsernameQuery(authsByUserQuery);
return userDetailsManager;
}
but how to return a custom UserDetails
object with an extra field, e.g. an email with the new version?
Solution
OK after many tries what I did was to remove completely JdbcUserDetailsManager
stuff from my custom SecurityConfig
class and I created a custom UserDetailsService
and custom UserDetails
class and it worked.
So security config class had no code regarding the authentication of the users.
I was very confused because I thought that somehow I had to create a @Bean
inside the config class, implement the authentication myself and in general that all this authentication code had to be done inside the config class, but it worked with this approach.
@Service
public class MyCustomUserDetailsService implements UserDetailsService {
@Autowired
UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User Not Found with username: " + username);
}
return MyUserDetails.build(user);
}
}
And the details class
public class MyUserDetails implements UserDetails {
private String username;
private String firstName;
private String lastName;
@JsonIgnore
private String password;
private Collection<? extends GrantedAuthority> authorities;
public MyUserDetails(String username, String firstName, String lastName, String password,
Collection<? extends GrantedAuthority> authorities) {
this.username = username;
this.firstName = firstName;
this.lastName = lastName;
this.password = password;
this.authorities = authorities;
}
public static MyUserDetails build(User user) {
List<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getAuthority()))
.collect(Collectors.toList());
return new MyUserDetails(
user.getUsername(),
user.getFirstName(),
user.getLastName(),
user.getPassword(),
authorities);
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
MyUserDetails user = (MyUserDetails) o;
return Objects.equals(username, user.username);
}
}
Also check Spring Security Architecture
Answered By - Apostolos
Answer Checked By - Pedro (JavaFixing Volunteer)