I am working on a project using Spring 3, and Spring Security. My problem is with IoC container. Problem started when I wrote my own implementation of UserDetailsService
for Spring Security-3. I checked the other questions but still could not solve the problem.
Definition of the problem is:
I have two seperate classes(One is UsersController.java
which extends @Controller
, and ProjectUserDetailsService
which extends @Service
) that uses a common object to be autowired. But while object is autowired successfully in UsersController
, it is null
in ProjectUserDetailsService
class altough the object of this class(ProjectUserDetailsService
) is successfully created(I verified this by debugging).
Any suggestions how to solve this?
Here are my web.xml
, project-servlet.xml
and project-security.xml
files and related classes.
Web.xml`
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Tutorial web application
-
-->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<display-name>Ecognitio with Spring Security</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/ecognitio-servlet.xml
/WEB-INF/ecognitio-security.xml
</param-value>
</context-param>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>tutorial.root</param-value>
</context-param>
<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>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<servlet>
<servlet-name>ecognitio</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>project</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>project</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
project-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!-- Scans the classpath of this application for @Components to deploy as beans -->
<context:component-scan base-package="com.project" />
<!-- Configures the @Controller programming model -->
<mvc:annotation-driven />
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource"
p:basename="Messages"/>
<!-- misc -->
<!-- <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="suffix" value=".jsp"/>
</bean> -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass">
<value>
org.springframework.web.servlet.view.tiles2.TilesView
</value>
</property>
</bean>
<bean id="tilesConfigurer"
class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/tiles.xml</value>
</list>
</property>
</bean>
<!-- Configures Hibernate - Database Config -->
<import resource="db-config.xml" />
</beans>
project-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Sample namespace-based configuration
-
-->
<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">
<debug />
<global-method-security pre-post-annotations="enabled">
<!-- AspectJ pointcut expression that locates our "post" method and applies security that way
<protect-pointcut expression="execution(* bigbank.*Service.post*(..))" access="ROLE_TELLER"/>
-->
</global-method-security>
<http pattern="/loggedout.jsp" security="none"/>
<http use-expressions="true" >
<intercept-url pattern="/secure/extreme/**" access="hasRole('ROLE_SUPERVISOR')"/>
<intercept-url pattern="/secure/**" access="isAuthenticated()" />
<!--
Allow all other requests. In a real application you should
adopt a whitelisting approach where access is not allowed by default
-->
<intercept-url pattern="/login.jsp*" access="isAuthenticated()==false"/>
<intercept-url pattern="/timeout.jsp*" access="isAuthenticated()==false"/>
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<!-- <intercept-url pattern="/**" access="permitAll" /> -->
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1" default-target-url="/dashboard.html" />
<logout logout-success-url="/login.jsp" delete-cookies="JSESSIONID"/>
<remember-me />
<!--
Uncomment to enable X509 client authentication support
<x509 />
-->
<!-- Uncomment to limit the number of sessions a user can have
<session-management invalid-session-url="/login.jsp">
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
-->
</http>
<!-- HERE IS WHERE I USE an object of ProjectUserDetailsService -->
<authentication-manager>
<authentication-provider user-service-ref="userDetailsService" />
</authentication-manager>
<!--
</beans:beans>
UsersController.java
(autowiring of an object of UsersDAO class is successful for this class)
package com.project.users;
//required imports
@Controller
public class UsersControllers
{
@Autowired
private UsersDAO usersDAO;
//Some more autowires and some class specific code
}
ProjectUserDetailsService.java
(where autowiring of UsersDAO does not work)
package com.project.security;
import java.util.ArrayList;
import java.util.Collection;
//required imports
@SuppressWarnings("deprecation")
@Service("userDetailsService")
public class ProjectUserDetailsService implements UserDetailsService {
@Autowired
private UsersDAO usersDAO;
@Autowired private Assembler assembler;
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
UserDetails userDetails = null;
//For debugging purposes
if(usersDAO==null){
System.out.println("DAO IS NULL");
System.out.println("DAO IS NULL");
System.out.println("DAO IS NULL");
}
User userEntity = usersDAO.findUserbyEmail("'"+username+"'");
if (userEntity == null)
throw new UsernameNotFoundException("user not found");
return assembler.buildUserFromUserEntity(userEntity);
}
}
yes, it seems to be impossible to autowire objects into beans that inherit from spring security classes. i don't know if this is a bug in spring security, or if it's done for security or what. if anyone has an explanation I would be interested in hearing it. You can solve your problem though by manually injecting the beans via xml configuration (as opposed to using the @Autowired annotation) and then they will be present. One word of caution though..
I did this, and i noticed that my userDao which had annotations on it (specifically @Transactional) was no longer operating in a transaction. My userDao was being used in multiple places. If I injected it into my custom AbstractUserDetailsAuthenticationProvider though, it no longer operated in a transaction for any other class that used it. Removing the injection into the custom AbstractUserDetailsAuthenticationProvider restored the transaction functionality to my userDao when used by other objects which were receiving it (either via @Autowired or manual xml injection).
So how did I get my userDao into my Spring Security context and still keep it @Transactional? I had to create a Factory class:
public class UserDaoFactory {
private static UserDao userDao;
public static UserDao getUserDao() {
return UserDaoFactory.userDao;
}
public void setUserDao(UserDao userDao) {
UserDaoFactory.userDao = userDao;
}
}
Then put this and your dao two objects into the spring container:
<bean id="userDao" class="com.package.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="userDaoFactory" class="com.package.UserDaoFactory">
<property name="userDao" ref="userDao" />
</bean>
So the userDao will get autowired into your userDaoFactory. It will have all of it's @Transactional capability (because spring security hasn't stripped it off?). Then in your spring security object you can do a:
userDao = UserDaoFactory.getUserDao();
I implemented ServletContextAware in my custom AbstractUserDetailsAuthenticationProvider object to do the above once during initialization, and viola.
So note, while you can manually inject your bean via xml configuration into the spring security object to overcome the @Autowired problem, you will end up with a new problem if you are trying to wrap that DAO in @Transactional.
now maybe someone knows why this may be happening. it could be a mis-configuration on my part (i admit i'm not a spring expert), or it could be a feature of spring. i would love to hear what anyone has to say and how to improve this though.