开发者

How to programmatically add Security to a Spring Bean

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 behavi开发者_StackOverflow社区our as the @Secured annotation.

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


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.


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
     }
     }


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜