Announcement Announcement Module
Collapse
No announcement yet.
Get NPE of EntityManager when customizing UserDetailsService with JPA (EclipseLink) Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Get NPE of EntityManager when customizing UserDetailsService with JPA (EclipseLink)

    Hi guys,

    I try to bring Spring (3.1.0) + Spring Security (3.1.0) + JSF (2.1.6) + JPA (EclipseLink 2.3.2) for our project.
    Firstly, I leverage the JdbcDaoImpl to query UserDetails from database and it works fine.
    Then I want to consolidate all authentication db operations into UserDAO which will provide support to user management.
    After customize own UserDetails and UserDetailsService, my application startup successfully. The implementation of UserDetailsService is as following (User is my JPA Entity Class and MyWrappedUserDetails is customized UserDetails).
    Code:
    @Service("myUserDetailsService")
    public class myUserDetailsService implements UserDetailsService {
    
        protected final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    
        private String rolePrefix = "";
        private boolean usernameBasedPrimaryKey = true;
        private boolean enableAuthorities = true;
        private boolean enableGroups;
    
        private UserDAO userDao;
    
        public void setUserDao(UserDAO userDao) {
            this.userDao = userDao;
        }
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
            List<UserDetails> users = loadUsersByUsername(username);
    
            if (users.size() == 0) {
                logger.debug("Query returned no results for user '" + username + "'");
    
                throw new UsernameNotFoundException(
                        messages.getMessage("JdbcDaoImpl.notFound", new Object[]{username}, "Username {0} not found"));
            }
    
            UserDetails user = users.get(0); // contains no GrantedAuthority[]
    
            Set<GrantedAuthority> dbAuthsSet = new HashSet<GrantedAuthority>();
    
            if (enableAuthorities) {
                dbAuthsSet.addAll(loadUserAuthorities(user));
            }
    
            List<GrantedAuthority> dbAuths = new ArrayList<GrantedAuthority>(dbAuthsSet);
    
            addCustomAuthorities(user.getUsername(), dbAuths);
    
            if (dbAuths.size() == 0) {
                throw new UsernameNotFoundException(
                        messages.getMessage("JdbcDaoImpl.noAuthority",
                                new Object[] {username}, "User {0} has no GrantedAuthority"));
            }
    
            return createUserDetails(username, user, dbAuths);
        }
    
        protected List<UserDetails> loadUsersByUsername(String username) {
    
            if (null == username || "".equalsIgnoreCase(username)) {
    	return new ArrayList<UserDetails>();
            }
    
            List<User> listUser = userDao.findUserByName(username);
        	
            for (User user : listUser) {
                listDetails.add(new MoyWrappedUserDetails(user, AuthorityUtils.NO_AUTHORITIES));
            }
    
            return listDetails;
        }
    
        // rest methods refer to JdbcDaoImpl respective methods
    }
    In my security.xml configuration file, it is like following.
    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <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.1.xsd
               http://www.springframework.org/schema/security
               http://www.springframework.org/schema/security/spring-security-3.1.xsd">
    
        <debug />
    
        <global-method-security pre-post-annotations="disabled" />
        
        <http pattern="/login.xhtml*" security="none" />
    
        <http use-expressions="true">
            <intercept-url pattern="/view/**" access="ROLE_ADMINISTRATOR" requires-channel="https" />
            <intercept-url pattern="/**" access="isAuthenticated()" requires-channel="https" />
            <form-login login-page="/login.xhtml" default-target-url="/index.xhtml" />
            <logout logout-success-url="/login.xhtml" delete-cookies="JSESSIONID"/>
        </http>
    
        <beans:bean id="myUserDetailsService" class="com.aaa.bbb.sss.auth.MyUserDetailsService">
            <beans:property name="userDao" ref="userDao" />
        </beans:bean>
    
        <authentication-manager >
            <authentication-provider user-service-ref="myUserDetailsService" />
        </authentication-manager>
    
    </beans:beans>
    However, when I try to login my system I get NPE of EntityManager and all other relative DAO class got similar issue that EntityManager is not injected properly.

    After searching within the forum, below two test cases are talking about instantiation sequence. The issue seems due to security object being instantiated prior to normal Spring bean. But they are both about Spring Security 2.0.
    https://jira.springsource.org/browse/SEC-750
    https://jira.springsource.org/browse/SEC-826

    I am newbie of Spring and Spring Security, so please give some help and suggestion.
    Appreciate in advance.

    Flik

  • #2
    Hi guys,

    I get a walk around solution to postpone the injection of dao instance till the spring context is loaded.
    I create a new utility class which implements the interface of ApplicationContextAware and its source code looks like following.
    Code:
    public class SpringContextUtil implements ApplicationContextAware {
    
        private ApplicationContext context;
    
        @Override
        public void setApplicationContext(ApplicationContext context) throws BeansException {
    
            this.context = context;
    
            MyUserDetailsService service = (MyUserDetailsService) context.getBean("myUserDetailsService");
            UserDAO userDao = (UserDAO) context.getBean("userDao");
            service.setUserDao(userDao);
        }
    }
    And change my security.xml as following.
    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <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.1.xsd
               http://www.springframework.org/schema/security
               http://www.springframework.org/schema/security/spring-security-3.1.xsd">
    
        <debug />
    
        <global-method-security pre-post-annotations="disabled" />
        
        <http pattern="/login.xhtml*" security="none" />
    
        <http use-expressions="true">
            <intercept-url pattern="/view/**" access="ROLE_ADMINISTRATOR" requires-channel="https" />
            <intercept-url pattern="/**" access="isAuthenticated()" requires-channel="https" />
            <form-login login-page="/login.xhtml" default-target-url="/index.xhtml" />
            <logout logout-success-url="/login.xhtml" delete-cookies="JSESSIONID"/>
        </http>
    
        <beans:bean id="myUserDetailsService" class="com.aaa.bbb.sss.auth.MyUserDetailsService">
            <!-- beans:property name="userDao" ref="userDao" / -->
        </beans:bean>
    
        <authentication-manager >
            <authentication-provider user-service-ref="myUserDetailsService" />
        </authentication-manager>
    
        <beans:bean id="mySpringContextUtil" class="com.aaa.bbb.sss.util.SpringContextUtil" />
    </beans:beans>
    However, I got another very strange issue although JPA operation is successfully done by my customized UserDetailsService. I am not sure which forum I should post this issue, here or Spring Framework or EclipseLink. The issue is about ClassCastException and output is as below.
    Code:
    Caused by: java.lang.ClassCastException: java.util.ArrayList cannot be cast to com.aaa.bbb.sss.model.User
            at $Proxy9.findUserByNamePasswd(Unknown Source)
            at com.aaa.bbb.sss.controller.UserManagementService.loginUser(UserManagementService.java:33)
            at com.aaa.bbb.sss.controller.UserManagementService$$FastClassByCGLIB$$9ec5a646.invoke(<generated>)
            at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
            at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:689)
            at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
            at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
            at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
            at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:90)
            at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
            at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
            at com.aaa.bbb.sss.controller.UserManagementService$$EnhancerByCGLIB$$7da05bdc.loginUser(<generated>)
            at com.aaa.bbb.sss.jsf.login.UserLogin.loginUser(UserLogin.java:115)
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
            at java.lang.reflect.Method.invoke(Method.java:597)
            at com.sun.el.parser.AstValue.invoke(AstValue.java:238)
            at com.sun.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:302)
            at com.sun.faces.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:105)
            at javax.faces.component.MethodBindingMethodExpressionAdapter.invoke(MethodBindingMethodExpressionAdapter.java:88)
            ... 34 more
    My DAO implementation of method findUserByNamePasswd is as below.
    Code:
        @Override
        public User findUserByNamePasswd(String username, String passwd) {
    
            if (null == username || null == passwd || "".equalsIgnoreCase(username) || "".equals(passwd)) {
                return null;
            }
    
            User user = new User();
            user.setUsername(username);
            user.setPassword(passwd);
            ReadObjectQuery roq = new ReadObjectQuery(User.class);
            roq.setExampleObject(user);
    		
            System.out.println("Entity Manager is null [" + (em==null) + "]");
    
            Query query = JpaHelper.createQuery(roq, em);
    
            // Wrap the native query in a standard JPA Query and execute it
            return (User) query.getSingleResult();
        }
    Any idea?

    Many thanks.
    Flik

    Comment


    • #3
      Is there any other approach?

      Thanks.
      Flik

      Comment


      • #4
        Remove the debug element from your configuration, it has issues with eagerly instantiating beans!

        Also as you appear to want to use autowiring make sure that you have a context:annotation-driven or component-scan in your configuration (also it has to be in the same applicationcontext! a contect:annotation-driven in the dispatcher-servlet does nothing for the contextloaderlistener and vice-versa).

        A final note why not simply use plain JPA instead of custom classes? It is shorter and your code doesn't really depend on EclipseLink or anyother specific classes. But just imho that is .

        Code:
        @Override
        public User findUserByNamePasswd(String username, String passwd) {
        
        	if (null == username || null == passwd || "".equalsIgnoreCase(username) || "".equals(passwd)) {
        		return null;
        	}
        	
        	TypedQuery query = entityManager.createTypedQuery("select u from User u where u.username=:username and u.password=:password", User.class);
        	query.setParameter("username", username);
        	query.setParameter("password", passwd);
        	System.out.println("Entity Manager is null [" + (em==null) + "]");
        	return query.getSingleResult();
        	
        }

        Comment


        • #5
          Originally posted by Marten Deinum View Post
          Remove the debug element from your configuration, it has issues with eagerly instantiating beans!
          A little more info for those that hit this thread later. The issue that Marten is referring to is logged as SEC-1885 and will be fixed in Spring Security 3.1.1

          Comment


          • #6
            Originally posted by Marten Deinum View Post
            A final note why not simply use plain JPA instead of custom classes? It is shorter and your code doesn't really depend on EclipseLink or anyother specific classes. But just imho that is .
            You are right it really looks stronger and nice to use plain JPA. In future we are planning to introduce repository layer and We don't want to rely on EclipseLink to much.

            Originally posted by Marten Deinum View Post
            Remove the debug element from your configuration, it has issues with eagerly instantiating beans!
            The debug element is the root cause of NPE and after remove it EntityManager get inject properly. But class cast issue is still there. Before I introduce Spring Security into project, it worked fine. Does Spring Security try to proxy my DAO implementation? Could you please give me some suggestion how to look into it?

            Many thanks.

            Comment


            • #7
              There might be some proxying going on but nothing that should change the return type of a method.

              Check the line that goes wrong. I suspect that you are trying to cast the User return type to a List<UserDetails> just as the code in the loadByUsername method does (which you posted initially).

              Comment


              • #8
                Originally posted by Marten Deinum View Post
                There might be some proxying going on but nothing that should change the return type of a method.

                Check the line that goes wrong. I suspect that you are trying to cast the User return type to a List<UserDetails> just as the code in the loadByUsername method does (which you posted initially).
                following is the method which is reported wrong.
                Code:
                30    public User loginUser(String strUserName, String strPassword) {
                31
                32        System.out.println("User Name: [" + strUserName + "] Password: [" + strPassword + "]");
                33        Object returnObj = userDao.findUserByNamePasswd(strUserName.toLowerCase(), strPassword);
                34        return (User) returnObj;
                35    }
                The dao class implementation method I have published previously like below.
                Code:
                    @Override
                    public User findUserByNamePasswd(String username, String passwd) {
                
                        if (null == username || null == passwd || "".equalsIgnoreCase(username) || "".equals(passwd)) {
                            return null;
                        }
                
                        User user = new User();
                        user.setUsername(username);
                        user.setPassword(passwd);
                        ReadObjectQuery roq = new ReadObjectQuery(User.class);
                        roq.setExampleObject(user);
                		
                        System.out.println("Entity Manager is null [" + (em==null) + "]");
                
                        Query query = JpaHelper.createQuery(roq, em);
                
                        // Wrap the native query in a standard JPA Query and execute it
                        return (User) query.getSingleResult();
                    }
                It works fine and I didn't get this issue before I bring Spring Security into project.
                Does Spring Security try to proxy my class?

                Thanks a lot.
                Last edited by schua; Jul 6th, 2012, 08:25 AM. Reason: highlight the wrong line

                Comment


                • #9
                  Things get more strange

                  In my JSF backing bean the method login responding login process and the code is as following.
                  Code:
                      public void loginUser() throws IOException, ServletException {
                  
                          FacesContext facesContext = FacesContext.getCurrentInstance();
                          ExternalContext context = facesContext.getExternalContext();
                          ServletRequest request = (ServletRequest) context.getRequest();
                          ServletResponse response = (ServletResponse) context.getResponse();
                          RequestDispatcher dispatcher = request.getRequestDispatcher("/j_spring_security_check?j_username=" + strUserName + "&j_password=" + strPassword);
                  		
                          dispatcher.forward(request, response);
                          facesContext.responseComplete();
                  
                          System.out.println("User Name: [" + strUserName + "] Password: [" + strPassword + "]");
                  		
                          User user = controller.loginUser(strUserName.toLowerCase(), strPassword);
                  		
                          if (null == user) {
                              bSuccessLogin = false;
                              iUserId = -1;
                              strUserName = null;
                              strPassword = null;
                              strFirstName = null;
                              strLastName = null;
                          } else {
                              bSuccessLogin = true;
                              iUserId = user.getOid();
                              strUserName = user.getUsername();
                              strPassword = user.getPassword();
                              strFirstName = user.getFirstName();
                              strLastName = user.getLastName();
                          }
                  
                          user = null;
                      }
                  I got error message is
                  Code:
                  Caused by: java.lang.ClassCastException: java.util.ArrayList cannot be cast to com.aaa.bbb.sss.model.User
                  	at $Proxy23.findUserByNamePasswd(Unknown Source)
                  	at com.aaa.bbb.sss.controller.UserManagementService.loginUser(UserManagementService.java:33)
                  After I change the sequence of fetching user information and forward to request as following.
                  Code:
                      public void loginUser() throws IOException, ServletException {
                  
                          System.out.println("User Name: [" + strUserName + "] Password: [" + strPassword + "]");
                  		
                          User user = controller.loginUser(strUserName.toLowerCase(), strPassword);
                  		
                          if (null == user) {
                              bSuccessLogin = false;
                              iUserId = -1;
                              strUserName = null;
                              strPassword = null;
                              strFirstName = null;
                              strLastName = null;
                          } else {
                              bSuccessLogin = true;
                              iUserId = user.getOid();
                              strUserName = user.getUsername();
                              strPassword = user.getPassword();
                              strFirstName = user.getFirstName();
                              strLastName = user.getLastName();
                          }
                  
                          user = null;
                  
                          FacesContext facesContext = FacesContext.getCurrentInstance();
                          ExternalContext context = facesContext.getExternalContext();
                          ServletRequest request = (ServletRequest) context.getRequest();
                          ServletResponse response = (ServletResponse) context.getResponse();
                          RequestDispatcher dispatcher = request.getRequestDispatcher("/j_spring_security_check?j_username=" + strUserName + "&j_password=" + strPassword);
                  		
                          dispatcher.forward(request, response);
                          facesContext.responseComplete();
                      }
                  The error message becomes into below.
                  Code:
                  java.lang.ClassCastException: com.aaa.bbb.sss.model.User cannot be cast to java.util.List
                  	at $Proxy23.findUserByName(Unknown Source)
                  	at com.aaa.bbb.sss.auth.MyUserDetailsService.loadUsersByUsername(MyUserDetailsService.java:103)
                  It seems the firstly running method will be cached and will be re-used when the second method invoked.
                  Due to different return type, class cast issue occurs.
                  Does it mean I could NOT proxy some class in one request?
                  Or I should forward request in another way?
                  Last edited by schua; Jul 6th, 2012, 09:05 AM. Reason: Highlight error information

                  Comment


                  • #10
                    One update about the ClassCastException.
                    After I consolidate the return types of all methods in DAO implementation to List<User> or User, all things work fine.
                    Currently it is my only solution to make all things work.
                    But I guess, it should not be like this.

                    I am not clear about the mechanism of AOP and proxy either not sure the root cause of this issue.
                    Hope someone could give some explanation for our understanding.

                    Thanks a lot.

                    Comment


                    • #11
                      It is difficult to say without seeing the code for findUserByNamePasswd

                      Comment

                      Working...
                      X