PDA

View Full Version : global-method-security on a roo app



mikej
Oct 21st, 2009, 01:37 AM
I'm having a heckuva time trying to use @PreAuthorize annotations, which seem to be a nice way to do some basic object-level security in a Roo-generated app. To start, I'm just trying to get global-method-security to work. So this may be a spring-security issue, not a Roo issue, but it seems to have something to do with the Roo-generated app's architecture (in particular, it seems that "global-method-security" aspects have problems when @configurable is also being used - but I can't figure out why). Here's what does not work for me:

I set up a simple project using Roo (trunk build #345), then added to applicationContext-security.xml:



<global-method-security pre-post-annotations="enabled" jsr250-annotations="enabled">
<protect-pointcut expression="execution(* com.mytest.domain.Task.findTask(..))" access="ROLE_ADMIN"/>
</global-method-security>


This should prevent me from using the "findTask" call, right? (I'm not an authenticated user here, I'm just using the generated Roo scaffolding which isn't yet protected.) Now, when I start Tomcat, I get a bunch of these:



[WebappClassLoader@1b446d1] warning javax.* types are not being woven because the weaver option '-Xset:weaveJavaxPackages=true' has not been specified
[WebappClassLoader@1b446d1] error aspect 'com.mytest.web.TaskController_Roo_Controller' woven into 'com.mytest.web.TaskController' must be defined to the weaver (placed on the aspectpath, or defined in an aop.xml file if using LTW).
..


I added 'javaagent:..aspejctjweaver.jar' to Tomcat's vm args but I still get the errors warnings above. Then when I load http://localhost:8080/mytest/task, I get:


Failed to invoke handler method [public java.lang.String com.mytest.web.TaskController.list(java.lang.Integ er,java.lang.Integer,org.springframework.ui.ModelM ap)]; nested exception is java.lang.IllegalStateException: Post-processor tried to replace bean instance of type [com.mytest.domain.Task] with (proxy) object of type [$Proxy36] - not supported for aspect-configured classes!


I added an META-INF/aop.xml:



<?xml version="1.0"?>
<aspectj>
<weaver options="-Xset:weaveJavaPackages=true,weaveJavaxPackages=tru e "/>
<aspects>
<aspect name="com.mytest.web.PersonController_Roo_Controller" />
<aspect name="com.mytest.web.TaskController_Roo_Controller" />
<aspect name="com.mytest.domain.Person_Roo_JavaBean" />
<aspect name="com.mytest.domain.Person_Roo_ToString" />
<aspect name="com.mytest.domain.Person_Roo_Entity" />
<aspect name="com.mytest.domain.Person_Roo_Configurable" />
<aspect name="com.mytest.domain.Task_Roo_JavaBean" />
<aspect name="com.mytest.domain.Task_Roo_ToString" />
<aspect name="com.mytest.domain.Task_Roo_Configurable" />
<aspect name="com.mytest.domain.Task_Roo_Entity" />
</aspects>

</aspectj>


The warning messages went away since I added all of the Roo aspects to my aop.xml, but I still get the "IllegalStateException" error.


For reference, my roo script is:



project --topLevelPackage com.mytest

persistence setup --database HYPERSONIC_IN_MEMORY --provider HIBERNATE

// Task
entity --name ~.domain.Task
field string --fieldName description
field date --fieldName due --type java.util.Date

// Person
entity --name ~.domain.Person
field string name


// relationships
field set --class ~.domain.Person --element ~.domain.Task --fieldName tasks --mappedBy person
field reference --fieldName person --type ~.domain.Person --class ~.domain.Task --notNull

controller all --package ~.web
security setup

Ben Alex
Oct 21st, 2009, 02:01 PM
Roo does compile-time weaving. It does not do load-time weaving, as the inter-type declarations need to be compile-time woven. Did you try it with compile-time weaving, which is the default setup you'd get out-of-the-box?

mikej
Oct 21st, 2009, 05:52 PM
Yes, I only tried adding run-time weaving to get around the exceptions in the first place. I started getting the exception with a very straight-forward project.

Any ideas would be appreciated, though I'm starting to realize this may fall out of the Roo scope.

Here's the full stacktrace (with no run-time weavaing enabled):



Exception in thread "main" java.lang.IllegalStateException: Post-processor tried to replace bean instance of type [com.mytest.domain.Task] with (proxy) object of type [$Proxy30] - not supported for aspect-configured classes!
at org.springframework.beans.factory.wiring.BeanConfi gurerSupport.checkExposedObject(BeanConfigurerSupp ort.java:170)
at org.springframework.beans.factory.wiring.BeanConfi gurerSupport.configureBean(BeanConfigurerSupport.j ava:142)
at org.springframework.beans.factory.aspectj.Annotati onBeanConfigurerAspect.configureBean(AnnotationBea nConfigurerAspect.aj:59)
at org.springframework.beans.factory.aspectj.Abstract DependencyInjectionAspect.ajc$afterReturning$org_s pringframework_beans_factory_aspectj_AbstractDepen dencyInjectionAspect$2$1ea6722c(AbstractDependency InjectionAspect.aj:89)
at com.mytest.domain.Task.<init>(Task.java:19)
at com.mytest.Main.main(Main.java:19)


Here's how I replicated it this time (not using STS or specifying a javaagent at all, just using a Main class and maven):


Ran the script above in a new "mytest" project directory
Added Main.java (below) and executed it successfully (using mvn -e exec:java -Dexec.mainClass="com.mytest.Main")
Added "global-method-security" element to META-INF/spring/applicationSecurity-context.xml
Added dependencies for org.aspectj.weaver and javax.annotation to pom.xml
Executed Main again and got the IllegalStateException stacktrace above


Main.java:


package com.mytest;

import java.util.Date;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlAp plicationContext;

import com.mytest.domain.Person;
import com.mytest.domain.Task;

public class Main {
public static void main(String[] args)
{
System.out.println("starting main");
ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/*.xml");
Person person = new Person();
person.setName("Jimmy");
person.persist();
Task task = new Task();
task.setDescription("my task");
task.setDue(new Date());
task.persist();
Task task2 = Task.findTask(1L);
System.out.println("task: "+task2);
System.out.println("done");
}
}

Ben Alex
Oct 26th, 2009, 02:46 AM
I believe this is because <global-method-security> is internally setting up Spring Security's MethodSecurityInterceptor and that in turn is trying to use Spring AOP to proxy the com.mytest.domain.Task.findTask(..) method. This won't work for two reasons:

1. Task is an entity class and therefore has no interface, so Spring AOP will not work with its normal dynamic proxying approach.

2. Task is already woven with AspectJ, so Spring AOP considers it incompatible.

The correct usage would be to use Spring Security's AspectJSecurityInterceptor instead of the Spring AOP-requiring MethodSecurityInterceptor.

I'll ask Luke Taylor (of Spring Security) whether he can offer any suggestions on the easiest way of configuring Spring Security to achieve the above.

kermiedefrog
Nov 15th, 2009, 07:18 PM
Mike: did you find a solution to this issue... we too are wanting to use this same mix but have stalled because of the same issue...

I would rather NOT have to move away from using the annotations approach of PreAuthorize etc..

cheers

mikej
Nov 16th, 2009, 02:01 PM
Mike: did you find a solution to this issue... we too are wanting to use this same mix but have stalled because of the same issue...

No, sorry.

Ben??? Did you get any tips from Luke on how to configure pre- and post- authorization (perhaps via <global-method-security>) in a roo app?

I think it would have something to do with using AspectJSecurityInterceptor as Ben suggested (http://static.springsource.org/spring-security/site/docs/3.0.x/reference/secure-object-impls.html#aspectj) and using PrePostAnnotationSecurityMetadataSource as your MetadataSource.

kermiedefrog
Nov 16th, 2009, 06:03 PM
With Spring Security V3 I can get @Secured working but only in the Entity itself, ie NOT in the Roo generated aspects... The aspects sample in the Security Source does seem to work as a configuration. However, only within the Entity, which doesn't work for us as we want to put the PreAuth/Secured on the Roo generated finders et al directly...

Spring Security fails within the generated entity call as a result of the Roo_Entity aspect calling org.aspectj.runtime.reflect.makeJP with a null target... though this call succeeds Security fails at another point as it is checking this target value and blowing up because it is null; within AbstractSecurityInterceptor.beforeInvocation...

mikej
Nov 16th, 2009, 10:10 PM
Hmmm...I looked at this a little myself today. Is that because the methods you are trying to secure are static (and static method matches have null targets in aspectj)?

And if that's causing AbstractSecurityInterceptor to blow up, is that a Spring Security bug? (I think it means that you can't use a security pointcut that matches any static methods....)

Would it be fair to open a Roo ticket on getting some kind of support for security annotations (@Secured, @PreAuthorize, e.g.) since this has something to do with the generated Entities being incompatible with <global-method-security>?

kermiedefrog
Nov 17th, 2009, 04:39 AM
Actually you are right there - they are on the Roo_entity static finder methods... I hacked the security code in one place to get the class from the signature rather than the target and @Secured works fine.. When I tried with PrePostAuth I now get a failure within the PrePostAuthVoter as it is coded up for AOP only... Might hack that tomorrow and see what I come up with...

Apparently SSV3.1 might be getting an aspectj version of global-method-securty, which will solve some of my issues but unfortunately not in time for our next release...

mikej
Nov 17th, 2009, 08:48 AM
You're right, I missed that. Nothing for Roo to do but wait :)

According to http://jira.springframework.org/browse/SEC-1232, the aspectj-compatible beans are already in the trunk (but not integrated into namespace config).

kermiedefrog
Nov 17th, 2009, 03:30 PM
there are some and with me we hack I could get the @secured working with roo - ie no aop... However cant use the PreInvocationAuthorizationAdviceVoter as that is still firmly spring aop and fails - it casts to a MethodInvocation... there are about 3+ classes around this area to look at some time today...

kermiedefrog
Nov 17th, 2009, 08:31 PM
I have managed to get a test roo entity fully PreAuthorize'd using aspectj... I have only tested the hasRole stuff out so far and not the other EL combos... will do this soon and post the work-arounds once I have completed that and tried it within our main application...

kermiedefrog
Nov 19th, 2009, 05:08 PM
Here is an example aspectj config file that does PreAuthorize and PostFilter within static and non-static methods within a generated Roo_entity aspect.. Obviously, it work in the entity itself...

There are some code changes that will also need to be made, as you will see by this config file.. I will list these as well.. they are snippets out of the full classed but should be easy to follow.

spring config file:


<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

<bean id="aspectJSecurityInterceptor"
class="org.springframework.security.access.intercept.aspe ctj.AspectJSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource" ref="PrePostAnnotationSecurityMetadataSource"/>
<property name="afterInvocationManager" ref="PostInvocationManager"/>
</bean>

<bean id="PostInvocationManager"
class="org.springframework.security.access.intercept.Afte rInvocationProviderManager">
<property name="providers">
<util:list>
<ref bean="postInvocationProvider"/>
</util:list>
</property>
</bean>


<bean id="PrePostAnnotationSecurityMetadataSource"
class="org.springframework.security.access.prepost.PrePos tAnnotationSecurityMetadataSource">
<constructor-arg ref="ExpressionBasedAnnotationAttributeFactory"/>
</bean>

<bean id="ExpressionBasedAnnotationAttributeFactory"
class="org.springframework.security.access.expression.met hod.ExpressionBasedAnnotationAttributeFactory">
<constructor-arg ref="DefaultMethodSecurityExpressionHandler"/>
</bean>

<bean id="DefaultMethodSecurityExpressionHandler"
class="org.springframework.security.access.expression.met hod.DefaultMethodSecurityExpressionHandler"/>

<bean id="postInvocationProvider" class="org.springframework.security.access.prepost.AJPost InvocationAdviceProvider">
<constructor-arg ref="postAdvice"/>
</bean>
<bean id="postAdvice"
class="org.springframework.security.access.expression.met hod.ExpressionBasedPostInvocationAdvice">
<constructor-arg ref="DefaultMethodSecurityExpressionHandler"/>
</bean>


<bean id="authenticationManager"
class="org.springframework.security.authentication.Provid erManager">
<property name="providers">
<bean
class="org.springframework.security.authentication.Testin gAuthenticationProvider"/>
</property>
</bean>

<bean id="accessDecisionManager" class="org.springframework.security.access.vote.Affirmati veBased">
<property name="decisionVoters">
<list>
<bean class="org.springframework.security.access.prepost.AJPreI nvocationAuthorizationAdviceVoter">
<constructor-arg ref="PreInvocationAuthorizationAdvice"/>
</bean>
</list>
</property>
</bean>

<bean id="PreInvocationAuthorizationAdvice"
class="org.springframework.security.access.expression.met hod.ExpressionBasedPreInvocationAdvice">

</bean>

<bean class="custom.aspects.PrePostAuthoriseFilterAnnotation"
factory-method="aspectOf">
<property name="securityInterceptor" ref="aspectJSecurityInterceptor"/>
</bean>

<bean class="sample.aspectj.Service"/>

<bean class="sample.aspectj.SecuredService"/>
<bean class="template.TemplateDescription"/>

</beans>


custom.aspect.PrePostAuthoriseFilterAnnotation:
In the spring security sample or code I have just change the following line within its aspect - there is only one so wont be hard to find. This is also not quite complete for the PostFilter stuff


private pointcut executionOfAnyPublicMethodInAtSecuredType() :
execution(public * ((@PreAuthorize *)+).*(..)) && @this(PreAuthorize);
// execution(public * ((@Secured *)+).*(..)) && @this(Secured);

private pointcut executionOfSecuredMethod() :
execution(* *(..)) && @annotation(PreAuthorize);




AJPreInvocationAuthorizationAdviceVoter - note messy code :-):


package org.springframework.security.access.prepost;
public class AJPreInvocationAuthorizationAdviceVoter implements AccessDecisionVoter {
public boolean supports(Class<?> clazz)
{
return clazz.isAssignableFrom(org.aspectj.lang.JoinPoint. class);
}

public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {

// Find prefilter and preauth (or combined) attributes
// if both null, abstain
// else call advice with them

PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes);

if (preAttr == null) {
// No expression based metadata, so abstain
return ACCESS_ABSTAIN;
}

JoinPoint jp = (JoinPoint)object;
Object[] args = jp.getArgs();

Object target = jp.getTarget();
Method method = null;
if(target != null)
{
String name = jp.getSignature().getName();

if(args.length == 0)
{
try
{
method = target.getClass().getMethod(name, null);
}
catch (NoSuchMethodException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
// todo: just ignore for now, but fix
}
}
else
{
Class[] types = new Class[args.length];
for (int i = 0; i < args.length; i++)
{
Object arg = args[i];
types[i] = arg.getClass();
}
// try
// {
// method = target.getClass().getMethod(name, types);
// }
// catch (NoSuchMethodException e)
// {
// e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
// // todo: just ignore for now, but fix
// }
}


}

SimpleMethodInvocation invo = new SimpleMethodInvocation(target, method, args);
boolean allowed = preAdvice.before(authentication, invo, preAttr);
// boolean allowed = preAdvice.before(authentication, (MethodInvocation)object, preAttr);

return allowed ? ACCESS_GRANTED : ACCESS_DENIED;
}

}

AJPreInvocationAuthorizationAdvice - note messy code :-):


package org.springframework.security.access.prepost;
public class AJPreInvocationAuthorizationAdviceVoter implements AccessDecisionVoter {
public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {

// Find prefilter and preauth (or combined) attributes
// if both null, abstain
// else call advice with them

PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes);

if (preAttr == null) {
// No expression based metadata, so abstain
return ACCESS_ABSTAIN;
}

JoinPoint jp = (JoinPoint)object;
Object[] args = jp.getArgs();

Object target = jp.getTarget();
Method method = null;
if(target != null)
{
String name = jp.getSignature().getName();

if(args.length == 0)
{
try
{
method = target.getClass().getMethod(name, null);
}
catch (NoSuchMethodException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
// todo: just ignore for now, but fix
}
}
else
{
Class[] types = new Class[args.length];
for (int i = 0; i < args.length; i++)
{
Object arg = args[i];
types[i] = arg.getClass();
}
// try
// {
// method = target.getClass().getMethod(name, types);
// }
// catch (NoSuchMethodException e)
// {
// e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
// // todo: just ignore for now, but fix
// }
}


}

SimpleMethodInvocation invo = new SimpleMethodInvocation(target, method, args);
boolean allowed = preAdvice.before(authentication, invo, preAttr);
// boolean allowed = preAdvice.before(authentication, (MethodInvocation)object, preAttr);

return allowed ? ACCESS_GRANTED : ACCESS_DENIED;
}



Finally

See here http://jira.springframework.org/browse/SEC-1295 for one last file change...

This should be all, any issues just post a question and I will try and help..

Ben Alex
Nov 19th, 2009, 10:46 PM
Thanks very much for sharing this. I am sure many Roo users interested in the subtleties of compile time weaving of static and non-static methods with Spring Roo and Spring Security will be very interested in the above. Thanks again!

cschooley
Feb 18th, 2010, 02:20 PM
@kermiedefrog;

Can you give us a hint on the includes for each file? So far I have:

import org.aspectj.lang.JoinPoint;
import org.springframework.security.access.AccessDecision Voter;
import org.springframework.security.core.Authentication;
import org.springframework.security.util.SimpleMethodInvo cation;

But the type "Method" could be several things - I assume (org.aspectj.apache.bcel.classfile.Method?).

Ben Alex
Feb 19th, 2010, 11:41 PM
For the benefit of the archives, see http://jira.springframework.org/browse/SEC-1232 for a related issue.

mikrobi
Apr 24th, 2010, 05:25 AM
I'm still trying to get annotation based security work in my Roo webapp without any success. Due to Roo the architecture I want to secure some methods within my Roo Controllers. But all @Secured annotation don't have any effect. Without logging in I'm able to access a mehtod like this one:


@Secured("ROLE_ADMIN")
@RequestMapping(value = "/admin/form", method = RequestMethod.GET)
public String createForm(ModelMap modelMap) {
return "admin/create";
}


I'm usign Spring Security 3.1.0

Here is my relevant configuration file: /myApp/src/main/resources/META-INF/spring/applicationContext-security.xml (if more is needed, i'll post it)


<!-- HTTP security configurations -->
<http auto-config="true" use-expressions="true">
<remember-me data-source-ref="dataSource" />
<form-login login-processing-url="/static/j_spring_security_check"
login-page="/login" authentication-failure-url="/login?login_error=t" />
<logout logout-url="/static/j_spring_security_logout" />

<anonymous />
</http>

<!-- Configure Authentication mechanism -->
<beans:bean id="passwordEncoder"
class="org.springframework.security.authentication.encodi ng.ShaPasswordEncoder" />
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="userProfileDao">
<password-encoder ref="passwordEncoder" />
</authentication-provider>
</authentication-manager>


<global-method-security secured-annotations="enabled" mode="aspectj" />

<!-- ACL service definitions -->

<beans:bean id="aclCache"
class="org.springframework.security.acls.domain.EhCacheBa sedAclCache">
<beans:constructor-arg>
<beans:bean class="org.springframework.cache.ehcache.EhCacheFactoryBe an">
<beans:property name="cacheManager">
<beans:bean
class="org.springframework.cache.ehcache.EhCacheManagerFa ctoryBean" />
</beans:property>
<beans:property name="cacheName" value="aclCache" />
</beans:bean>
</beans:constructor-arg>
</beans:bean>

<beans:bean id="lookupStrategy"
class="org.springframework.security.acls.jdbc.BasicLookup Strategy">
<beans:constructor-arg ref="dataSource" />
<beans:constructor-arg ref="aclCache" />
<beans:constructor-arg>
<beans:bean
class="org.springframework.security.acls.domain.AclAuthor izationStrategyImpl">
<beans:constructor-arg>
<beans:list>
<beans:bean
class="org.springframework.security.core.authority.Grante dAuthorityImpl">
<beans:constructor-arg value="ROLE_ADMIN" />
</beans:bean>
<beans:bean
class="org.springframework.security.core.authority.Grante dAuthorityImpl">
<beans:constructor-arg value="ROLE_ADMIN" />
</beans:bean>
<beans:bean
class="org.springframework.security.core.authority.Grante dAuthorityImpl">
<beans:constructor-arg value="ROLE_ADMIN" />
</beans:bean>
</beans:list>
</beans:constructor-arg>
</beans:bean>
</beans:constructor-arg>
<beans:constructor-arg>
<beans:bean
class="org.springframework.security.acls.domain.ConsoleAu ditLogger" />
</beans:constructor-arg>
</beans:bean>

<beans:bean id="aclService"
class="org.springframework.security.acls.jdbc.JdbcMutable AclService">
<beans:constructor-arg ref="dataSource" />
<beans:constructor-arg ref="lookupStrategy" />
<beans:constructor-arg ref="aclCache" />
</beans:bean>


After starting Tomcat the only info I get from org.springframework.security is:



[main] INFO org.springframework.security.access.intercept.aspe ctj.AspectJMethodSecurityInterceptor - Validated configuration attributes


Any hints for getting the annotation to work in a Roo WebApp?

serenapotts
Aug 20th, 2010, 04:53 AM
move global-method-security into webmvc-config.xml

see http://static.springsource.org/spring-security/site/faq/faq.html#faq-method-security-in-web-context