Dmitrij Kostyushko
Dmitrij Kostyushko

Reputation: 656

How to provide multiple Authentication providers with java configuration of spring security

I store information about users in seperated tables owners,employees,users i am trying to use java configuration in spring security.

I have created three diferent authentication providers for each user type, but only the Users Provider is being triggered. I have read the spring security docs and the only way to do this seems to be is create class with multiple embedded classes extended from WebSecurityConfigurerAdapter but i don't want to do it this way because it requires a lot of duplicating code, is there any other way

I tryed to use the simple userDetailService inside which i send request to all tables in databese but still there is not results, only one query is buing executed and nothing, the only responce i get is:

2016-02-09 23:06:25.976 DEBUG 8780 --- [nio-8080-exec-1] .s.a.DefaultAuthenticationEventPublisher : No event was found for the exception org.springframework.security.authentication.InternalAuthenticationServiceException

2016-02-09 23:06:25.976 DEBUG 8780 --- [nio-8080-exec-1] o.s.s.w.a.www.BasicAuthenticationFilter : Authentication request for failed: org.springframework.security.authentication.InternalAuthenticationServiceException: No entity found for query; nested exception is javax.persistence.NoResultException: No entity found for query

But i never throw any exception!! And the most strange is that i can see in the debugger how the execution rapidly stops right after em.createQuery(..).getSingleResult().. and that's it, nothing more! There is no return statement no exception nothing, wtf!!

This is part of my current configuration:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
            .authenticationProvider(createAuthenticationProvider(employeeDetailService()))
            .authenticationProvider(createAuthenticationProvider(ownerDetailsService()))
            .authenticationProvider(createAuthenticationProvider(userDetailsService()));
}
 @Bean
    public OwnerDetailsService ownerDetailsService() {
        return new OwnerDetailsService();
    }

    @Bean
    public EmployeeDetailServiceImpl employeeDetailService() {
        return new EmployeeDetailServiceImpl();
    }

    @Bean
    public UserDetailsServiceImpl userDetailsService() {
        return new UserDetailsServiceImpl();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(6);
    }

    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return new MySimpleUrlAuthenticationSuccessHendler();
    }



    private AuthenticationProvider createAuthenticationProvider(UserDetailsService service) {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(service);
    provider.setPasswordEncoder(passwordEncoder());
    provider.setHideUserNotFoundExceptions(true);
    return provider;
}

User detail services:

  @Service
    public abstract class CustomUserDetailService implements UserDetailsService{

        @Autowired
        IDBBean dao;

        protected CustomUserDetails getUser(GetUserByNameFunction function, String name) {
            return createUser(function.get(name));
        }

        protected CustomUserDetails createUser(Authenticational user) {
            return new CustomUserDetails(user, getAuthorities(user.getAuthority()));
        }

        protected List<GrantedAuthority> getAuthorities(String authority) {
            return Collections.singletonList(new SimpleGrantedAuthority(authority));
        }
    }

Implementations

    public class EmployeeDetailServiceImpl extends CustomUserDetailService {

        @Override
        public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
            return super.getUser(dao::getEmployeeByEmail, email);
        }
    }

    public class OwnerDetailsService extends CustomUserDetailService {

        @Override
        public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
            return super.getUser(dao::getOwnerByEmail, email);
        }
    }

public class UserDetailsServiceImpl extends CustomUserDetailService {


    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        return super.getUser(dao::getUserByEmail, userName);
    }
}

Custom user details:

private Long id;
    private String userEmail;


    public CustomUserDetails(Authenticational user,
                             Collection<? extends GrantedAuthority> authorities) {
        super(
                user.getName(),
                user.getPassword().toLowerCase(),
                user.isEnabled(),
                true,
                true,
                true,
                authorities);
        upadateValues(user);
    }

    private void upadateValues(Authenticational user) {
        this.id = user.getId();
        this.userEmail = user.getEmail();
    }

Upvotes: 4

Views: 7547

Answers (3)

MOHAMED FAROUK
MOHAMED FAROUK

Reputation: 11

this is an example I'v done to provide multi-auth for my application which have two diffirent user : admin and client .

ps: the admin and the client are two diffirent model.

public class CustomUserDetails implements UserDetails{

private Admin admin;
private Client client;


public CustomUserDetails(Admin admin) {
    this.admin = admin;
}
public CustomUserDetails(Client client) {
    this.client = client;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return null;
}

@Override
public String getPassword() {
    if((admin != null)&&(client==null)) {
        return admin.getPassword();
    }
    else {
        return client.getPassword();
    }
}


@Override
public String getUsername() {
    if((admin != null)&&(client==null)) {
        return admin.getEmail();
    }
    else {
        return client.getMail();
    }
}

@Override
public boolean isAccountNonExpired() {
    return true;
}

@Override
public boolean isAccountNonLocked() {
    return true;
}

@Override
public boolean isCredentialsNonExpired() {
    return true;
}

@Override
public boolean isEnabled() {
    return true;
 }
}

the CustomUserDetailsService class :

public class CustomUserDetailsService implements UserDetailsService {

@Autowired
AdminRepository adminRepo;
@Autowired
ClientRepository clientRepo;

@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
    Admin admin = adminRepo.findByEmail(email);
    Client client = clientRepo.findByEmail(email);
    if((admin == null)&&(client==null)) {
        throw new UsernameNotFoundException("User not found");
    }
    else if((admin != null)&&(client==null)) {
        return new CustomUserDetails(admin);
    }
    else {
        return new CustomUserDetails(client);
    }
  }
}

Upvotes: 0

JIMI
JIMI

Reputation: 449

Just to clarify something from the other answer:

Your authentication providers are stored in a list inside ProviderManager that iterates your authentication request through them. If your authentication provider throws AuthenticationException (BadCredentialsException extends AuthenticationException), then the ProviderManager will try another provider. If you set the hideUserNotFoundExceptions property, then it will also wrap and ignore UsernameNotFoundException and try another provider in this case too.

If I were you I would start by placing a debugging point inside ProviderManager's authenticate method. From there you can find out why the other authentication providers are not being called for their authenticate method.

Also I would think about having only one authentication provider with one UserDetailsService. It seems to me that you are doing a lot of complex not really needed operations like passing function to your abstract implementation when all you could do would be to have one UserDetailsService that would ask all your DAOs for a user. Which is basically what you're trying to accomplish but minus 2 authentication providers, minus 1 abstract class and minus 2 UserDetailsService implementations.

Upvotes: 8

nagendra.c
nagendra.c

Reputation: 41

Spring Security will not try other authentication providers if a provider throws an AccountStatusException or if a UserDetailsService throws a UserNameNotFoundException or any other AuthenticationException

If you want other providers to be tried, then the loadUserByUserName methods of your UserDetailsServiceImpl and OwnerDetailsService should not throw the UserNameNotFound exception.

You should decide if you either want to return a dummy anonymous UserDetails object that will be used exclusively for fallback or some other mechanism to not throw the exception when a user is not available in your UserDetailsService implementation

Upvotes: 0

Related Questions