Spring #Autowired OK on JUnit, NPE on main class - spring

I have a open source project hosted at this github. I am facing a weird scenario. I found many people who could make #Autowired Spring annotation work on their main classes but not on their JUnit test classes. My problem is the other way around. I can successfully use #Autowired in my JUnit test class but when a test calls my main class, the dependencies don't get injected there. Here is my context (simplified version):
Login Class:
package net.openrally.restaurant.core.exposure.resource;
#Path("/login")
#Component
#Transactional
#Singleton
#Produces("application/json")
#Consumes("application/json")
public class Login extends BaseResource{
#Autowired
private UserDAO userDAO;
#POST
public Response post(String requestBody){
...
//NullPointerException
User user = userDAO.loadByCompanyIdAndLogin(companyId, login);
}
...
}
LoginTest Class:
package net.openrally.restaurant.core.exposure.resource;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("/applicationContext.xml")
public class LoginTest extends BaseResourceTest {
#Autowired
private UserDAO userDAO;
...
#Test
public void testInvalidPassword() {
....
// Works perfectly!
userDAO.save(user);
....
}
}
applicationContext.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:context="http://www.springframework.org/schema/context"
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">
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:configuration.properties</value>
</list>
</property>
</bean>
<context:annotation-config />
<context:component-scan base-package="net.openrally.restaurant.core" />
<import resource="db-config.xml" />
</beans>
I am searching for a solution for a few days now. From what I could find so far, most of the people who have #Autowired problems dont have either <context:annotation-config /> or xmlns:context="http://www.springframework.org/schema/context" in their spring xml or dont have a #Component family annotation in the class they want DI to take place, and as you can see, they are both there :(. I have only one applicationContext.xml in my project which suits for both runtime and test (I have distinct configuration.properties to set database credentials and log levels differently, but no spring configuration there)
I'm using:
Spring: 3.1.0.RELEASE
JUnit: 4.10
Jersey: 1.11
CLIB: 2.2.2
Any ideas, and I mean ANY :), is much appreciated.
UPDATE
The following logs come up when I run the test:
2012-03-27 07:37:02,457 DEBUG [main] org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'login'
2012-03-27 07:37:02,457 DEBUG [main] org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'login'
2012-03-27 07:37:02,459 DEBUG [main] org.springframework.beans.factory.annotation.InjectionMetadata - Found injected element on class [net.openrally.restaurant.core.exposure.resource.Login]: AutowiredFieldElement for private net.openrally.restaurant.core.persistence.dao.ConfigurationDAO net.openrally.restaurant.core.exposure.resource.Login.configurationDAO
2012-03-27 07:37:02,459 DEBUG [main] org.springframework.beans.factory.annotation.InjectionMetadata - Found injected element on class [net.openrally.restaurant.core.exposure.resource.Login]: AutowiredFieldElement for private net.openrally.restaurant.core.persistence.dao.UserDAO net.openrally.restaurant.core.exposure.resource.Login.userDAO
2012-03-27 07:37:02,459 DEBUG [main] org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'login' to allow for resolving potential circular references
2012-03-27 07:37:02,459 TRACE [main] org.springframework.beans.CachedIntrospectionResults - Getting BeanInfo for class [net.openrally.restaurant.core.exposure.resource.Login]
2012-03-27 07:37:02,462 TRACE [main] org.springframework.beans.CachedIntrospectionResults - Caching PropertyDescriptors for class [net.openrally.restaurant.core.exposure.resource.Login]
2012-03-27 07:37:02,462 TRACE [main] org.springframework.beans.CachedIntrospectionResults - Found bean property 'class' of type [java.lang.Class]
2012-03-27 07:37:02,462 DEBUG [main] org.springframework.beans.factory.annotation.InjectionMetadata - Processing injected method of bean 'login': AutowiredFieldElement for private net.openrally.restaurant.core.persistence.dao.ConfigurationDAO net.openrally.restaurant.core.exposure.resource.Login.configurationDAO
2012-03-27 07:37:02,462 DEBUG [main] org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'configurationDAO'
2012-03-27 07:37:02,462 DEBUG [main] org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - Autowiring by type from bean name 'login' to bean named 'configurationDAO'
2012-03-27 07:37:02,463 DEBUG [main] org.springframework.beans.factory.annotation.InjectionMetadata - Processing injected method of bean 'login': AutowiredFieldElement for private net.openrally.restaurant.core.persistence.dao.UserDAO net.openrally.restaurant.core.exposure.resource.Login.userDAO
2012-03-27 07:37:02,463 DEBUG [main] org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'userDAO'
2012-03-27 07:37:02,463 DEBUG [main] org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - Autowiring by type from bean name 'login' to bean named 'userDAO'
2012-03-27 07:37:02,464 DEBUG [main] org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator - Creating implicit proxy for bean 'login' with 0 common interceptors and 1 specific interceptors
2012-03-27 07:37:02,464 DEBUG [main] org.springframework.aop.framework.Cglib2AopProxy - Creating CGLIB2 proxy: target source is SingletonTargetSource for target object [net.openrally.restaurant.core.exposure.resource.Login#60532a0a]
2012-03-27 07:37:02,465 DEBUG [main] org.springframework.aop.framework.Cglib2AopProxy - Unable to apply any optimisations to advised method: public javax.ws.rs.core.Response net.openrally.restaurant.core.exposure.resource.Login.post(java.lang.String)
Could Spring be creating my bean correctly but Jersey using its own non-autowired instance?

After researching a while, I could not find a way to make GrizzlyWeb use the context loaded by spring, so I tried to find an alternative solution.
I then found Hifaces20, which will let you start and stop a jetty instance, within the same JVM (which means you may, for instance, use a memory database that will be seen both by your tests and your application)

I had the exact same issue and later found that it was a developer issue :)
I was creating a new object instead of using the spring bean.
Lets say class MyService autowires a dao class MyDao myDaoBean. Now lets say I want to use MyService in MyController, I should wire in the Spring bean myServiceSpringBean. If I try to create a new myServiceObject then Spring doesn't wire the myDaoBean into the myServiceObject because it's not aware about that new service object.
And that leads to myDaoBean being null.

Related

How can I confirm that Spring Transactions are working properly?

I working on an Spring 3.2 application running in OC4j. I have a number of methods annotated with Spring's #Transactional annotation as follows:
// in MyServiceImpl
#Transactional()
public void insertPaymentData(PaymentParams paymentParams) {
// call to MyDaoImpl which in turn calls a
// Spring JDBC Stored Procedure implementation
}
// in the Stored Procedure implementation
#Transactional(propagation = Propagation.MANDATORY)
public void insertPaymentData(PaymentParams paymentParams) {
// execute procedure here
}
In the logs for my local OC4J instance I see entries such as the following which make me think that Transactions are configured correctly:
[1.2.3.4-user123] 2019-03-20 17:36:14,805 DEBUG AbstractFallbackTransactionAttributeSource::getTransactionAttribute - Adding transactional method 'MyServiceImpl.insertPaymentData' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,timeout_1800; ''
[1.2.3.4-user123] 2019-03-20 17:36:14,809 DEBUG AbstractBeanFactory::doGetBean - Returning cached instance of singleton bean 'transactionManager'
[1.2.3.4-user123] 2019-03-20 17:36:14,812 DEBUG AbstractPlatformTransactionManager::getTransaction - Creating new transaction with name [my.app.service.payments.MyServiceImpl.insertPaymentData]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,timeout_1800; ''
[1.2.3.4-user123] 2019-03-20 17:36:14,821 DEBUG AbstractBeanFactory::doGetBean - Returning cached instance of singleton bean 'transactionManager'
[1.2.3.4-user123] 2019-03-20 17:36:14,822 DEBUG AbstractPlatformTransactionManager::handleExistingTransaction - Participating in existing transaction
[1.2.3.4-user123] 2019-03-20 17:36:14,822 DEBUG DataSourceUtils::doGetConnection - Fetching JDBC Connection from DataSource
[1.2.3.4-user123] 2019-03-20 17:36:14,823 DEBUG DataSourceUtils::doGetConnection - Registering transaction synchronization for JDBC Connection
[1.2.3.4-user123] 2019-03-20 17:38:42,550 DEBUG DataSourceUtils::doReleaseConnection - Returning JDBC Connection to DataSource
[1.2.3.4-user123] 2019-03-20 17:38:42,551 DEBUG AbstractPlatformTransactionManager::processCommit - Initiating transaction commit
Sometimes I see timeout and rollback messages there too.
However when I deploy to the development server provided by the ops department I do not see any messages like this in the logs, although the DEBUG level messages are displayed there too. I added in the following logging lines which I found somewhere on stack overflow:
logger.debug("Transaction name=" + TransactionSynchronizationManager.getCurrentTransactionName());
logger.debug("isActualTransactionActive=" + TransactionSynchronizationManager.isActualTransactionActive());
logger.debug("isSynchronizationActive=" + TransactionSynchronizationManager.isSynchronizationActive());
In the logs I now see things like this:
Transaction name=my.app.service.payments.MyServiceImpl.insertPaymentData
isActualTransactionActive=true
isSynchronizationActive=true
Are these values from TransactionSynchronizationManager telling me anything useful?
Is it telling me that transactions are working fine and to stop worrying?
I have the following in a Spring configuration file:
#Configuration
#EnableTransactionManagement
public class TransactionConfig {
#Bean
public JtaTransactionManager transactionManager() {
return new JtaTransactionManager();
}
}
And I have the following in a spring xml config file:
<tx:annotation-driven transaction-manager="transactionManager" />
Thanks!

Could not detect default configuration classes for test class with Kotlin and #AutoConfigureMockMvc

I'm giving Kotlin a whirl and was converting the test below that was running fine in Java to Kotlin. After converting the test using the IntelliJ conversion tool I tried to run it but I got this error:
22:32:19.476 [main] DEBUG org.springframework.test.context.junit4.SpringJUnit4ClassRunner - SpringJUnit4ClassRunner constructor called with [class com.test.app.web.DeveloperControllerTest]
22:32:19.486 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
22:32:19.494 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
22:32:19.517 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [com.test.app.web.DeveloperControllerTest] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper]
22:32:19.539 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither #ContextConfiguration nor #ContextHierarchy found for test class [com.test.app.web.DeveloperControllerTest], using SpringBootContextLoader
22:32:19.543 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.test.app.web.DeveloperControllerTest]: class path resource [com/test/app/web/DeveloperControllerTest-context.xml] does not exist
22:32:19.544 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.test.app.web.DeveloperControllerTest]: class path resource [com/test/app/web/DeveloperControllerTestContext.groovy] does not exist
22:32:19.544 [main] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [com.test.app.web.DeveloperControllerTest]: no resource found for suffixes {-context.xml, Context.groovy}.
22:32:19.545 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [com.test.app.web.DeveloperControllerTest]: DeveloperControllerTest does not declare any static, non-private, non-final, nested classes annotated with #Configuration.
java.lang.StackOverflowError
at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936)
at java.util.concurrent.ConcurrentHashMap.containsKey(ConcurrentHashMap.java:964)
at java.lang.reflect.WeakCache.containsValue(WeakCache.java:175)
at java.lang.reflect.Proxy.isProxyClass(Proxy.java:791)
at java.lang.reflect.Proxy.getInvocationHandler(Proxy.java:815)
at sun.reflect.annotation.AnnotationInvocationHandler.asOneOfUs(AnnotationInvocationHandler.java:226)
at sun.reflect.annotation.AnnotationInvocationHandler.equalsImpl(AnnotationInvocationHandler.java:201)
at sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:64)
at com.sun.proxy.$Proxy13.equals(Unknown Source)
at java.util.HashMap.putVal(HashMap.java:634)
at java.util.HashMap.put(HashMap.java:611)
at java.util.HashSet.add(HashSet.java:219)
at org.springframework.boot.test.context.ImportsContextCustomizer$ContextCustomizerKey.collectElementAnnotations(ImportsContextCustomizer.java:239)
at org.springframework.boot.test.context.ImportsContextCustomizer$ContextCustomizerKey.collectClassAnnotations(ImportsContextCustomizer.java:226)
Java test:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class DeveloperControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void createNewDeveloper() throws Exception {
mvc.perform(
post("/api/v1/developers")
.param("username", "boaty")
.param("email", "boaty#mcboatface.org")
.param("password", "123loveboats")
.param("passwordConfirmation", "123loveboats")
).andExpect(status().isCreated());
}
}
Converted to Kotlin:
#RunWith(SpringRunner::class)
#SpringBootTest
#AutoConfigureMockMvc
class DeveloperControllerTest {
#Autowired
lateinit var mvc: MockMvc
#Test
#Throws(Exception::class)
fun createNewDeveloper() {
mvc.perform(
post("/api/v1/developers")
.param("username", "boaty")
.param("email", "boaty#mcboatface.org")
.param("password", "123loveboats")
.param("passwordConfirmation", "123loveboats"))
.andExpect(status().isCreated)
}
}
What I've noticed with this is that if I remove the annotation AutoConfigureMockMvc Spring will boot and try to run, but it'll fail because it can't autowire MockMvc.
I'm using Kotlin 1.0.1-2 together with Spring Boot 1.4.0-M2.
try adding
#ComponentScan(basePackages = arrayOf("YourAppBasePackage"))
#SpringBootTest(classes = arrayOf(MyExampleApplication::class))
This solved the issue for me
The stack overflow is occurring when Spring Boot attempts to introspect the annotations on DeveloperControllerTest.
DeveloperControllerTest is annotated with kotlin.Metadata. Metadata is annotated with kotlin.annotation.Retention. Retention is annotated with kotlin.annotation.Target and Target is annotated with itself. Target being annotated with itself is the cause of the stack overflowing.
It is legal, although probably quite unusual, for an annotation to be used to annotate itself. Spring Boot should probably be more defensive.

Scan specific packages for Spring AOP

I'm trying to use AOP into an existing (big) Spring project. The thing is that I don't want Spring to create proxies for all the objects in the ApplicationContext, mainly for performance but also because there are final classes that I cannot modify.
I've tried to make Spring search only inside "com.foo.bar.*" by defining the following aspect:
com.baz.MyAspect
#Aspect
public class MyAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(MyAspect.class);
#Before("within(com.foo.bar.*) && " +
"execution(* com.foo.bar.MyController.handleRequest(..))")
public void getData() {
// Nothing yet
}
}
And I've added this lines to the configuration:
<?xml version="1.0" encoding="utf-8"?>
<beans ...>
<aop:aspectj-autoproxy proxy-target-class="true" />
<bean id="myAspect" class="com.baz.MyAspect"/>
</beans>
But when I run the app, I get the following Exception:
Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.foobar.FinalController]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class com.foobar.FinalController
So it seems that Spring is scanning packages other than the ones defined in the within expression. I would like to know whether there is a way to specify the packages to be scanned or any other way to solve this problem.
yes you can define your point cut like this
the execution of any method defined in the bar package or a sub-package:
execution(* com.foo.bar...(..)) click here for detail

#Autowired fails with JUnit & Spring

Really need help. I used JUnit(4.6) plus Spring(3.0.5) for unit testing. When I tried to autowire a service object in my test class, I got a NoSuchBeanDefinitionException.
The JUnit code:
package com.aaa.service.impl;
#ContextConfiguration(locations = { "classpath:/spring/applicationContext.xml",
"classpath:/spring/applicationContext-aop.xml",
"classpath:/spring/applicationContext-dao.xml",
"classpath:/spring/applicationContext-service.xml" })
#RunWith(SpringJUnit4ClassRunner.class)
public class TestManageTargetServiceImpl {
#Autowired
ManageTargetServiceImpl manageTargetServiceImpl;
#Test
public void testQueryFinancialMonthList() {
List<Map<String, Object>> value = new ArrayList<Map<String, Object>>();
ManageTargetDao manageTargetDao = mock(ManageTargetDao.class);
when(manageTargetDao.queryFinancialMonthList()).thenReturn(value);
manageTargetServiceImpl.setManageTargetDao(manageTargetDao);
// I hope it return null
assertNull(manageTargetServiceImpl.queryFinancialMonthList());
}
}
The applicationContext-service.xml code:
<context:annotation-config />
<bean id="manageTargetService" class="com.aaa.service.impl.ManageTargetServiceImpl">
<property name="manageTargetDao" ref="manageTargetDao"></property>
</bean>
The error track:
00:51:28,625 ERROR main context.TestContextManager:324 - Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener#882dfc] to prepare test instance [com.aaa.service.impl.TestManageTargetServiceImpl#ce2db0]
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.aaa.service.impl.TestManageTargetServiceImpl': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: com.aaa.service.impl.ManageTargetServiceImpl com.aaa.service.impl.TestManageTargetServiceImpl.manageTargetServiceImpl; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.aaa.service.impl.ManageTargetServiceImpl] 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)}
Some log:
00:51:26,828 DEBUG main context.TestContextManager:282 - beforeTestClass(): class [class com.aaa.service.impl.TestManageTargetServiceImpl]
00:51:26,828 DEBUG main annotation.ProfileValueUtils:68 - Retrieved #ProfileValueSourceConfiguration [null] for test class [com.aaa.service.impl.TestManageTargetServiceImpl]
00:51:26,828 DEBUG main annotation.ProfileValueUtils:80 - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.aaa.service.impl.TestManageTargetServiceImpl]
00:51:26,828 DEBUG main context.TestContextManager:315 - prepareTestInstance(): instance [com.aaa.service.impl.TestManageTargetServiceImpl#ce2db0]
00:51:26,843 DEBUG main support.DependencyInjectionTestExecutionListener:73 - Performing dependency injection for test context [[TestContext#e0eb3f testClass = TestManageTargetServiceImpl, locations = array<String>['classpath:/spring/applicationContext.xml', 'classpath:/spring/applicationContext-aop.xml', 'classpath:/spring/applicationContext-dao.xml', 'classpath:/spring/applicationContext-service.xml'], testInstance = com.aaa.service.impl.TestManageTargetServiceImpl#ce2db0, testMethod = [null], testException = [null]]].
00:51:26,843 DEBUG main support.AbstractGenericContextLoader:75 - Loading ApplicationContext for locations [classpath:/spring/applicationContext.xml,classpath:/spring/applicationContext-aop.xml,classpath:/spring/applicationContext-dao.xml,classpath:/spring/applicationContext-service.xml].
00:51:26,968 INFO main xml.XmlBeanDefinitionReader:315 - Loading XML bean definitions from class path resource [spring/applicationContext.xml]
00:51:27,187 INFO main xml.XmlBeanDefinitionReader:315 - Loading XML bean definitions from class path resource [spring/applicationContext-aop.xml]
00:51:27,312 INFO main xml.XmlBeanDefinitionReader:315 - Loading XML bean definitions from class path resource [spring/applicationContext-dao.xml]
00:51:27,421 INFO main xml.XmlBeanDefinitionReader:315 - Loading XML bean definitions from class path resource [spring/applicationContext-service.xml]
00:51:27,453 INFO main support.GenericApplicationContext:456 - Refreshing org.springframework.context.support.GenericApplicationContext#16c14e7: startup date [Tue Jan 01 00:51:27 NZDT 2013]; root of context hierarchy
00:51:27,718 INFO main config.PropertyPlaceholderConfigurer:177 - Loading properties file from class path resource [jdbc.properties]
00:51:27,796 INFO main support.DefaultListableBeanFactory:555 - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory#2938d8: defining beans [loggingAop,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.aop.aspectj.AspectJPointcutAdvisor#0,service,loginDao,manageTargetDao,org.springframework.beans.factory.config.PropertyPlaceholderConfigurer#0,dataSource,txManager,txAdvice,txDAO,org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,loginService,manageTargetService]; root of factory hierarchy
Guessing here - but given that injection of a class type ManageTargetServiceImpl is failing, I think the reason is that a proxy is getting created for ManageTargetServiceImpl and so the type of the bean will not be ManageTargetServiceImpl anymore but the interface of that class.
from Biju Kunjummen
Look at your property id. It should be manageTargetServiceImpl in your applicationContext-service.xml
Change it to this:
<bean id="manageTargetServiceImpl" class="com.aaa.service.impl.ManageTargetServiceImpl">
<property name="manageTargetDao" ref="manageTargetDao"></property>
</bean>

PreAuthorize doesn't work

I'm writing a socket server (no web-application !) application and want to use method-based security to handle my ACL needs. i followed a small tutorial i found spring security by example
so far i configured:
<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler" />
</security:global-method-security>
<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator">
<bean id="permissionEvaluator" class="myPermissionEvaluator" />
</property>
</bean>
<security:authentication-manager id="authenticationmanager">
<security:authentication-provider ref="authenticationprovider" />
</security:authentication-manager>
<bean id="authenticationprovider" class="myAuthenticationProvider" />
With a service bean:
#Named
public class ChannelService {
#PreAuthorize("isAuthenticated() and hasPermission(#channel, 'CHANNEL_WRITE')")
public void writeMessage(Channel channel, String message) { ... }
}
Everything compiles and the application starts and works fine, but without access control. My debug log shows that my Evaluator is never called.
When i tried something similar with a #Secured annotation the annotation was evaluated and access was denied. but simple role based security isn't enough for my requirements.
EDIT
did some more tests: when i configure only secured-annotations="enabled" the role based security works. when configure pre-post-annotations="enabled" in ADDITION neither secured nor preauthorize works. when i configure only pre-post-annotations it still doesn't work.
EDIT2
some more tests:
with only secured_annotations="enabled" the call to my channelservice goes through the Cglib2AopProxy
as soon as i activate pre-post-annotations the call lands directly in the channelservice. no interceptor, no proxy, nothing.
I'm getting kind of desperate...
EDIT3
I debug-logged my testruns here is the part for spring-security
with only secured-annotations="enabled"
2012-04-12 13:36:46,171 INFO [main] o.s.s.c.SpringSecurityCoreVersion - You are running with Spring Security Core 3.1.0.RELEASE
2012-04-12 13:36:46,174 INFO [main] o.s.s.c.SecurityNamespaceHandler - Spring Security 'config' module version is 3.1.0.RELEASE
2012-04-12 13:36:49,042 DEBUG [main] o.s.s.a.m.DelegatingMethodSecurityMetadataSource - Caching method [CacheKey[mystuff.UserService; public void mystuff.UserService.serverBan(java.lang.String,mystuff.models.User,org.joda.time.DateTime)]] with attributes [user]
2012-04-12 13:36:49,138 DEBUG [main] o.s.s.a.i.a.MethodSecurityInterceptor - Validated configuration attributes
2012-04-12 13:36:49,221 DEBUG [main] o.s.s.a.m.DelegatingMethodSecurityMetadataSource - Caching method [CacheKey[mystuff.ChannelService; public void mystuff.ChannelService.writeMessage(mystuff.models.Channel,java.lang.String)]] with attributes [blubb]
2012-04-12 13:36:51,159 DEBUG [main] o.s.s.a.ProviderManager - Authentication attempt using mystuff.GlobalchatAuthenticationProvider
2012-04-12 13:36:56,166 DEBUG [Timer-1] o.s.s.a.ProviderManager - Authentication attempt using mystuff.GlobalchatAuthenticationProvider
2012-04-12 13:36:56,183 DEBUG [Timer-1] o.s.s.a.i.a.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public void mystuff.ChannelService.writeMessage(mystuff.models.Channel,java.lang.String); target is of class [mystuff.ChannelService]; Attributes: [blubb]
2012-04-12 13:36:56,184 DEBUG [Timer-1] o.s.s.a.i.a.MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#312e8aef: Principal: mystuff.UserId#ced1752b; Credentials: [PROTECTED]; Authenticated: true; Details: null; Not granted any authorities
Exception in thread "Timer-1" org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AbstractAccessDecisionManager.checkAllowIfAllAbstainDecisions(AbstractAccessDecisionManager.java:70)
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:88)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:205)
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:59)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
at mystuff.ChannelService$$EnhancerByCGLIB$$3ad5e57f.writeMessage(<generated>)
at mystuff.run(DataGenerator.java:109)
at java.util.TimerThread.mainLoop(Timer.java:512)
at java.util.TimerThread.run(Timer.java:462)
2012-04-12 13:36:56,185 DEBUG [Timer-1] o.s.s.access.vote.AffirmativeBased - Voter: org.springframework.security.access.vote.RoleVoter#1cfe174, returned: 0
2012-04-12 13:36:56,185 DEBUG [Timer-1] o.s.s.access.vote.AffirmativeBased - Voter: org.springframework.security.access.vote.AuthenticatedVoter#da89a7, returned: 0
with pre-post-annotations="enabled"
2012-04-12 13:39:54,926 INFO [main] o.s.s.c.SpringSecurityCoreVersion - You are running with Spring Security Core 3.1.0.RELEASE
2012-04-12 13:39:54,929 INFO [main] o.s.s.c.SecurityNamespaceHandler - Spring Security 'config' module version is 3.1.0.RELEASE
2012-04-12 13:39:54,989 INFO [main] o.s.s.c.m.GlobalMethodSecurityBeanDefinitionParser - Using bean 'expressionHandler' as method ExpressionHandler implementation
2012-04-12 13:39:59,812 DEBUG [main] o.s.s.a.ProviderManager - Authentication attempt mystuff.GlobalchatAuthenticationProvider
2012-04-12 13:39:59,850 DEBUG [main] o.s.s.a.i.a.MethodSecurityInterceptor - Validated configuration attributes
As far as i understand this log output spring doesn't realize my beans need to be proxied, so they aren't and so i don't get security.
EDIT4
I debug-logged the complete sprint startup... (thats one big log) and there i find:
2012-04-12 14:40:41,385 INFO [main] o.s.c.s.ClassPathXmlApplicationContext - Bean 'channelService' of type [class mystuff.ChannelService] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
is there a way to figure out why? because as far as i understand it. because of #preauthorize the bean should be eligible. with only secured-annotations="enabled" i get a post processing log.
This configuration worked just as expected for me:
<bean id="securityExpressionHandler"
class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler" />
<bean id="preInvocationAdvice"
class="org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice"
p:expressionHandler-ref="securityExpressionHandler" />
<util:list id="decisionVoters">
<bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
<bean class="org.springframework.security.access.vote.RoleVoter" />
<bean class="org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter"
c:pre-ref="preInvocationAdvice" />
</util:list>
<bean id="accessDecisionManager"
class="org.springframework.security.access.vote.UnanimousBased"
c:decisionVoters-ref="decisionVoters" />
<sec:global-method-security
authentication-manager-ref="authenticationManager"
access-decision-manager-ref="accessDecisionManager"
pre-post-annotations="enabled" />
I got the log message:
WARN org.springframework.security.access.expression.DenyAllPermissionEvaluator -
Denying user jack permission 'CHANNEL_WRITE' on object Channel[ name=null ]
And an exception:
org.springframework.security.access.AccessDeniedException: Access is denied
From a simple test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:META-INF/spring/application-context.xml")
public class SpringSecurityPrePostTest {
#Autowired
ChannelService channelService;
#Test
public void shouldSecureService() throws Exception {
Authentication authentication = new UsernamePasswordAuthenticationToken("jack", "sparrow");
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
channelService.writeMessage(new Channel(), "test");
}
}
One thing I did diffrent was to use interface on a service and JDK proxies instead of cglib:
public interface ChannelService {
void writeMessage(Channel channel, String message);
}
and:
#Component
public class ChannelServiceImpl implements ChannelService {
private static final Logger LOG = LoggerFactory.getLogger(ChannelServiceImpl.class);
#Override
#PreAuthorize("isAuthenticated() and hasPermission(#channel, 'CHANNEL_WRITE')")
public void writeMessage(Channel channel, String message) {
LOG.info("Writing message {} to: {}" , message, channel);
}
}
UPDATE1:
With this simplified config I get the same result:
<bean id="securityExpressionHandler"
class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler" />
<sec:global-method-security
authentication-manager-ref="authenticationManager"
pre-post-annotations="enabled">
<sec:expression-handler ref="securityExpressionHandler" />
</sec:global-method-security>
UPDATE2:
The debug message from Edit4 indicates that channelService may not have bean proxied at all as it got classified as not eligible for auto-proxying. This qiestion answers similar problem - try not to use #Autowired or any other mechanism based on BeanPostProcessors to set up the beans involved in security checks (i.e. myPermissionEvaluator).
UPDATE3:
You cannot use secured resources (i.e. services) within beans responsible for security checks! This creates a dependency loop and is a error in Your configuration. You must use lover level access (i.e. DAO) to check permissions, anything that is not secured! Implementing security checks using secured resources is not what You want to do.
If despite using not secured resources with #Autowired things don't work as expected, try using old-school XML confiuration style for all beans involved in security checks. Also remember that <context:component-scan /> is in fact a BeanDefinitionRegistryPostProcessor and introduces the scanned beans into the BeanFactory after all the ones declared in XML are already there.
it works,
make sure that you have <sec:global-method-security pre-post-annotations="enabled"/> in your spring servlet (ie where you may have your <mvc:annotation-driven/>)
"sec" is from xmlns:sec="http://www.springframework.org/schema/security"

Resources