From 0796268452c597c71eb8bc41f2191e61e16f90e1 Mon Sep 17 00:00:00 2001 From: John Blum Date: Mon, 31 Oct 2016 21:42:31 -0700 Subject: [PATCH] SGF-559 - Reimmplement the Apache Shiro security component configuration to avoid premature GemFire cache creation and initialization as well as to ensure proper initialization of Apache Shiro Realms. --- .../ApacheShiroSecurityConfiguration.java | 184 +++++++++--------- .../GeodeIntegratedSecurityConfiguration.java | 13 +- .../EmbeddedServiceConfigurationSupport.java | 71 ++++++- 3 files changed, 168 insertions(+), 100 deletions(-) diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java index 2b0a600c..1fa2ff12 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java @@ -18,10 +18,10 @@ package org.springframework.data.gemfire.config.annotation; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.Collections; import java.util.List; - -import javax.annotation.PostConstruct; +import java.util.Map; import org.apache.geode.cache.GemFireCache; import org.apache.geode.internal.security.SecurityService; @@ -29,15 +29,17 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ConfigurationCondition; +import org.springframework.core.OrderComparator; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.data.gemfire.util.CollectionUtils; import org.springframework.util.Assert; @@ -52,43 +54,62 @@ import org.springframework.util.ReflectionUtils; * @author John Blum * @see org.apache.geode.cache.GemFireCache * @see org.apache.geode.internal.security.SecurityService - * @see org.apache.shiro.mgt.SecurityManager + * @see org.apache.shiro.mgt.DefaultSecurityManager + * @see org.apache.shiro.realm.Realm * @see org.apache.shiro.spring.LifecycleBeanPostProcessor + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.beans.factory.ListableBeanFactory + * @see org.springframework.context.annotation.Bean * @see org.springframework.context.annotation.Condition * @see org.springframework.context.annotation.Conditional - * @since 1.0.0 + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.data.gemfire.config.annotation.ApacheShiroSecurityConfiguration.ApacheShiroPresentCondition + * @since 1.9.0 */ @Configuration @Conditional(ApacheShiroSecurityConfiguration.ApacheShiroPresentCondition.class) @SuppressWarnings("unused") -public class ApacheShiroSecurityConfiguration { +public class ApacheShiroSecurityConfiguration implements BeanFactoryAware { - @Autowired(required = false) - private List realms = Collections.emptyList(); - - @Autowired(required = false) - private org.apache.shiro.mgt.SecurityManager shiroSecurityManager; + private ListableBeanFactory beanFactory; /** - * {@link Bean} definition to configure and register an Apache Shiro, Spring {@link LifecycleBeanPostProcessor} - * used to automatically call lifecycle callback methods on Shiro security components during Spring container - * initialization and destruction phases. + * @inheritDoc + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + Assert.isInstanceOf(ListableBeanFactory.class, beanFactory); + this.beanFactory = (ListableBeanFactory) beanFactory; + } + + /** + * Returns a reference to the Spring {@link BeanFactory}. * - * The registration of this {@link Bean} definition is dependent upon whether the user is using Apache Shiro - * to secure Apache Geode, which is determined by the presence of Apache Shiro {@link Realm Realms} - * declared in the Spring {@link org.springframework.context.ApplicationContext}. + * @return a reference to the Spring {@link BeanFactory}. + * @throws IllegalStateException if the Spring {@link BeanFactory} was not properly initialized. + * @see org.springframework.beans.factory.BeanFactory + */ + protected ListableBeanFactory getBeanFactory() { + org.apache.shiro.util.Assert.state(this.beanFactory != null, "BeanFactory was not properly initialized"); + return this.beanFactory; + } + + /** + * {@link Bean} definition to define, configure and register an Apache Shiro Spring + * {@link LifecycleBeanPostProcessor} to automatically call lifecycle callback methods + * on Shiro security components during Spring container initialization and destruction phases. * - * @return an Apache Shiro, Spring {@link LifecycleBeanPostProcessor} bean. + * @return an instance of the Apache Shiro Spring {@link LifecycleBeanPostProcessor} to handle the lifecycle + * of Apache Shiro security framework components. * @see org.apache.shiro.spring.LifecycleBeanPostProcessor */ @Bean - //@Conditional(ShiroRealmsConfigured.class) public BeanPostProcessor shiroLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** - * {@link Bean} definition used to configure and register an Apache Shiro + * {@link Bean} definition to define, configure and register an Apache Shiro * {@link org.apache.shiro.mgt.SecurityManager} implementation to secure Apache Geode. * * The registration of this {@link Bean} definition is dependent upon whether the user is using Apache Shiro @@ -96,39 +117,64 @@ public class ApacheShiroSecurityConfiguration { * declared in the Spring {@link org.springframework.context.ApplicationContext}. * * This {@link Bean} definition declares a dependency on the Apache Geode {@link GemFireCache} instance - * in order to ensure the Geode cache is created and initialized first, thus evaluating any security configuration - * logic internally in Apache Geode that may potentially overwrite the Spring configuration. + * in order to ensure the Geode cache is created and initialized first. This ensures that any internal Geode + * security configuration logic is evaluated and processed before SDG attempts to configure Apache Shiro + * as Apache Geode's security provider. * + * Additionally, this {@link Bean} definition will register the Apache Shiro + * {@link org.apache.geode.security.SecurityManager} with the Apache Shiro security framework + * + * Finally, this method proceeds to enable Apache Geode security. + * @return an Apache Shiro {@link org.apache.shiro.mgt.SecurityManager} implementation used to secure Apache Geode. + * @throws IllegalStateException if an Apache Shiro {@link org.apache.shiro.mgt.SecurityManager} was registered + * with the Apache Shiro security framework but Apache Geode security could not be enabled. * @see org.apache.shiro.mgt.SecurityManager + * @see #registerSecurityManager(org.apache.shiro.mgt.SecurityManager) + * @see #enableApacheGeodeSecurity() * @see #isRealmsPresent() * @see #getRealms() */ @Bean - //@Conditional(ShiroRealmsConfigured.class) public org.apache.shiro.mgt.SecurityManager shiroSecurityManager(GemFireCache gemfireCache) { - return (isRealmsPresent() ? new DefaultSecurityManager(getRealms()) : null); - } + org.apache.shiro.mgt.SecurityManager shiroSecurityManager = null; - /** - * Post processes the Apache Shiro security components by registering the Apach Shiro - * {@link org.apache.shiro.mgt.SecurityManager} if present with the Apache Shiro security framework - * and proceeds to enable Apache Geode security. - * - * @throws IllegalStateException if an Apache Shiro {@link org.apache.shiro.mgt.SecurityManager} is present - * and Apache Geode security could not be enabled. - * @see #registerSecurityManager(org.apache.shiro.mgt.SecurityManager) - * @see #enableApacheGeodeSecurity() - */ - @PostConstruct - public void postProcess() { - if (this.shiroSecurityManager != null) { - registerSecurityManager(this.shiroSecurityManager); + List realms = resolveRealms(); + + if (!realms.isEmpty()) { + shiroSecurityManager = registerSecurityManager(new DefaultSecurityManager(realms)); if (!enableApacheGeodeSecurity()) { throw new IllegalStateException("Failed to enable security services in Apache Geode"); } } + + return shiroSecurityManager; + } + + /** + * Resolves all the Apache Shiro {@link Realm Realms} declared and configured as Spring managed beans + * in the Spring {@link org.springframework.context.ApplicationContext}. + * + * This method will order the Realms according to priority order to ensure that the Apache Shiro Realms + * are applied in the correct sequence, as declared/configured. + * + * @return a {@link List} of all Apache Shiro {@link Realm Realms} declared and configured as Spring managed beans + * in the Spring {@link org.springframework.context.ApplicationContext}. + * @see org.springframework.beans.factory.ListableBeanFactory#getBeansOfType(Class, boolean, boolean) + * @see org.springframework.core.OrderComparator + * @see org.apache.shiro.realm.Realm + */ + protected List resolveRealms() { + try { + Map realmBeans = getBeanFactory().getBeansOfType(Realm.class, false, true); + List realms = new ArrayList<>(CollectionUtils.nullSafeMap(realmBeans).values()); + Collections.sort(realms, OrderComparator.INSTANCE); + return realms; + } + catch (Exception ignore) { + return Collections.emptyList(); + } } /** @@ -177,29 +223,6 @@ public class ApacheShiroSecurityConfiguration { return false; } - /** - * Returns the {@link List} of Apache Shiro {@link Realm Realms} configured in - * this Spring {@link org.springframework.context.ApplicationContext}. - * - * @return a {@link List} of configured/declared Apache Shiro {@link Realm Realms}. - * @see org.apache.shiro.realm.Realm - */ - protected List getRealms() { - return this.realms; - } - - /** - * Determines whether any Apache Shiro {@link Realm Realms} were configured in - * this Spring {@link org.springframework.context.ApplicationContext}. - * - * @return a boolean value indicating whether any Apache Shiro {@link Realm Realms} were declared and configured - * in this Spring {@link org.springframework.context.ApplicationContext}. - * @see #getRealms() - */ - protected boolean isRealmsPresent() { - return !CollectionUtils.isEmpty(getRealms()); - } - /** * A Spring {@link Condition} to determine whether the user has included (declared) the 'shiro-spring' dependency * on their application's classpath, which is necessary for configuring Apache Shiro to secure Apache Geode @@ -209,7 +232,7 @@ public class ApacheShiroSecurityConfiguration { */ public static class ApacheShiroPresentCondition implements Condition { - protected static final String APACHE_SHIRO_LIFECYCLE_BEAN_POST_PROCESOR_CLASS_NAME = + protected static final String APACHE_SHIRO_LIFECYCLE_BEAN_POST_PROCESSOR_CLASS_NAME = "org.apache.shiro.spring.LifecycleBeanPostProcessor"; /** @@ -217,39 +240,8 @@ public class ApacheShiroSecurityConfiguration { */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { - return ClassUtils.isPresent(APACHE_SHIRO_LIFECYCLE_BEAN_POST_PROCESOR_CLASS_NAME, + return ClassUtils.isPresent(APACHE_SHIRO_LIFECYCLE_BEAN_POST_PROCESSOR_CLASS_NAME, context.getClassLoader()); } } - - /** - * A Spring {@link Condition} implementation that determines whether the user declared and configured - * any Apache Shiro {@link Realm Realms}, which are necessary to configure the Apache Shiro security framework - * with security meta-data used to secure Apache Geode. - * - * @see org.springframework.context.annotation.ConfigurationCondition - */ - public static class ShiroRealmsConfigured implements ConfigurationCondition { - - /** - * @inheritDoc - */ - @Override - public ConfigurationPhase getConfigurationPhase() { - return ConfigurationPhase.REGISTER_BEAN; - } - - /** - * @inheritDoc - */ - @Override - public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { - ListableBeanFactory beanFactory = context.getBeanFactory(); - - ApacheShiroSecurityConfiguration securityConfiguration = - beanFactory.getBean(ApacheShiroSecurityConfiguration.class); - - return (securityConfiguration.isRealmsPresent() || !beanFactory.getBeansOfType(Realm.class).isEmpty()); - } - } } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/GeodeIntegratedSecurityConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/GeodeIntegratedSecurityConfiguration.java index 2d062023..b7b3ff5b 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/GeodeIntegratedSecurityConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/GeodeIntegratedSecurityConfiguration.java @@ -20,6 +20,7 @@ package org.springframework.data.gemfire.config.annotation; import java.util.Map; import java.util.Properties; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.data.gemfire.config.annotation.support.EmbeddedServiceConfigurationSupport; import org.springframework.data.gemfire.util.PropertiesBuilder; @@ -32,7 +33,8 @@ import org.springframework.data.gemfire.util.PropertiesBuilder; * @since 1.0.0 */ @SuppressWarnings("unused") -public class GeodeIntegratedSecurityConfiguration extends EmbeddedServiceConfigurationSupport { +public class GeodeIntegratedSecurityConfiguration extends EmbeddedServiceConfigurationSupport + implements BeanFactoryAware { protected static final String SECURITY_CLIENT_AUTH_INIT = "security-client-auth-init"; protected static final String SECURITY_PEER_AUTH_INIT = "security-peer-auth-init"; @@ -50,7 +52,14 @@ public class GeodeIntegratedSecurityConfiguration extends EmbeddedServiceConfigu /* (non-Javadoc) */ protected boolean isShiroSecurityConfigured() { - return false; + try { + // NOTE experimental... + //return resolveBean(ApacheShiroSecurityConfiguration.class).isRealmsPresent(); + return false; + } + catch (Exception ignore) { + return false; + } } /* (non-Javadoc) */ diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/support/EmbeddedServiceConfigurationSupport.java b/src/main/java/org/springframework/data/gemfire/config/annotation/support/EmbeddedServiceConfigurationSupport.java index b103ff61..0580e8e6 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/support/EmbeddedServiceConfigurationSupport.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/support/EmbeddedServiceConfigurationSupport.java @@ -21,9 +21,13 @@ import java.util.Map; import java.util.Properties; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.NamedBeanHolder; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -39,12 +43,14 @@ import org.springframework.util.StringUtils; * of Pivotal GemFire and Apache Geode embedded services. * * @author John Blum + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.BeanFactoryAware * @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar * @see org.springframework.data.gemfire.config.annotation.AbstractCacheConfiguration * @since 1.9.0 */ @SuppressWarnings("unused") -public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanDefinitionRegistrar { +public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanDefinitionRegistrar, BeanFactoryAware { public static final Integer DEFAULT_PORT = 0; public static final String DEFAULT_HOST = "localhost"; @@ -53,6 +59,8 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD @SuppressWarnings("all") private AbstractCacheConfiguration cacheConfiguration; + private BeanFactory beanFactory; + /** * Returns a reference to an instance of the {@link AbstractCacheConfiguration} class used to configure * a GemFire (Singleton, client or peer) cache instance along with it's associated, embedded services. @@ -99,6 +107,26 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD return getAnnotationType().getSimpleName(); } + /** + * @inheritDoc + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + /** + * Returns a reference to the Spring {@link BeanFactory}. + * + * @return a reference to the Spring {@link BeanFactory}. + * @throws IllegalStateException if the Spring {@link BeanFactory} was not properly initialized. + * @see org.springframework.beans.factory.BeanFactory + */ + protected BeanFactory getBeanFactory() { + org.apache.shiro.util.Assert.state(this.beanFactory != null, "BeanFactory was not properly initialized"); + return this.beanFactory; + } + /** * {@inheritDoc} */ @@ -185,6 +213,38 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD return String.format("%1$s.%2$s", getClass().getName(), nameQualifier); } + /** + * Resolves a Spring managed bean with the given {@link Class} type from the Spring {@link BeanFactory}. + * + * It is assumed that the given typed bean is the only bean of this {@link Class} type. If more than 1 bean + * of the given {@link Class} type is found, then the Spring {@link BeanFactory} will throw + * a {@link org.springframework.beans.factory.NoUniqueBeanDefinitionException}. + * + * If the {@link BeanFactory} is an instance of {@link AutowireCapableBeanFactory}, then the returned bean + * will also be configured. + * + * @param {@link Class} type of the registered Spring managed bean. + * @param beanType required {@link Class} type of the registered Spring managed bean. + * @return a Spring managed bean instance for the given, required {@link Class} type, or {@literal null} + * if no bean instance of the given, required {@link Class} type could be found. + * @throws BeansException if the Spring manage bean of the required {@link Class} type could not be resolved. + * @see #getBeanFactory() + */ + @SuppressWarnings("unchecked") + protected T resolveBean(Class beanType) { + BeanFactory beanFactory = getBeanFactory(); + + if (beanFactory instanceof AutowireCapableBeanFactory) { + AutowireCapableBeanFactory autowiringBeanFactory = (AutowireCapableBeanFactory) beanFactory; + NamedBeanHolder beanHolder = autowiringBeanFactory.resolveNamedBean(beanType); + + return (T) autowiringBeanFactory.configureBean(beanHolder.getBeanInstance(), beanHolder.getBeanName()); + } + else { + return beanFactory.getBean(beanType); + } + } + /* (non-Javadoc) */ protected String resolveHost(String hostname) { return resolveHost(hostname, DEFAULT_HOST); @@ -217,7 +277,14 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD private final Properties gemfireProperties; - /* (non-Javadoc) */ + /** + * Construct an instance of the {@link GemFirePropertiesBeanPostProcessor} initialized with + * the given GemFire {@link Properties}. + * + * @param gemfireProperties {@link Properties} used to configure GemFire. + * @throws IllegalArgumentException if the {@link Properties} are null or empty. + * @see java.util.Properties + */ protected GemFirePropertiesBeanPostProcessor(Properties gemfireProperties) { Assert.notEmpty(gemfireProperties, "GemFire Properties must not be null or empty"); this.gemfireProperties = gemfireProperties;