7

I'm trying to map the users in my database to the Spring Security users, but without much luck. My UserServiceImpl is as follows (the autowiring normally works fine when I'm calling it through a servlet, but throws a null pointer when used in Spring Security...

@Service("userService")
@Transactional
public class UserServiceImpl implements UserService, UserDetailsService {

    protected static Logger logger = Logger.getLogger("service");

    @Autowired
    private UserDAO userDao;

    public UserServiceImpl() {
    }

    @Transactional
    public User getById(Long id) {
        return userDao.getById(id);
    }

    @Transactional
    public User getByUsername(String username) {
        return userDao.getByUsername(username);
    }

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {

        UserDetails user = null;
        try {
            System.out.println(username);
            User dbUser = getByUsername(username);

            user = new org.springframework.security.core.userdetails.User(
                    dbUser.getUsername(), dbUser.getPassword(), true, true,
                    true, true, getAuthorities(dbUser.getAccess()));
        } catch (Exception e) {
            e.printStackTrace();
            logger.log(Level.FINE, "Error in retrieving user");
            throw new UsernameNotFoundException("Error in retrieving user");
        }

        // Return user to Spring for processing.
        // Take note we're not the one evaluating whether this user is
        // authenticated or valid
        // We just merely retrieve a user that matches the specified username
        return user;
    }

    public Collection<GrantedAuthority> getAuthorities(Integer access) {
        // Create a list of grants for this user
        List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>(2);

        // All users are granted with ROLE_USER access
        // Therefore this user gets a ROLE_USER by default
        logger.log(Level.INFO, "User role selected");
        authList.add(new GrantedAuthorityImpl("ROLE_USER"));

        // Check if this user has admin access
        // We interpret Integer(1) as an admin user
        if (access.compareTo(1) == 0) {
            // User has admin access
            logger.log(Level.INFO, "Admin role selected");
            authList.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
        }

        // Return list of granted authorities
        return authList;
    }
}

I get the following exception (the first line is the System.out)

dusername
java.lang.NullPointerException
    at org.assessme.com.service.UserServiceImpl.getByUsername(UserServiceImpl.java:40)
    at org.assessme.com.service.UserServiceImpl.loadUserByUsername(UserServiceImpl.java:50)
    at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:81)
    at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:132)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
    at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:94)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:194)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:173)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:259)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:859)
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
    at java.lang.Thread.run(Thread.java:722)

So it looks like my userDao is not autowiring correctly, but it works fine when I call the service layer from a servlet, just apparently not when using Spring-Security.

Line 40 refers to return userDao.getByUsername(username);

Does anyone have any ideas how I can get the userDao to be populated through @autowired? As I say, it works fine when I call it through a servlet, just not when trying to use spring-security.

Is there any easier way I can map users and passwords in Spring-security?

My security-app-context is as follows...

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                    http://www.springframework.org/schema/security 
                    http://www.springframework.org/schema/security/spring-security-3.1.xsd"
                    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx">
<context:annotation-config />
<context:component-scan base-package="org.assessme.com." />


    <http pattern="/static/**" security="none" />
    <http use-expressions="true">
        <intercept-url pattern="/login" access="permitAll" />
        <intercept-url pattern="/*" access="isAuthenticated()" />
        <!-- <intercept-url pattern="/secure/extreme/**" access="hasRole('supervisor')" 
            /> -->
        <!-- <intercept-url pattern="/listAccounts.html" access="isAuthenticated()" 
            /> -->
        <!-- <intercept-url pattern="/post.html" access="hasAnyRole('supervisor','teller')" 
            /> -->
<!--        <intercept-url pattern="/*" access="denyAll" /> -->
        <form-login />
        <logout invalidate-session="true" logout-success-url="/"
            logout-url="/logout" />
    </http>

    <authentication-manager>
        <authentication-provider user-service-ref="UserDetailsService">
          <password-encoder ref="passwordEncoder"/>
        </authentication-provider>
</authentication-manager>
<context:component-scan base-package="org.assessme.com" /><context:annotation-config />
<beans:bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

<beans:bean class="org.assessme.com.service.UserServiceImpl" id="UserDetailsService" autowire="byType"/>


</beans:beans> 

I guess my question is, why is my userDao @Autowired not working with spring-security, yet it works fine when used in a servlet to return the user object? For example, the following servlet works fine...

Why is my autowiring (as it throws a NPE) not working when it's going through spring-security, yet it works fine when being called from a servlet?

EDIT :- added

But now I get

ERROR: org.springframework.web.context.ContextLoader - Context initialization failed
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 9 in XML document from ServletContext resource [/WEB-INF/spring/security-app-context.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 9; columnNumber: 30; cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'context:annotation-config'.
David
  • 19,577
  • 28
  • 108
  • 128

4 Answers4

8

You care creating your service through bean declaration:

<beans:bean class="org.assessme.com.service.UserServiceImpl" id="UserDetailsService"/>

therefore you need to configure it as eligible for autowiring by setting property autowire. Default is No. The configuration will look like:

<beans:bean class="org.assessme.com.service.UserServiceImpl" id="UserDetailsService"  autowire="byType" />

additionally you could indicate that this bean will participate in others bean autowire process by configuring property autowire-candidate="true".

Check documentation in order to find out which is the best strategy for your bean have its properties autowired. I personally use byType and constructor, but it depends really on your requirement.

Another solution would be configure default-autowire="true" in the tag beans of your context.

Francisco Spaeth
  • 23,493
  • 7
  • 67
  • 106
  • I added but I still get a NPE on the same line after restarting Tomcat. Updated question, but thank you for the advice :) – David Jul 07 '12 at 12:45
  • please could you add this to your context xml:`` – Francisco Spaeth Jul 07 '12 at 12:50
  • Also, I already have in my servlet-context, do I need it in both XML files? – David Jul 07 '12 at 12:56
  • you need it in the context you are working. In this case the file you posted on the question. – Francisco Spaeth Jul 07 '12 at 12:59
  • ah, ok got you, I'm getting a different exception now though with the use of context: – David Jul 07 '12 at 13:01
  • but is it autowiring the bean now? – Francisco Spaeth Jul 07 '12 at 13:02
  • ERROR: org.springframework.web.context.ContextLoader - Context initialization failed org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 9 in XML document from ServletContext resource [/WEB-INF/spring/security-app-context.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 9; columnNumber: 30; cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'context:annotation-config'. – David Jul 07 '12 at 13:05
2

I think you just missed the <context:annotation-config/> and <context:component-scan base-package="..."/> in you spring-security context.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • This gives ERROR: org.springframework.web.context.ContextLoader - Context initialization failed...Updated question with new xml, thanks for your help :) – David Jul 07 '12 at 12:59
  • You of course need to declare the XML context namespace at the beginning of the XML file, as shown in http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/htmlsingle/spring-framework-reference.html#beans-annotation-config – JB Nizet Jul 07 '12 at 13:03
  • Ah, I thought I did that with xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" – David Jul 07 '12 at 13:04
  • You didn't tell it where the corresponding XSD can be found in the xsi:schemaLocation – JB Nizet Jul 07 '12 at 13:07
0

I just notice that you've double the tag in your xml file.

<context:component-scan base-package="org.assessme.com" /><context:annotation-config />

Let's try removing it.

bnguyen82
  • 6,048
  • 5
  • 30
  • 39
0

All you have to do is to replace your security-app-context with this:

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

<context:annotation-config>
<context:component-scan base-package="org.assessme.com." />

<http pattern="/static/**" security="none" />
<http use-expressions="true">
    <intercept-url pattern="/login" access="permitAll" />
    <intercept-url pattern="/*" access="isAuthenticated()" />
    <!-- <intercept-url pattern="/secure/extreme/**" access="hasRole('supervisor')" 
        /> -->
    <!-- <intercept-url pattern="/listAccounts.html" access="isAuthenticated()" 
        /> -->
    <!-- <intercept-url pattern="/post.html"     access="hasAnyRole('supervisor','teller')" 
        /> -->
<!--        <intercept-url pattern="/*" access="denyAll" /> -->
    <form-login />
    <logout invalidate-session="true" logout-success-url="/"
        logout-url="/logout" />
</http>
<authentication-manager>
    <authentication-provider user-service-ref="UserDetailsService">
      <password-encoder ref="passwordEncoder"/>
    </authentication-provider>
</authentication-manager>
<context:annotation-config />


You miss typed some parts, which I corrected for you.

Matin Kh
  • 5,192
  • 6
  • 53
  • 77