DATAGEODE-194 - Add support to authenticate HTTP requests when using @EnableClusterConfiguration and @EnableSecurity.
This commit is contained in:
@@ -101,7 +101,7 @@ public class ApacheShiroSecurityConfiguration extends AbstractAnnotationConfigSu
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
|
||||
super.setBeanFactory(Optional.ofNullable(beanFactory)
|
||||
.filter(it -> it instanceof ListableBeanFactory)
|
||||
.filter(ListableBeanFactory.class::isInstance)
|
||||
.orElseThrow(() -> newIllegalArgumentException(
|
||||
"BeanFactory [%s] must be an instance of ListableBeanFactory",
|
||||
ObjectUtils.nullSafeClassName(beanFactory))));
|
||||
@@ -192,8 +192,8 @@ public class ApacheShiroSecurityConfiguration extends AbstractAnnotationConfigSu
|
||||
|
||||
try {
|
||||
|
||||
Map<String, Realm> realmBeans = getListableBeanFactory().getBeansOfType(Realm.class,
|
||||
false, false);
|
||||
Map<String, Realm> realmBeans =
|
||||
getListableBeanFactory().getBeansOfType(Realm.class, false, false);
|
||||
|
||||
List<Realm> realms = new ArrayList<>(CollectionUtils.nullSafeMap(realmBeans).values());
|
||||
|
||||
|
||||
@@ -13,21 +13,33 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.gemfire.config.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Authenticator;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.geode.management.internal.security.ResourceConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.Import;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.data.gemfire.config.annotation.support.AutoConfiguredAuthenticationInitializer;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -59,9 +71,105 @@ public class AutoConfiguredAuthenticationConfiguration {
|
||||
protected static final String AUTO_CONFIGURED_AUTH_INIT_STATIC_FACTORY_METHOD =
|
||||
AutoConfiguredAuthenticationInitializer.class.getName().concat(".newAuthenticationInitializer");
|
||||
|
||||
protected static final String DEFAULT_USERNAME = "test";
|
||||
protected static final String DEFAULT_PASSWORD = DEFAULT_USERNAME;
|
||||
protected static final String HTTP_PROTOCOL = "HTTP";
|
||||
protected static final String SECURITY_CLIENT_AUTH_INIT = "security-client-auth-init";
|
||||
protected static final String SECURITY_PEER_AUTH_INIT = "security-peer-auth-init";
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Bean("GemFireSecurityAuthenticator")
|
||||
public Authenticator authenticator(Environment environment) {
|
||||
|
||||
Authenticator authenticator = new Authenticator() {
|
||||
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
|
||||
String username =
|
||||
environment.getProperty(AutoConfiguredAuthenticationInitializer.SDG_SECURITY_USERNAME_PROPERTY,
|
||||
DEFAULT_USERNAME);
|
||||
|
||||
String password =
|
||||
environment.getProperty(AutoConfiguredAuthenticationInitializer.SDG_SECURITY_PASSWORD_PROPERTY,
|
||||
DEFAULT_PASSWORD);
|
||||
|
||||
return new PasswordAuthentication(username, password.toCharArray());
|
||||
}
|
||||
};
|
||||
|
||||
Authenticator.setDefault(authenticator);
|
||||
|
||||
return authenticator;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public ClientHttpRequestInterceptor loggingAwareClientHttpRequestInterceptor() {
|
||||
|
||||
return (request, body, execution) -> {
|
||||
|
||||
logger.debug("HTTP Request URI [{}]", request.getURI());
|
||||
|
||||
Optional.ofNullable(request.getHeaders())
|
||||
.ifPresent(httpHeaders -> {
|
||||
|
||||
CollectionUtils.nullSafeSet(httpHeaders.keySet()).forEach(httpHeaderName -> {
|
||||
logger.debug("HTTP Request Header Name [{}] Value [{}]",
|
||||
httpHeaderName, httpHeaders.get(httpHeaderName));
|
||||
});
|
||||
});
|
||||
|
||||
ClientHttpResponse response = execution.execute(request, body);
|
||||
|
||||
Optional.ofNullable(response)
|
||||
.ifPresent(it -> {
|
||||
|
||||
try {
|
||||
logger.debug("HTTP Response Status Code [{}] Message [{}]",
|
||||
it.getRawStatusCode(), it.getStatusText());
|
||||
}
|
||||
catch (IOException cause) {
|
||||
logger.debug("Error occurred getting HTTP Response Status Code and Message", cause);
|
||||
}
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public ClientHttpRequestInterceptor securityAwareClientHttpRequestInterceptor(Authenticator authenticator) {
|
||||
|
||||
return (request, body, execution) -> {
|
||||
|
||||
URI uri = request.getURI();
|
||||
|
||||
PasswordAuthentication passwordAuthentication =
|
||||
Authenticator.requestPasswordAuthentication(uri.getHost(), null, uri.getPort(),
|
||||
HTTP_PROTOCOL, null, uri.getScheme());
|
||||
|
||||
String username = passwordAuthentication.getUserName();
|
||||
char[] password = passwordAuthentication.getPassword();
|
||||
|
||||
if (isAuthenticationEnabled(username, password)) {
|
||||
|
||||
HttpHeaders requestHeaders = request.getHeaders();
|
||||
|
||||
requestHeaders.add(ResourceConstants.USER_NAME, username);
|
||||
requestHeaders.add(ResourceConstants.PASSWORD, String.valueOf(password));
|
||||
}
|
||||
|
||||
return execution.execute(request, body);
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isAuthenticationEnabled(String username, char[] password) {
|
||||
return StringUtils.hasText(username) && password != null && password.length > 0;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ClientCacheConfigurer authenticationCredentialsSettingClientCacheConfigurer(Environment environment) {
|
||||
return (beanName, beanFactory) -> setAuthenticationCredentials(beanFactory.getProperties(), environment);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.data.gemfire.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
@@ -61,6 +60,7 @@ public class GeodeIntegratedSecurityConfiguration extends EmbeddedServiceConfigu
|
||||
* @see #isShiroSecurityNotConfigured()
|
||||
*/
|
||||
protected boolean isShiroSecurityConfigured() {
|
||||
|
||||
try {
|
||||
//return resolveBean(ApacheShiroSecurityConfiguration.class).isRealmsPresent();
|
||||
return false;
|
||||
@@ -92,6 +92,7 @@ public class GeodeIntegratedSecurityConfiguration extends EmbeddedServiceConfigu
|
||||
(String) annotationAttributes.get("clientAuthenticationInitializer")));
|
||||
|
||||
if (isShiroSecurityNotConfigured()) {
|
||||
|
||||
gemfireProperties.setPropertyIfNotDefault(SECURITY_MANAGER,
|
||||
annotationAttributes.get("securityManagerClass"), Void.class);
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.gemfire.config.annotation.support;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.gemfire.config.annotation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration.ClusterSchemaObjectInitializer;
|
||||
import static org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration.SchemaObjectContext;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.Authenticator;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.geode.management.internal.security.ResourceConstants;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.PropertiesPropertySource;
|
||||
import org.springframework.data.gemfire.config.admin.GemfireAdminOperations;
|
||||
import org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate;
|
||||
import org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Integration Test for {@link EnableClusterConfiguration} and {@link EnableSecurity}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.net.Authenticator
|
||||
* @see java.net.PasswordAuthentication
|
||||
* @see java.net.URI
|
||||
* @see java.util.Properties
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.context.ApplicationContextInitializer
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
* @see org.springframework.core.env.ConfigurableEnvironment
|
||||
* @see org.springframework.core.env.PropertiesPropertySource
|
||||
* @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations
|
||||
* @see org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects
|
||||
* @see org.springframework.http.client.ClientHttpRequestInterceptor
|
||||
* @see org.springframework.http.client.InterceptingClientHttpRequestFactory
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringRunner
|
||||
* @see org.springframework.web.client.RestTemplate
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(initializers = EnableClusterConfigurationWithSecurityIntegrationTests.SecurityConfigurationApplicationContextInitializer.class)
|
||||
public class EnableClusterConfigurationWithSecurityIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("GemFireSecurityAuthenticator")
|
||||
private Authenticator authenticator;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("loggingAwareClientHttpRequestInterceptor")
|
||||
private ClientHttpRequestInterceptor loggingAwareClientHttpRequestInterceptor;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("securityAwareClientHttpRequestInterceptor")
|
||||
private ClientHttpRequestInterceptor securityAwareClientHttpRequestInterceptor;
|
||||
|
||||
@Autowired
|
||||
private ClusterSchemaObjectInitializer initializer;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T resolveFieldValue(Object target, String fieldName) throws NoSuchFieldException {
|
||||
|
||||
Field field = ReflectionUtils.findField(target.getClass(), fieldName);
|
||||
|
||||
return Optional.ofNullable(field)
|
||||
.map(it -> {
|
||||
ReflectionUtils.makeAccessible(it);
|
||||
return field;
|
||||
})
|
||||
.map(it -> (T) ReflectionUtils.getField(it, target))
|
||||
.orElseThrow(() ->
|
||||
new NoSuchFieldException(String.format("Field with name [%s] was not found on Object of type [%s]",
|
||||
fieldName, target.getClass().getName())));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
assertThat(this.authenticator).isNotNull();
|
||||
assertThat(this.initializer).isNotNull();
|
||||
assertThat(this.loggingAwareClientHttpRequestInterceptor).isNotNull();
|
||||
assertThat(this.securityAwareClientHttpRequestInterceptor).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticatorReturnsPasswordAuthenticationWithGemFireSecurityProperties() {
|
||||
|
||||
PasswordAuthentication passwordAuthentication =
|
||||
Authenticator.requestPasswordAuthentication("localhost", null, 80,
|
||||
"https", null, "https");
|
||||
|
||||
assertThat(passwordAuthentication).isNotNull();
|
||||
assertThat(passwordAuthentication.getUserName()).isEqualTo("skeletor");
|
||||
assertThat(String.valueOf(passwordAuthentication.getPassword())).isEqualTo("s3cr3t");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initializerRestTemplateIncludesClientHttpRequestInterceptors() throws Exception {
|
||||
|
||||
SchemaObjectContext schemaObjectContext = this.initializer.getSchemaObjectContext();
|
||||
|
||||
assertThat(schemaObjectContext).isNotNull();
|
||||
assertThat(schemaObjectContext.<GemfireAdminOperations>getGemfireAdminOperations())
|
||||
.isInstanceOf(RestHttpGemfireAdminTemplate.class);
|
||||
|
||||
RestHttpGemfireAdminTemplate template = schemaObjectContext.getGemfireAdminOperations();
|
||||
|
||||
RestTemplate restTemplate = resolveFieldValue(template, "restTemplate");
|
||||
|
||||
assertThat(restTemplate).isNotNull();
|
||||
assertThat(restTemplate.getInterceptors()).containsExactly(this.securityAwareClientHttpRequestInterceptor,
|
||||
this.loggingAwareClientHttpRequestInterceptor);
|
||||
assertThat(restTemplate.getRequestFactory()).isInstanceOf(InterceptingClientHttpRequestFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityAwareClientHttpRequestInterceptorAppliesGemFireSecurityPropertiesToHttpHeaders()
|
||||
throws Exception {
|
||||
|
||||
byte[] body = new byte[0];
|
||||
|
||||
URI uri = URI.create("https://localhost:8080/gemfire/v1");
|
||||
|
||||
ClientHttpRequestExecution mockClientHttpRequestExecution = mock(ClientHttpRequestExecution.class);
|
||||
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
|
||||
HttpRequest mockHttpRequest = mock(HttpRequest.class);
|
||||
|
||||
when(mockHttpRequest.getHeaders()).thenReturn(httpHeaders);
|
||||
when(mockHttpRequest.getURI()).thenReturn(uri);
|
||||
|
||||
this.securityAwareClientHttpRequestInterceptor.intercept(mockHttpRequest, body, mockClientHttpRequestExecution);
|
||||
|
||||
assertThat(httpHeaders).containsKeys(ResourceConstants.USER_NAME, ResourceConstants.PASSWORD);
|
||||
assertThat(httpHeaders.getFirst(ResourceConstants.USER_NAME)).isEqualTo("skeletor");
|
||||
assertThat(httpHeaders.getFirst(ResourceConstants.PASSWORD)).isEqualTo("s3cr3t");
|
||||
|
||||
verify(mockClientHttpRequestExecution, times(1)).execute(eq(mockHttpRequest), eq(body));
|
||||
verify(mockHttpRequest, times(1)).getHeaders();
|
||||
verify(mockHttpRequest, times(1)).getURI();
|
||||
}
|
||||
|
||||
static class SecurityConfigurationApplicationContextInitializer
|
||||
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
|
||||
ConfigurableEnvironment environment = applicationContext.getEnvironment();
|
||||
|
||||
Properties gemfireSecurityProperties = new Properties();
|
||||
|
||||
gemfireSecurityProperties.setProperty("spring.data.gemfire.security.username", "skeletor");
|
||||
gemfireSecurityProperties.setProperty("spring.data.gemfire.security.password", "s3cr3t");
|
||||
|
||||
environment.getPropertySources().
|
||||
addFirst(new PropertiesPropertySource("EnableClusterConfigurationWithSecurityIntegrationTests",
|
||||
gemfireSecurityProperties));
|
||||
}
|
||||
}
|
||||
|
||||
@ClientCacheApplication
|
||||
@EnableSecurity
|
||||
@EnableGemFireMockObjects
|
||||
@EnableClusterConfiguration(useHttp = true)
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
BeanPostProcessor clusterSchemaObjectInitializerPostProcessor() {
|
||||
|
||||
return new BeanPostProcessor() {
|
||||
|
||||
@Nullable @Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
|
||||
if (bean instanceof ClusterSchemaObjectInitializer) {
|
||||
|
||||
ClusterSchemaObjectInitializer initializer = spy((ClusterSchemaObjectInitializer) bean);
|
||||
|
||||
doReturn(false).when(initializer).isAutoStartup();
|
||||
|
||||
bean = initializer;
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user