Edit guide and refactor example of Spring Boot auto-configuration for Apache Geode/Pivotal GemFire to include Security with Authentication.

This commit is contained in:
John Blum
2019-04-22 23:44:40 -07:00
parent 4f52e19acd
commit 58d9970ce1
7 changed files with 377 additions and 12 deletions

View File

@@ -0,0 +1,192 @@
/*
* Copyright 2019 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
*
* https://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 example.app.crm.config;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.util.Optional;
import org.apache.geode.management.internal.security.ResourceConstants;
import org.apache.shiro.util.Assert;
import org.apache.shiro.util.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate;
import org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration;
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.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.client.RestTemplate;
/**
* Spring {@link Configuration} class used to configure and initialize a Java Platform {@link Authenticator},
* which is used by the Java Platform anytime a Java process needs to make a secure network connection.
*
* @author John Blum
* @see java.net.Authenticator
* @see java.net.PasswordAuthentication
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.context.annotation.Profile
* @see org.springframework.core.env.Environment
* @since 1.0.0
*/
@Configuration
@Profile("security")
@SuppressWarnings("unused")
public class HttpSecurityConfiguration {
private static final String DEFAULT_USERNAME = "test";
private static final String DEFAULT_PASSWORD = DEFAULT_USERNAME;
@Bean
public Authenticator authenticator(Environment environment) {
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
String username =
environment.getProperty("spring.data.gemfire.security.username", DEFAULT_USERNAME);
String password =
environment.getProperty("spring.data.gemfire.security.password", DEFAULT_PASSWORD);
return new PasswordAuthentication(username, password.toCharArray());
}
};
Authenticator.setDefault(authenticator);
return authenticator;
}
@Bean
BeanPostProcessor schemaObjectInitializerPostProcessor(Environment environment) {
return new BeanPostProcessor() {
@Nullable @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ClusterConfigurationConfiguration.ClusterSchemaObjectInitializer) {
Optional.of(bean)
.map(ClusterConfigurationConfiguration.ClusterSchemaObjectInitializer.class::cast)
.map(schemaObjectInitializer -> invokeMethod(schemaObjectInitializer, "getSchemaObjectContext"))
.filter(ClusterConfigurationConfiguration.SchemaObjectContext.class::isInstance)
.map(ClusterConfigurationConfiguration.SchemaObjectContext.class::cast)
.map(schemaObjectContext -> invokeMethod(schemaObjectContext, "getGemfireAdminOperations"))
.filter(RestHttpGemfireAdminTemplate.class::isInstance)
.map(RestHttpGemfireAdminTemplate.class::cast)
.map(gemfireAdminTemplate -> invokeMethod(gemfireAdminTemplate, "getRestOperations"))
.filter(RestTemplate.class::isInstance)
.map(RestTemplate.class::cast)
.ifPresent(restTemplate -> registerInterceptor(restTemplate,
new SecurityAwareClientHttpRequestInterceptor(environment)));
}
return bean;
}
};
}
private RestTemplate registerInterceptor(RestTemplate restTemplate,
ClientHttpRequestInterceptor clientHttpRequestInterceptor) {
restTemplate.getInterceptors().add(clientHttpRequestInterceptor);
return restTemplate;
}
@SuppressWarnings("unchecked")
private <T> T invokeMethod(Object target, String methodName) {
return this.doOperationSafely(() -> {
Method method = target.getClass().getDeclaredMethod(methodName);
ReflectionUtils.makeAccessible(method);
return (T) ReflectionUtils.invokeMethod(method, target);
});
}
private <T> T doOperationSafely(ExceptionThrowingOperation<T> operation) {
try {
return operation.doOperation();
}
catch (Exception ignore) {
return null;
}
}
@FunctionalInterface
interface ExceptionThrowingOperation<T> {
T doOperation() throws Exception;
}
public static class SecurityAwareClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
private final Environment environment;
public SecurityAwareClientHttpRequestInterceptor(Environment environment) {
Assert.notNull(environment, "Environment is required");
this.environment = environment;
}
protected boolean isAuthenticationEnabled() {
return StringUtils.hasText(getUsername()) && StringUtils.hasText(getPassword());
}
protected String getUsername() {
return this.environment.getProperty("spring.data.gemfire.security.username");
}
protected String getPassword() {
return this.environment.getProperty("spring.data.gemfire.security.password");
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
HttpHeaders requestHeaders = request.getHeaders();
if (isAuthenticationEnabled()) {
requestHeaders.add(ResourceConstants.USER_NAME, getUsername());
requestHeaders.add(ResourceConstants.PASSWORD, getPassword());
}
return execution.execute(request, body);
}
}
}

View File

@@ -0,0 +1,4 @@
# Spring Boot application.properties containing Spring Data GemFire Security properties
spring.data.gemfire.security.username=test
spring.data.gemfire.security.password=test

View File

@@ -1 +1,3 @@
# Spring Boot application.properties containing Spring Data GemFire properties
spring.data.gemfire.cache.log-level=error

View File

@@ -0,0 +1,5 @@
# Gfsh shell script to start a secure GemFire/Geode cluster
start locator --name=LocatorOne --classpath=${SBDG_HOME}/apache-geode-extensions/build/libs/apache-geode-extensions-1.0.0.BUILD-SNAPSHOT.jar --properties-file=${SBDG_HOME}/spring-geode-samples/boot/configuration/src/main/resources/geode/config/gemfire.properties
connect --user=test --password=test
start server --name=ServerOne --classpath=${SBDG_HOME}/apache-geode-extensions/build/libs/apache-geode-extensions-1.0.0.BUILD-SNAPSHOT.jar --properties-file=${SBDG_HOME}/spring-geode-samples/boot/configuration/src/main/resources/geode/config/gemfire.properties

View File

@@ -0,0 +1,4 @@
#!/bin/bash
gfsh -e "run --file=${SBDG_HOME}/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/start-secure-cluster.gfsh"
#gfsh -e "run --file=/Users/jblum/pivdev/spring-boot-data-geode/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/start-secure-cluster.gfsh"

View File

@@ -0,0 +1,7 @@
# GemFire Properties
log-level=config
security-manager=org.springframework.geode.security.TestSecurityManager
#security-peer-auth-init=org.springframework.geode.security.TestAuthInitialize.create
security-username=test
security-password=test