Reputation: 862
I m looking a way to create and use my own method to load user in Java Spring Security.
I would like to retrieve my user not by UserName but by email.
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
@Autowired
UserRepository userRepository;
private static final Logger logger = LoggerFactory.getLogger(UserDetailsService.class);
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
Optional<User> oUser = userRepository.findByUserName(userName);
if(!oUser.isPresent()){
throw new UsernameNotFoundException(userName);
} else {
logger.info("user found");
}
User user = oUser.get();
return this.buildUserDetails(user);
}
But loadUserByUsername is called in this method in class DaoAuthenticationProvider. How can i override this behavior ?
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
UserDetails loadedUser;
try {
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
} catch (UsernameNotFoundException var6) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, (Object)null);
}
throw var6;
} catch (Exception var7) {
throw new InternalAuthenticationServiceException(var7.getMessage(), var7);
}
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
}
My WebSecurityConfig here with customDaoAuthenticationProvider
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
UserDetailsServiceExtended userDetailsServiceExtended;
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
protected TokenAuthenticationService tokenAuthenticationService;
@Value("${web.security.debug}")
private boolean debug;
public WebSecurityConfig() { super(false);}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/api/**").authenticated();
http
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint);
http
.addFilterBefore(customEmailPasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService), CustomEmailPasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Bean
public CustomEmailPasswordAuthenticationFilter customEmailPasswordAuthenticationFilter() throws Exception {
CustomEmailPasswordAuthenticationFilter filter = new CustomEmailPasswordAuthenticationFilter();
filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
filter.setAuthenticationFailureHandler(authenticationFailureHandler);
filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/api/users/authenticate", "POST"));
filter.setAuthenticationManager(authenticationManager());
return filter;
}
@Bean
public CustomDaoAuthenticationProvider daoAuthenticationProvider() {
CustomDaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider();
authenticationProvider.setPasswordEncoder(this.passwordEncoder);
authenticationProvider.setUserDetailsService(userDetailsServiceExtended);
return authenticationProvider;
}
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
auth.userDetailsService(this.userDetailsServiceExtended).passwordEncoder(this.passwordEncoder);
}
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
Upvotes: 2
Views: 3692
Reputation: 3834
To answer your direct question, retrieveUser
cannot be overriden. Not only is it final
, indicating that it cannot be overriden, it's protected, meaning that you can't access it from outside the org.springframework.security.authentication.dao
package.
Obviously, I wouldn't be answering without a solution.
Spring's biggest forte is its layer of abstraction. A lot of people misunderstand its use, but in simple terms, as long as a class extends the same abstract class as the default class, it can be replaced by using the @Bean
annotation.
So in your case, DaoAuthenticationProvider
extends AbstractUserDetailsAuthenticationProvider
. Following that logic, as long as we make a class that extends AbstractUserDetailsAuthenticationProvider
and configure it accordingly, we should be able to replace DaoAuthenticationProvider
.
Let's call that class CustomDaoAuthenticationProvider
.
public class CustomDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
}
Copy everything from DaoAuthenticationProvider here.
The only difference is that the constructor and the class name should be renamed from DaoAuthenticationProvider
to CustomDaoAuthenticationProvider
.
If you're using a different version of Spring, you should be able to just navigate to the source code of DaoAuthenticationProvider
from your IDE.
Now you'll need to create a configuration class, let's call it SecurityConfiguration
:
@Configuration
@WebSecurity // optional?
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userService; // can be replaced by whatever service implements UserDetailsService
@Bean
public CustomDaoAuthenticationProvider daoAuthenticationProvider() {
CustomDaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider();
authenticationProvider.setPasswordEncoder(passwordEncoder());
authenticationProvider.setUserDetailsService(userService);
System.out.println("Using my custom DaoAuthenticationProvider");
return authenticationProvider;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
// ...
}
The following configuration should tell Spring to use CustomDaoAuthenticationProvider
instead of DaoAuthenticationProvider
.
I briefly tested it on my end, and it should work. From there, you can modify retrieveUser
directly in CustomDaoAuthenticationProvider
as you wish.
Good luck!
Upvotes: 3