【问题标题】:How to programmatically add Security to a Spring Bean如何以编程方式将安全性添加到 Spring Bean
【发布时间】:2010-12-29 21:04:55
【问题描述】:

我正在使用 spring-security-tiger-2.0.5。

有没有办法以编程方式向 Spring Bean 添加安全代理?

我正在通过 BeanDefinitionBuilder 构造 bean,并且我想添加与 @Secured 注释相同的行为。

roleName 的 @Secured 等效项将作为参数传递。

【问题讨论】:

    标签: java spring spring-security


    【解决方案1】:

    感谢您的回答,但我终于让它以完全程序化的方式工作。这不是一个简单的解决方案,但我想分享它。

    首先,我可以把问题分成两部分:

    1) 为我自己的 bean 创建代理。

    2) 将安全角色添加到每个 bean。不幸的是,我无法添加基于 xml 的切入点,因为 bean 都是同一个类(通用服务)。我想要一种以不同方式保护每个 bean 的方法,尽管它们都是同一个类。 我决定使用与 aopalliance 相同的声明((执行 . 等...)

    对于可能感兴趣的人,我是这样做的:

    1) 使用 spring 的 BeanNameAutoProxyCreator 类,用于围绕我手动添加的类创建代理:

    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;
     }
    
    • exportedServices 代表我希望代理的 bean
    • 可以使用我自己的 BeanFactoryPostProcessor 实现来获取 beanFactory 实例

    2) 对于可参数化的类安全部分:

    创建了 spring 的 MethodDefinitionSource 的实现。 MethodSecurityProxy 使用此接口的实现者在运行时获取securedObjects。

       @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, ","));
     }
    
    • 请注意,我必须将我希望为每个对象实例保护的方法放入缓存中。

    最棘手的部分是将我的 MethodDefinitionSource impl 注册到 applicationContext 中,以便 MethodSecurityInterceptor 意识到它:

       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();
         }
                }
    

    最后,为了检查 aop 样式的方法入口点,我使用了如下代码:

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

    【讨论】:

      【解决方案2】:

      要以编程方式将 Spring Security 功能注入现有 bean,您可能需要使用 Spring Security 应用程序上下文并在那里注册您的 bean:

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

      我使用了内存中的应用程序上下文,正如 MethodDefinitionSourceAdvisor 在其文档中指出的那样,仅对 ApplicationContexts 启用了自动代理。因此,如果您不为每个服务对象使用单独的ProxyFactoryBean,我相信您需要一个用于自动代理的应用程序上下文。但是由于您使用的是@Secured 注释,我怀疑这与自动代理相同。

      fakeSecurityContext() 只是将具有给定角色的Authentication 对象设置到SecurityContextHolder 中以用于测试目的。

      您可以自己使用 Spring Core 功能来完成。假设您有一个返回客户列表的服务,并且当前用户只能查看具有特定收入限制的客户。下面的测试用例将是我们的开始:

      @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());
      }
      

      这是原来的服务实现:

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

      spring.xml中配置你的服务对象并添加方法拦截器:

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

      安全拦截器实现:

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

      使用这种方法的优势在于,您不仅可以以声明方式检查当前用户的角色,还可以以动态方式执行公司策略,例如根据业务价值限制返回的 Objects。

      【讨论】:

      • 嗨,谢谢你的好答案,但我正在寻找的是有点不同。我正在使用带有基于切入点的拦截器的 spring-security,因此对于给定的方法调用只允许某些角色。我的服务正在以编程方式注册到 spring,所以我不能使用 ApplicationContext 方法。我不想在我自己的代理中重新实现 spring-security 功能,而是以与在 applicationContext 中声明的方式相同的方式使用它,但以编程方式创建它。 TKS。
      【解决方案3】:

      您应该查看 org.springframework.aop.framework.ProxyFactory 班级。用 ProxyFactory 包装你的 bean 并添加安全拦截器

      【讨论】:

      • 我已经看过这个类,但无法以正确的方式添加安全拦截器。 1)什么是正确的类? (我试过 MethodSecurityInterceptor) 2)如何设置正确的角色?谢了。
      • 应该是这样的: new ProxyFactory(new YourBean()).addAdvice(new MethodSecurityInterceptor());并且 YourBean 的方法必须使用 @Secured("ROLE_ADMIN") 进行注释
      • 好的,但是我的 bean 不能用 @Secured 注释,因为 roleName 作为参数传递(有时是“ADMIN”,有时是“USER”等)tks。
      • 也许你应该检查像缝这样的东西,你可以这样做; @Secured("#{user.roles}")
      猜你喜欢
      • 2017-08-18
      • 2015-01-31
      • 1970-01-01
      • 2011-05-31
      • 1970-01-01
      • 2018-05-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多