I have this problem implementing a custom login authentication using SpringBoot and SpringBoot-Security. I made a Bitbucket repository as reference for this thread (within CustomSecuringWeb branch). Before anything else, most of the comments here follows the Securing a Web Application tutorial.
The thing is, I was curious as how could the authentication data is now from the database instead of just memory data (which is very common in production line applications).
Throughout the process I made two attempts (though both attempts are located on the same branch - my bad for that).
UserDetailsService
implementationAbstractUserDetailsAuthentictionProvider
implementationI don't know where the problem lies, but upon checking the console log both returns that the persistence(even the repository) DI on each custom class where null.
The question is how could I make both attempts working. And (possibly) which one of the two attempts is better than the other.
First of all the two approaches are used for different purpose and not interchangeable.
Case 1:
UserDetailsService
is used purely as DAO to locate user information by your authentication and based on that info authenticate user, no authentication should be done within UserDetailsService
, just data access.
Specifications clearly mention that. This is what you are looking for.
Case2:
AuthentictionProvider
on the other hand is used for providing custom method of authentication, for example if you want to custom authenticate on fields other than login and password you may do that by implementing AuthentictionProvider
and supplying this object to your AuthenticationManagerBuilder
. I do not think this is what you want to do in you project. You are just looking to implement your authentication based on users stored in database using login and password which is default way.
In above Case 1 where you implemented just UserDetailsService
, instance of AuthentictionProvider
was created for you in AuthenticationManager
by the container and it was DaoAuthenticationProvider
since you supplied UserDetailsService which is nothing else but DAO in your system that is used to retrive user for authentication.
Now to your implementation, in your configuration instead of :
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(new AdminSecurityService());
auth.authenticationProvider(new AdminSecurityAuthenticationProvider());
}
do something like this
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
and your CustomUserDetailsService
has to implement org.springframework.security.core.userdetails.UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final AdminRepository userRepository;
@Autowired
public CustomUserDetailsService(AdminRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Admin user = userRepository.findByLogin(username);
if (user == null) {
throw new UsernameNotFoundException(String.format("User %s does not exist!", username));
}
return new UserRepositoryUserDetails(user);
}
private final static class UserRepositoryUserDetails extends Admin implements UserDetails {
private static final long serialVersionUID = 1L;
private UserRepositoryUserDetails(User user) {
super(user);
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.createAuthorityList("ROLE_USER");
}
@Override
public String getUsername() {
return getLogin();//inherited from user
}
@Override
public boolean isAccountNonExpired() {
return true;//not for production just to show concept
}
@Override
public boolean isAccountNonLocked() {
return true;//not for production just to show concept
}
@Override
public boolean isCredentialsNonExpired() {
return true;//not for production just to show concept
}
@Override
public boolean isEnabled() {
return true;//not for production just to show concept
}
//getPassword() is already implemented in User.class
}
}
of course implementation is up to you but you have to be able to provide user password, and rest of the methods in that interface based on the retrieved user (Admin.class in your case). Hope it helps. I did not run this example so if I made some typos go ahead and ask if something does not work. I would also get rid of that 'AuthentictionProvider' from your project if you don't need it.
Here you got documentation:http://docs.spring.io/spring-security/site/docs/4.0.0.RC1/reference/htmlsingle/#tech-userdetailsservice
After comments:
You can set PasswordEncoder
in your configure method without too much hassle just do:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
@Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
You can do that because you get access to AbstractDaoAuthenticationConfigurer
returned from auth.userDetailsService(userDetailsService)
and it allows you to configure DaoAuthenticationProvider
, which is your provider of choice when you choose to use UserDetailsService
.
You are right PasswordEncoder
is set in AuthenticationProvider
but you do not have to
implement AuthenticationProvider
just use convineince object that is returned from auth.userDetailsService(userDetailsService)
and set your encoder on that object which will pass it to AuthenticationPriovider
in your case DaoAuthenticationProvider
that was already created for you.
Just like roadrunner mentioned in the comment you very rarely need to implement your own AuthenticationProvider
usually most of authentication configuration adjustments can be done with the use of AbstractDaoAuthenticationConfigurer
which as mentioned above is returned from auth.userDetailsService(userDetailsService)
.
"And if I ever wanted to add a password encryption. And if I ever wanted to do other authentication (like checking if the user is locked, active, user is still logged-in, etc. [excluding password hashing]) will use the AuthenticationProvider."
No this is done for you as part of standard authentication mechanism
http://docs.spring.io/autorepo/docs/spring-security/3.2.0.RELEASE/apidocs/org/springframework/security/core/userdetails/UserDetails.html
If you look at the interface UserDetails
you will see that if any of the above methods returns false authentication will fail.
Implementing AuthenticationProvider
is really needed in very nonstandard cases. All standard stuff is pretty much covered by the framework .
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With