mik3fly-4steri5k
mik3fly-4steri5k

Reputation: 862

Create and user loadUserByEmail instead of loadUserByUsername

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

Solution

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

Answers (1)

TwiN
TwiN

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

Related Questions