views:

711

answers:

3

I´m using spring-security-tiger-2.0.5.

Is there a way to programmatically add a security proxy to a Spring Bean?

I´m constructing the bean through BeanDefinitionBuilder, and i´d like to add same behaviour as the @Secured annotation.

The roleName´s @Secured equivalent will be passed as a parameter.

A: 

you should check out the org.springframework.aop.framework.ProxyFactory class. Wrap your bean with ProxyFactory and add the security interceptor

MCA
I´ve already seen this class, but couldn´t manage to add the security interceptor the right way.1) What is the correct class? (I´ve tried MethodSecurityInterceptor)2) How to set the correct ROLES?Tks.
Luiz Henrique Martins Lins Rol
It should be something like : new ProxyFactory(new YourBean()).addAdvice(new MethodSecurityInterceptor()); and YourBean's methods has to be annotated with @Secured("ROLE_ADMIN")
MCA
OK, but my bean cannot be annotated with @Secured, because the roleName is passed as a parameter (Sometimes "ADMIN", sometimes "USER", etc...)tks.
Luiz Henrique Martins Lins Rol
than may be you should check-out something like seam has, you could do like; @Secured("#{user.roles}")
MCA
A: 

To programmatically inject the Spring Security functionality into existing beans, you may need to use a Spring Security applicaton context and register your beans there:

@Test
public void testSpringSecurity() throws Exception {
 InMemoryXmlApplicationContext ctx = initSpringAndSpringSecurity();

 // Creates new instance
 IMyService secured = (IMyService) ctx.getAutowireCapableBeanFactory()
   .initializeBean(new MyService(), "myService");

 assertTrue(AopUtils.isAopProxy(secured));

 fakeSecurityContext("ROLE_USER");
 secured.getCustomers(); // Works: @Secured("ROLE_USER")

 fakeSecurityContext("ROLE_FOO");
 try {
  secured.getCustomers(); // Throws AccessDenied Exception
  fail("AccessDeniedException expected");
 } catch (AccessDeniedException expected) {
 }
}

private InMemoryXmlApplicationContext initSpringAndSpringSecurity() {
 InMemoryXmlApplicationContext ctx = new InMemoryXmlApplicationContext(
   "<b:bean id='authenticationManager' class='org.springframework.security.MockAuthenticationManager' /> "
     + "<b:bean id='accessDecisionManager' class='org.springframework.security.vote.UnanimousBased'><b:property name='decisionVoters'><b:list><b:bean class='org.springframework.security.vote.RoleVoter'/></b:list></b:property></b:bean>"
     + "<b:bean id='objectDefinitionSource' class='org.springframework.security.annotation.SecuredMethodDefinitionSource' /> "
     + "<b:bean id='autoproxy' class='org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator'/>"
     + "<b:bean id='methodSecurityAdvisor' class='org.springframework.security.intercept.method.aopalliance.MethodDefinitionSourceAdvisor' autowire='constructor'/>"
     + "<b:bean id='securityInterceptor' class='org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor'><b:property name='authenticationManager' ref='authenticationManager' /><b:property name='accessDecisionManager' ref='accessDecisionManager' /><b:property name='objectDefinitionSource' ref='objectDefinitionSource' /></b:bean>");
 return ctx;
}

I used an in-memory application context, as the MethodDefinitionSourceAdvisor states in its documentation that auto-proxying is only enabled for ApplicationContexts. Thus, i belief that you need an app context for auto-proxying, if you don't use a separate ProxyFactoryBean for each service object. But since you're using the @Secured annotation, i suspect that this is the same as auto-proxying.

The fakeSecurityContext() just set an Authentication object with the given role into the SecurityContextHolder for testing purposes.

You can do it with Spring Core functionality on your own. Assume you've got a service which returns a List of Customers and the current user may only view Customers with up to a specific limit of revenue. The following test case will be our start:

@Test
public void testSecurity() throws Exception {
 ClassPathXmlApplicationContext appCtx = new ClassPathXmlApplicationContext(
   "spring.xml");
 IMyService service = (IMyService) appCtx.getBean("secured",
   IMyService.class);
 assertEquals(1, service.getCustomers().size());
}

This is the original service implementation:

public class MyService implements IMyService {

 public List<Customer> getCustomers() {
  return Arrays.asList(new Customer(100000), new Customer(5000));
 }

}

Configure your service object in spring.xml and add the method interceptor:

<bean id="service" class="de.mhaller.spring.MyService"></bean>

<bean id="securityInterceptor" class="de.mhaller.spring.MyServiceInterceptor">
 <property name="revenueLimit" value="50000"/>
</bean>

<bean id="secured" class="org.springframework.aop.framework.ProxyFactoryBean">
 <property name="targetName" value="service" />
 <property name="interceptorNames">
  <list>
   <value>securityInterceptor</value>
  </list>
 </property>
</bean>

The security interceptor implementation:

public class MyServiceInterceptor implements MethodInterceptor {

 private int revenueLimit = 10000;
 public void setRevenueLimit(int revenueLimit) {
  this.revenueLimit = revenueLimit;
 }

 @SuppressWarnings("unchecked")
 public Object invoke(MethodInvocation mi) throws Throwable {
  List<Customer> filtered = new ArrayList<Customer>();
  List<Customer> result = (List<Customer>) mi.proceed();
  for (Customer customer : result) {
   if (customer.isRevenueBelow(revenueLimit)) {
    filtered.add(customer);
   }
  }
  return filtered;
 }

}

The advantage of using such an approach is that you not only can check declaratively for roles of the current user, but also enforce corporate policies in a dynamic way, e.g. limit the returned Objects based on business values.

mhaller
Hi, thanks for the nice answer, but what i´m looking for is a little bit different.I´m using spring-security, with pointcut-based interceptors, so that only some roles are allowed for a given method invocation.My Services, are being registered into spring programmatically, so i cannot use the ApplicationContext approach.I don´t want to re-implement spring-security funcionality in my own proxy, but to use it the same way as it was declared in the applicationContext, but creating it programmatically.TKS.
Luiz Henrique Martins Lins Rol
A: 

Hi guys, thanks for the answers, but I finnally got it working in a totally programatic way.It wasn´t an easy solution, but I´d like to share It.

First of all, I could split the problem into 2 parts:

1) Create the proxy for my own beans.

2) Add the security-roles to every bean. Unfortunately, i couldn´t add a xml-based pointcut, as the beans were all of the same class (a generic service). I wanted a way to secure each bean in a different way, despite they were all the same class. I decided to use the same declaration used by aopalliance ((execution . etc...)

For whom it may interest, this is how I done:

1) Used spring´s BeanNameAutoProxyCreator class, for creating the proxies around the classes i have manually added:

private void addProxies(ConfigurableListableBeanFactory beanFactory, List<String> exportedServices) {
  List<String> interceptorNames = findInterceptorNames(beanFactory);
  BeanNameAutoProxyCreator beanPostProcessor = new BeanNameAutoProxyCreator();
  beanPostProcessor.setBeanNames(exportedServices.toArray(new String[exportedServices.size()]));
  beanPostProcessor.setInterceptorNames(interceptorNames.toArray(new String[interceptorNames.size()]));
  beanPostProcessor.setBeanFactory(beanFactory);
  beanPostProcessor.setOrder(Ordered.LOWEST_PRECEDENCE);
  beanFactory.addBeanPostProcessor(beanPostProcessor);
 }

 @SuppressWarnings("unchecked")
 private List<String> findInterceptorNames(ConfigurableListableBeanFactory beanFactory) {
  List<String> interceptorNames = new ArrayList<String>();
  List<? extends Advisor> list = new BeanFactoryAdvisorRetrievalHelper(beanFactory).findAdvisorBeans();
  for (Advisor ad : list) {
   Advice advice = ad.getAdvice();
   if (advice instanceof MethodInterceptor) {
    Map<String, ?> beansOfType = beanFactory.getBeansOfType(advice.getClass());
    for (String name : beansOfType.keySet()) {
     interceptorNames.add(name);
    }
   }
  }
  return interceptorNames;
 }
  • where exportedServices stands for the beans i wished to proxy
  • The beanFactory instance could be fetched using my own implementation of BeanFactoryPostProcessor

2) For the parameterizable secured-like part:

Created a implementation of spring´s MethodDefinitionSource. implementors of this interface are used by MethodSecurityProxy to fetch securedObjects at runtime.

   @SuppressWarnings("unchecked")
    @Override
 public ConfigAttributeDefinition getAttributes(Object object) throws                                              IllegalArgumentException {
 if (!(object instanceof ReflectiveMethodInvocation)) {
  return null;
 }
 ReflectiveMethodInvocation invokation = (ReflectiveMethodInvocation) object;
 if (!(invokation.getThis() instanceof MyService)) {
  return null;
 }
 MyService service = (MyService) invokation.getThis();
  Map<String, String> map =getProtectedServiceMethodMap(service);
  String roles = map.get(MethodKeyGenerator.generate(invokation.getMethod()));
  return roles == null ? null : new ConfigAttributeDefinition(StringUtils.delimitedListToStringArray(roles, ","));
 }
  • Note that I had to put in a cache the methods wich I wanted to secure for each object instance.

The trickiest part was to register my MethodDefinitionSource impl into the applicationContext, so that the MethodSecurityInterceptor became aware of it:

   private void addCrudDefinitionSource() {
 DelegatingMethodDefinitionSource source = (DelegatingMethodDefinitionSource)    beanFactory.getBean(BeanIds.DELEGATING_METHOD_DEFINITION_SOURCE);
     try {
     Field field = source.getClass().getDeclaredField("methodDefinitionSources");
     field.setAccessible(true);
     List list = (List) field.get(source);
     list.add(new MyMethodDefinitionSource());
     } catch (Exception e) {
     e.printStackTrace();
     }
            }

Last, for checking Method entrypoints in a aop-style I used code like the following:

  private void addToCache(){ // Ommiting parameters
     PointcutExpression expression = parser.parsePointcutExpression(fullExpression);
     Method[] methods = clazzToCheck.getMethods();
     for (int i = 0; i < methods.length; i++) {
     Method currentMethod = methods[i];
     boolean matches = expression.matchesMethodExecution(currentMethod).alwaysMatches();
     if (matches){
 //Add to Cache
     }
     }
Luiz Henrique Martins Lins Rol