Volodymyr Levytskyi
Volodymyr Levytskyi

Reputation: 437

How to programmatically authenticate `User` with spring security and use my `UserDetailsServie` implementation?

I want to use spring security UserDetailsService together with spring data jpa CrudRepository interface. Principal is represented by User entity which implements UserDetails:

@Entity
public class User implements UserDetails {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String username;
    private String password;

    //other methods of UserDetails 

I have spring-security.xml config file:

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security-4.0.xsd">

    <authentication-manager>
        <!-- refers to spring data repository bean org.baeldung.SpringDataAuditDemo.dao.repos.UserDao -->
        <authentication-provider user-service-ref="userDao" />
    </authentication-manager>

</beans:beans>

It refers to bean userDao of type UserDetailsService and CrudRepository:

public interface UserDao extends CrudRepository<User, Long>, UserDetailsService {

    // @Override    
    @Query(value = "select u from User u where u.username=:username")
    public UserDetails loadUserByUsername(@Param("username") String username) throws UsernameNotFoundException;
}

Now I want to authenticate User object programmatically in jUnit tests.

I hope to run UserDao.loadUserByUsername method when authenticating User. For that I wanted to use org.springframework.security.provisioning.UserDetailsManager but it implements UserDetailsService interface as well as my UserDao. Therefore I got exception that two beans of same type exists!!!

Then I tried to use UserDao instead of UserDetailsManager but my UserDao doesn't authenticate User, but only loads it from db. In this case spring-security.xml is not needed.

How to programmatically authenticate User with spring security default DaoAuthenticationProvider and use my UserDetailsServie implementation?

EDIT

When I tried to autowire my UserDao with @Qualifier and UserDetailsManager without it, I got this exception :

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.security.provisioning.UserDetailsManager] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1118)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:967)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:862)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:494)
... 28 more

Upvotes: 0

Views: 910

Answers (1)

Rob Winch
Rob Winch

Reputation: 21720

You can programmatically authenticate against your custom UserDetailsService by using the AuthenticationManager. For example, the following would work in a JUnit Test:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class SecurityConfigTests {

    @Autowired
    private AuthenticationManager manager;

    @Before
    public void setup() {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        Authentication user = manager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"));
        context.setAuthentication(user);

        SecurityContextHolder.setContext(context);
    }
    ...
}

Any time you have multiple beans of the same type you can use a Qualifier annotation. This means if you are getting an exception that two beans of the same type exists, you can specify which one you want. For example, if you want a UserDetailsService bean by the name of userDao, you can use:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

...

@Qualifier("userDao")
@Autowired
UserDetailsService userDao;

Upvotes: 1

Related Questions