20

What is the correct way to add my custom implementation of UserDetailsService (which uses Spring Data JPA) to Spring Boot app?

public class DatabaseUserDetailsService implements UserDetailsService {

    @Inject
    private UserAccountService userAccountService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userAccountService.getUserByEmail(username);
        return new MyUserDetails(user);
    }

}


public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {

    public User findByEmail(String email);

}



@Service
public class UserAccountService {

    @Inject
    protected UserRepository userRepository;

    public User getUserByEmail(String email) {
        return userRepository.findByEmail(email);
    }

}


@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.sample")
@EntityScan(basePackages = { "com.sample" })
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    ...

    @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
    protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .antMatchers("/").hasRole("USER")
                    .and()
                .formLogin()
                    .loginPage("/login")
                    .permitAll()
                    .and()
                .logout()
                    .permitAll();
        }


    }

    @Order(Ordered.HIGHEST_PRECEDENCE + 10)
    protected static class AuthenticationSecurity extends GlobalAuthenticationConfigurerAdapter {

        @Inject
        private UserAccountService userAccountService;

        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService());
        }

        @Bean
        public UserDetailsService userDetailsService() {
            return new DatabaseUserDetailsService();
        }

    }

}


@Entity
public class User extends AbstractPersistable<Long> {

    @ManyToMany
    private List<Role> roles = new ArrayList<Role>();

    // getter, setter

}


@Entity
public class Role extends AbstractPersistable<Long> {

    @Column(nullable = false)
    private String authority;

    // getter, setter

}

I cannot start app beacouse I get (full exception here http://pastebin.com/gM804mvQ)

Caused by: org.hibernate.AnnotationException: Use of @OneToMany or @ManyToMany targeting an unmapped class: com.sample.model.User.roles[com.sample.model.Role]
    at org.hibernate.cfg.annotations.CollectionBinder.bindManyToManySecondPass(CollectionBinder.java:1134)

When I configure my ApplicationSecurity with auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery("...).authoritiesByUsernameQuery("...") everything is working including JPA and Spring Data repositories.

igo
  • 6,359
  • 6
  • 42
  • 51
  • The problem is the JPA mapping. Please add the User and Role entities(classes). – Evgeni Dimitrov Jul 13 '14 at 14:35
  • Entities added. Anyway, I don't think entities are problem because JPA is working fine without my UserDetailsService. – igo Jul 13 '14 at 15:21
  • Nevertheless I think Evgeni is right. The error is a mapping error. Maybe you can share a complete project? – Dave Syer Jul 14 '14 at 05:02
  • @DaveSyer, I tried to minimise it here https://github.com/igo/spring-boot-userdetails. Still not able to run however I got another exception NPE - UserAccountService in DatabaseUserDetailsService is null. Please try, you might know how to use custom UserDetailsService :) – igo Jul 14 '14 at 15:54
  • Your app seems to work for me (once I added @Configuration to the AuthenticationSecurity). How do I break it? – Dave Syer Jul 14 '14 at 16:29
  • Hi @DaveSyer, i want to build same application with angular, can you give any good reference for it. – Pramod Gaikwad Jan 01 '16 at 12:35

2 Answers2

11

Your app seems to work for me (once I added @Configuration to the AuthenticationSecurity). Here's another working sample of a simple app with JPA UserDetailsService in case it helps: https://github.com/scratches/jpa-method-security-sample

Dave Syer
  • 56,583
  • 10
  • 155
  • 143
  • 1
    Thanks, that helped to move forward. But now I get `o.s.s.w.a.www.BasicAuthenticationFilter : Authentication request for failed: org.springframework.security.authentication.InternalAuthenticationServiceException: failed to lazily initialize a collection of role: com.sample.model.User.roles, could not initialize proxy - no Session`. Yet I did not find any examples which load also roles with lazy fetching. I updated my github sample. – igo Jul 14 '14 at 17:53
  • Lazy roles sound like a bad idea (you always need to check the authorization after authentication), but that just looks like you don't have a transaction. Maybe the `UserDetailsService` boils be transactional *and* load the roles, killing two birds with one stone, as it were? – Dave Syer Jul 14 '14 at 20:35
  • @DaveSyer Hello, I can't get what you mean. I have the same error as the OP (`failed to lazily initialize a collection of role`). Should I have to add `fetch=FetchType.EAGER` to `com.sample.model.User.roles` or is there a less heavy way? Thank you! – bluish Jan 08 '15 at 15:24
  • Ideally you should not use JPA for user details. If you must then fetch the roles inside the `UserDetailsService` (and make it transactional). – Dave Syer Jan 08 '15 at 18:22
4

You can also follow this blog to implement custom user details service.

This example shows how you can send bean to userdetails service for injection.

  1. Autowire the Repository in the WebSecurityConfigurer
  2. Send this bean as a parameter to the user details service by a parameterized constructor.
  3. assign this a private member and use to load users from database.
Ekansh Rastogi
  • 2,418
  • 2
  • 14
  • 23