简体   繁体   中英

Spring Security 3.1 + JPA - Null pointer exception

I am a Spring newbie and facing issues with Spring Security.

I am trying to implement a custom UserDetailsService for user retrieval and getting a null pointer exception when I access the UserService object. I am autowiring this object. The autowirng is working fine when done on other Controller and Service methods but for some reason, its not working here and hence I am getting the null pointer exception when the autowired object(UserService) is accessed.

I would really appreciate help on this.

Exception Stack trace:

java.lang.NullPointerException
java.lang.NullPointerException
at com.contact.list.service.CustomUserDetailsService.loadUserByUsername(CustomUserDetailsService.java:37)
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:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:225)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:927)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at com.springsource.insight.collection.tcserver.request.HttpRequestOperationCollectionValve.invoke(HttpRequestOperationCollectionValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1001)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:585)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)

CustomUserDetailsService Class:

    package com.contact.list.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.contact.list.form.Role;
import com.contact.list.repository.UserRepository;

@Service
@Transactional(readOnly = true)
public class CustomUserDetailsService implements UserDetailsService {

@Autowired
private UserService userService;


public UserDetails loadUserByUsername(String username)
        throws UsernameNotFoundException {

    try{
        com.contact.list.form.User domainuser =     userService.findByUsername(username);


        boolean enabled = true;
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;

        return new User(domainuser.getUsername(),
                        domainuser.getPassword().toLowerCase(),
                        enabled,accountNonExpired,
                        credentialsNonExpired,
                        accountNonLocked,
                        getAuthorities(domainuser.getRoles())
                );


    }catch (Exception e){
        System.out.println(e);
        e.printStackTrace();
        throw new RuntimeException(e);
    }


}

public Collection<? extends GrantedAuthority> getAuthorities(List<Role> roles){

    List<GrantedAuthority> authList = getGrantedAuthorities(getroles(roles));
    return authList;
}

public static List<GrantedAuthority> getGrantedAuthorities(List<String> userroles){

    List<GrantedAuthority> authorities  = new ArrayList<GrantedAuthority>();
    for(String userrole:userroles){
        authorities.add(new SimpleGrantedAuthority(userrole));
    }
    return authorities;
}

public List<String> getroles(List<Role> roles){

    List<String> userroles = new ArrayList<String>();

    for (Role role : roles){

     if(role.getRole() == 1){
         userroles.add("ROLE_USER");
     }
     if(role.getRole() == 2){
         userroles.add("ROLE_ADMIN");
     }

    }

    return userroles;
}




}

UserService Interface:

    package com.contact.list.service;

import java.util.List;

import com.contact.list.form.Contact;
import com.contact.list.form.User;

public interface UserService {

public List<User> findAll();

public void save(User user);

public User findByUsername(String username);
}

UserService Implementation class:

    package com.contact.list.service;

    import java.util.List;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Repository;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import com.contact.list.form.Contact;
    import com.contact.list.form.User;
    import com.contact.list.repository.UserRepository;
    import com.google.common.collect.Lists;


    @Service
    @Repository
    @Transactional
    public class UserServiceImpl implements UserService {

@Autowired
private UserRepository userrepository;

public void save(User user) {

    userrepository.save(user);
}

@Transactional(readOnly=true)
public List<User> findAll() {
    return Lists.newArrayList(userrepository.findAll());
}

public User findByUsername(String username){

    return userrepository.findByUsername(username);

}

    }

Userrepository:

    package com.contact.list.repository;

import org.springframework.data.repository.CrudRepository;

import com.contact.list.form.User;

public interface UserRepository extends CrudRepository<User, Long> {

User findByUsername(String username);

}

User Class:

    package com.contact.list.form;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;


@Entity
@Table(name = "USER_TBL")
public class User {



@Column(name = "FIRST_NAME")
private String firstName;

@Column(name = "LAST_NAME")
private String lastName;

@Column(name = "EMAIL")
private String email;

@Id
@Column(name = "USERID")
private String username;

@Column(name = "PASSWORD")
private String password;

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Role> roles = new ArrayList<Role>();


public String getFirstName() {
    return firstName;
}
public void setFirstName(String firstName) {
    this.firstName = firstName;
}
public String getLastName() {
    return lastName;
}
public void setLastName(String lastName) {
    this.lastName = lastName;
}
public String getUsername() {
    return username;
}
public void setUsername(String username) {
    this.username = username;
}
public String getPassword() {
    return password;
}
public void setPassword(String password) {
    this.password = password;
}
public String getEmail() {
    return email;
}
public void setEmail(String email) {
    this.email = email;
}
public List<Role> getRoles() {
    return roles;
}
public void setRoles(List<Role> roles) {
    this.roles = roles;
}


}

web.xml:

<!-- Spring Security Configuration --> 
<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>
     /WEB-INF/spring/root-context.xml
     /WEB-INF/spring-security.xml
   </param-value>
</context-param>


<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- Processes application requests -->
<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
       <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
           /WEB-INF/spring/appServlet/servlet-context.xml
        </param-value>
       </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

servlet-context.xml:

<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />

<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />

<interceptors>
    <beans:bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
</interceptors>

<beans:bean id="themeSource" class="org.springframework.ui.context.support.ResourceBundleThemeSource"/>

<beans:bean id="themeResolver" class="org.springframework.web.servlet.theme.CookieThemeResolver"/>




<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/views/" />
    <beans:property name="suffix" value=".jsp" />
    <beans:property name="requestContextAttribute" value="requestContext"/>
</beans:bean>

<context:component-scan base-package="com.contact.list" />

<beans:bean id = "myDataSource" class = "org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <beans:property name="driverClassName" value = "org.postgresql.Driver"/>
    <beans:property name="url" value = "jdbc:postgresql://localhost:5432/hibernatedb"/>
    <beans:property name="username" value = "postgres"/>
    <beans:property name="password" value = "password"/>
</beans:bean>


 <!-- JPA Config -->

 <beans:bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <beans:property name="entityManagerFactory" ref="emf"/>
 </beans:bean>

 <tx:annotation-driven transaction-manager="transactionManager" />

 <beans:bean id = "emf" class = "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
   <beans:property name = "dataSource" ref = "myDataSource"/>
   <beans:property name = "jpaVendorAdapter">
     <beans:bean class = "org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
   </beans:property>
   <beans:property name = "packagesToScan" value = "com.contact.list.form" />
   <beans:property name="jpaProperties">
     <beans:props>
      <beans:prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</beans:prop>
      <beans:prop key = "hibernate.show_sql">true</beans:prop>
      <beans:prop key="hibernate.hbm2ddl.auto">update</beans:prop>
     </beans:props>
   </beans:property>
 </beans:bean>

 <beans:bean id = "passwordEncoder" class = "org.springframework.security.authentication.encoding.Md5PasswordEncoder"/>



 <context:annotation-config/>

 <!-- JPA Config -->

 <!-- JPA Repository Abstraction Config -->

 <jpa:repositories base-package="com.contact.list.repository"  entity-manager-factory-ref="emf" transaction-manager-ref="transactionManager"/>

spring-security.xml

 <http auto-config="true" use-expressions="true">
 <intercept-url pattern="/home*" access="hasRole('ROLE_USER')"/>
     <form-login login-page="/login" default-target-url="/home" authentication-failure-url="/loginfailed" />
     <logout logout-success-url="/logout" />
     </http>


     <authentication-manager>
     <authentication-provider user-service-ref = "customUserDetailsService">
     <password-encoder ref = "passwordEncoder"/>
     </authentication-provider>
     </authentication-manager>

     <beans:bean id="customUserDetailsService" class="com.contact.list.service.CustomUserDetailsService"/>

     <beans:bean id = "passwordEncoder" class = "org.springframework.security.authentication.encoding.Md5PasswordEncoder"/>

     </beans:beans>

So, to write it as an answer, what happened here is that in a typical Spring web application, you have the application context (Spring terminology, here is where beans live ) that belongs to the Spring MVC Servlet.

This one is defined in the web.xml as /WEB-INF/spring/appServlet/servlet-context.xml .

On the other hand, the Spring Security Filter cannot access such context, it can only access the Root Context. The Root Context is loaded with the ContextLoaderListener and the beans defined in:

<param-value>
     /WEB-INF/spring/root-context.xml
     /WEB-INF/spring-security.xml
</param-value>

As a Root Context was defined, the Servlet Context is built as a child of the Root Context. This enables the Servlet Context to access the beans that live in its parent, but the other way is not possible.

Then, basic beans as the DataSource, the Persistence system (JPA) and Services were defined in the Servlet Context. The security system was trying to access a service bean (the User Service) without success as this service was located in the Servlet Context instead of the Root Context (where Spring Security lives), hence the Null Pointer Exception.

Solution: Move the Datasource, JPA and Services beans to the Root Context, and leave the Servlet Context for Spring MVC Controllers and Views.

You should remove the @Repository annotation from the UserServiceImpl class. Otherwise Spring tries to instantiates the bean twice (because of the @Service and the @Repository annotation) ...

@Service
@Repository
@Transactional
public class UserServiceImpl implements UserService {

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM