Prefer file resolution when loading SSL content
Update `SslAutoConfiguration` so that the used resource loader prefers file based resolution when paths are specified without a prefix. This restores the behavior found in Spring Boot 3.3. The `ApplicationResourceLoader` has been updated with a new `get` method that accepts a `preferFileResolution` parameter. Unfortunately, we can't directly influence the resource returned by the delegate `ResourceLoader` since we can't override `getResourceByPath(...)`. Instead we check if the returned type was likely to have been created by a call to that method. If so, we change it to a `FileSystemResource`. This approach should hopefully work with `DefaultResourceLoader` and subclasses. Fixes gh-43274
This commit is contained in:
parent
7a4e071709
commit
3ddfd62f16
|
|
@ -43,7 +43,7 @@ public class SslAutoConfiguration {
|
|||
private final SslProperties sslProperties;
|
||||
|
||||
SslAutoConfiguration(ResourceLoader resourceLoader, SslProperties sslProperties) {
|
||||
this.resourceLoader = ApplicationResourceLoader.get(resourceLoader);
|
||||
this.resourceLoader = ApplicationResourceLoader.get(resourceLoader, true);
|
||||
this.sslProperties = sslProperties;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -119,6 +119,24 @@ class SslAutoConfigurationTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void sslBundleWithoutClassPathPrefix() {
|
||||
List<String> propertyValues = new ArrayList<>();
|
||||
String location = "src/test/resources/org/springframework/boot/autoconfigure/ssl/";
|
||||
propertyValues.add("spring.ssl.bundle.pem.test.key.alias=alias1");
|
||||
propertyValues.add("spring.ssl.bundle.pem.test.key.password=secret1");
|
||||
propertyValues.add("spring.ssl.bundle.pem.test.keystore.certificate=" + location + "rsa-cert.pem");
|
||||
propertyValues.add("spring.ssl.bundle.pem.test.keystore.keystore.private-key=" + location + "rsa-key.pem");
|
||||
propertyValues.add("spring.ssl.bundle.pem.test.truststore.certificate=" + location + "rsa-cert.pem");
|
||||
this.contextRunner.withPropertyValues(propertyValues.toArray(String[]::new)).run((context) -> {
|
||||
assertThat(context).hasSingleBean(SslBundles.class);
|
||||
SslBundles bundles = context.getBean(SslBundles.class);
|
||||
SslBundle bundle = bundles.getBundle("test");
|
||||
assertThat(bundle.getStores().getKeyStore().getCertificate("alias1")).isNotNull();
|
||||
assertThat(bundle.getStores().getTrustStore().getCertificate("ssl")).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(CustomSslProperties.class)
|
||||
public static class CustomSslBundleConfiguration {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package org.springframework.boot.io;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.ContextResource;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
|
|
@ -26,6 +27,7 @@ import org.springframework.core.io.Resource;
|
|||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -120,8 +122,25 @@ public class ApplicationResourceLoader extends DefaultResourceLoader {
|
|||
* @since 3.4.0
|
||||
*/
|
||||
public static ResourceLoader get(ResourceLoader resourceLoader) {
|
||||
return get(resourceLoader, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link ResourceLoader} delegating to the given resource loader and
|
||||
* supporting additional {@link ProtocolResolver ProtocolResolvers} registered in
|
||||
* {@code spring.factories}. The factories file will be resolved using the default
|
||||
* class loader at the time this call is made.
|
||||
* @param resourceLoader the delegate resource loader
|
||||
* @param preferFileResolution if file based resolution is preferred over
|
||||
* {@code ServletContextResource} or {@link ClassPathResource} when no resource prefix
|
||||
* is provided.
|
||||
* @return a {@link ResourceLoader} instance
|
||||
* @since 3.4.1
|
||||
*/
|
||||
public static ResourceLoader get(ResourceLoader resourceLoader, boolean preferFileResolution) {
|
||||
Assert.notNull(resourceLoader, "'resourceLoader' must not be null");
|
||||
return get(resourceLoader, SpringFactoriesLoader.forDefaultResourceLocation(resourceLoader.getClassLoader()));
|
||||
return get(resourceLoader, SpringFactoriesLoader.forDefaultResourceLocation(resourceLoader.getClassLoader()),
|
||||
preferFileResolution);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -135,9 +154,15 @@ public class ApplicationResourceLoader extends DefaultResourceLoader {
|
|||
* @since 3.4.0
|
||||
*/
|
||||
public static ResourceLoader get(ResourceLoader resourceLoader, SpringFactoriesLoader springFactoriesLoader) {
|
||||
return get(resourceLoader, springFactoriesLoader, false);
|
||||
}
|
||||
|
||||
private static ResourceLoader get(ResourceLoader resourceLoader, SpringFactoriesLoader springFactoriesLoader,
|
||||
boolean preferFileResolution) {
|
||||
Assert.notNull(resourceLoader, "'resourceLoader' must not be null");
|
||||
Assert.notNull(springFactoriesLoader, "'springFactoriesLoader' must not be null");
|
||||
return new ProtocolResolvingResourceLoader(resourceLoader, springFactoriesLoader.load(ProtocolResolver.class));
|
||||
return new ProtocolResolvingResourceLoader(resourceLoader, springFactoriesLoader.load(ProtocolResolver.class),
|
||||
preferFileResolution);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -185,13 +210,30 @@ public class ApplicationResourceLoader extends DefaultResourceLoader {
|
|||
*/
|
||||
private static class ProtocolResolvingResourceLoader implements ResourceLoader {
|
||||
|
||||
private static final String SERVLET_CONTEXT_RESOURCE_CLASS_NAME = "org.springframework.web.context.support.ServletContextResource";
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
private final List<ProtocolResolver> protocolResolvers;
|
||||
|
||||
ProtocolResolvingResourceLoader(ResourceLoader resourceLoader, List<ProtocolResolver> protocolResolvers) {
|
||||
private final boolean preferFileResolution;
|
||||
|
||||
private Class<?> servletContextResourceClass;
|
||||
|
||||
ProtocolResolvingResourceLoader(ResourceLoader resourceLoader, List<ProtocolResolver> protocolResolvers,
|
||||
boolean preferFileResolution) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.protocolResolvers = protocolResolvers;
|
||||
this.preferFileResolution = preferFileResolution;
|
||||
this.servletContextResourceClass = resolveServletContextResourceClass(
|
||||
resourceLoader.getClass().getClassLoader());
|
||||
}
|
||||
|
||||
private static Class<?> resolveServletContextResourceClass(ClassLoader classLoader) {
|
||||
if (!ClassUtils.isPresent(SERVLET_CONTEXT_RESOURCE_CLASS_NAME, classLoader)) {
|
||||
return null;
|
||||
}
|
||||
return ClassUtils.resolveClassName(SERVLET_CONTEXT_RESOURCE_CLASS_NAME, classLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -204,7 +246,20 @@ public class ApplicationResourceLoader extends DefaultResourceLoader {
|
|||
}
|
||||
}
|
||||
}
|
||||
return this.resourceLoader.getResource(location);
|
||||
Resource resource = this.resourceLoader.getResource(location);
|
||||
if (this.preferFileResolution
|
||||
&& (isClassPathResourceByPath(location, resource) || isServletResource(resource))) {
|
||||
resource = new ApplicationResource(location);
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
private boolean isClassPathResourceByPath(String location, Resource resource) {
|
||||
return (resource instanceof ClassPathResource) && !location.startsWith(CLASSPATH_URL_PREFIX);
|
||||
}
|
||||
|
||||
private boolean isServletResource(Resource resource) {
|
||||
return this.servletContextResourceClass != null && this.servletContextResourceClass.isInstance(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -24,13 +24,19 @@ import java.util.Base64;
|
|||
import java.util.Enumeration;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.web.context.support.ServletContextResource;
|
||||
import org.springframework.web.context.support.ServletContextResourceLoader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
|
@ -152,6 +158,48 @@ class ApplicationResourceLoaderTests {
|
|||
.withMessage("Location must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourceWithPreferFileResolutionWhenFullPathWithClassPathResource() throws Exception {
|
||||
File file = new File("src/main/resources/a-file");
|
||||
ResourceLoader loader = ApplicationResourceLoader.get(new DefaultResourceLoader(), true);
|
||||
Resource resource = loader.getResource(file.getAbsolutePath());
|
||||
assertThat(resource).isInstanceOf(FileSystemResource.class);
|
||||
assertThat(resource.getFile().getAbsoluteFile()).isEqualTo(file.getAbsoluteFile());
|
||||
ResourceLoader regularLoader = ApplicationResourceLoader.get(new DefaultResourceLoader(), false);
|
||||
assertThat(regularLoader.getResource(file.getAbsolutePath())).isInstanceOf(ClassPathResource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourceWithPreferFileResolutionWhenRelativePathWithClassPathResource() throws Exception {
|
||||
ResourceLoader loader = ApplicationResourceLoader.get(new DefaultResourceLoader(), true);
|
||||
Resource resource = loader.getResource("src/main/resources/a-file");
|
||||
assertThat(resource).isInstanceOf(FileSystemResource.class);
|
||||
assertThat(resource.getFile().getAbsoluteFile())
|
||||
.isEqualTo(new File("src/main/resources/a-file").getAbsoluteFile());
|
||||
ResourceLoader regularLoader = ApplicationResourceLoader.get(new DefaultResourceLoader(), false);
|
||||
assertThat(regularLoader.getResource("src/main/resources/a-file")).isInstanceOf(ClassPathResource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourceWithPreferFileResolutionWhenExplicitClassPathPrefix() {
|
||||
ResourceLoader loader = ApplicationResourceLoader.get(new DefaultResourceLoader(), true);
|
||||
Resource resource = loader.getResource("classpath:a-file");
|
||||
assertThat(resource).isInstanceOf(ClassPathResource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourceWithPreferFileResolutionWhenPathWithServletContextResource() throws Exception {
|
||||
ServletContext servletContext = new MockServletContext();
|
||||
ServletContextResourceLoader servletContextResourceLoader = new ServletContextResourceLoader(servletContext);
|
||||
ResourceLoader loader = ApplicationResourceLoader.get(servletContextResourceLoader, true);
|
||||
Resource resource = loader.getResource("src/main/resources/a-file");
|
||||
assertThat(resource).isInstanceOf(FileSystemResource.class);
|
||||
assertThat(resource.getFile().getAbsoluteFile())
|
||||
.isEqualTo(new File("src/main/resources/a-file").getAbsoluteFile());
|
||||
ResourceLoader regularLoader = ApplicationResourceLoader.get(servletContextResourceLoader, false);
|
||||
assertThat(regularLoader.getResource("src/main/resources/a-file")).isInstanceOf(ServletContextResource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassLoaderReturnsDelegateClassLoader() {
|
||||
ClassLoader classLoader = new TestClassLoader(this::useTestProtocolResolversFactories);
|
||||
|
|
|
|||
Loading…
Reference in New Issue