Skip to content

Commit 3ddfd62

Browse files
committed
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
1 parent 7a4e071 commit 3ddfd62

File tree

4 files changed

+126
-5
lines changed

4 files changed

+126
-5
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class SslAutoConfiguration {
4343
private final SslProperties sslProperties;
4444

4545
SslAutoConfiguration(ResourceLoader resourceLoader, SslProperties sslProperties) {
46-
this.resourceLoader = ApplicationResourceLoader.get(resourceLoader);
46+
this.resourceLoader = ApplicationResourceLoader.get(resourceLoader, true);
4747
this.sslProperties = sslProperties;
4848
}
4949

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfigurationTests.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,24 @@ void sslBundlesCreatedWithCustomSslBundle() {
119119
});
120120
}
121121

122+
@Test
123+
void sslBundleWithoutClassPathPrefix() {
124+
List<String> propertyValues = new ArrayList<>();
125+
String location = "src/test/resources/org/springframework/boot/autoconfigure/ssl/";
126+
propertyValues.add("spring.ssl.bundle.pem.test.key.alias=alias1");
127+
propertyValues.add("spring.ssl.bundle.pem.test.key.password=secret1");
128+
propertyValues.add("spring.ssl.bundle.pem.test.keystore.certificate=" + location + "rsa-cert.pem");
129+
propertyValues.add("spring.ssl.bundle.pem.test.keystore.keystore.private-key=" + location + "rsa-key.pem");
130+
propertyValues.add("spring.ssl.bundle.pem.test.truststore.certificate=" + location + "rsa-cert.pem");
131+
this.contextRunner.withPropertyValues(propertyValues.toArray(String[]::new)).run((context) -> {
132+
assertThat(context).hasSingleBean(SslBundles.class);
133+
SslBundles bundles = context.getBean(SslBundles.class);
134+
SslBundle bundle = bundles.getBundle("test");
135+
assertThat(bundle.getStores().getKeyStore().getCertificate("alias1")).isNotNull();
136+
assertThat(bundle.getStores().getTrustStore().getCertificate("ssl")).isNotNull();
137+
});
138+
}
139+
122140
@Configuration
123141
@EnableConfigurationProperties(CustomSslProperties.class)
124142
public static class CustomSslBundleConfiguration {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/io/ApplicationResourceLoader.java

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.List;
2020

21+
import org.springframework.core.io.ClassPathResource;
2122
import org.springframework.core.io.ContextResource;
2223
import org.springframework.core.io.DefaultResourceLoader;
2324
import org.springframework.core.io.FileSystemResource;
@@ -26,6 +27,7 @@
2627
import org.springframework.core.io.ResourceLoader;
2728
import org.springframework.core.io.support.SpringFactoriesLoader;
2829
import org.springframework.util.Assert;
30+
import org.springframework.util.ClassUtils;
2931
import org.springframework.util.StringUtils;
3032

3133
/**
@@ -120,8 +122,25 @@ public static ResourceLoader get(ClassLoader classLoader, SpringFactoriesLoader
120122
* @since 3.4.0
121123
*/
122124
public static ResourceLoader get(ResourceLoader resourceLoader) {
125+
return get(resourceLoader, false);
126+
}
127+
128+
/**
129+
* Return a {@link ResourceLoader} delegating to the given resource loader and
130+
* supporting additional {@link ProtocolResolver ProtocolResolvers} registered in
131+
* {@code spring.factories}. The factories file will be resolved using the default
132+
* class loader at the time this call is made.
133+
* @param resourceLoader the delegate resource loader
134+
* @param preferFileResolution if file based resolution is preferred over
135+
* {@code ServletContextResource} or {@link ClassPathResource} when no resource prefix
136+
* is provided.
137+
* @return a {@link ResourceLoader} instance
138+
* @since 3.4.1
139+
*/
140+
public static ResourceLoader get(ResourceLoader resourceLoader, boolean preferFileResolution) {
123141
Assert.notNull(resourceLoader, "'resourceLoader' must not be null");
124-
return get(resourceLoader, SpringFactoriesLoader.forDefaultResourceLocation(resourceLoader.getClassLoader()));
142+
return get(resourceLoader, SpringFactoriesLoader.forDefaultResourceLocation(resourceLoader.getClassLoader()),
143+
preferFileResolution);
125144
}
126145

127146
/**
@@ -135,9 +154,15 @@ public static ResourceLoader get(ResourceLoader resourceLoader) {
135154
* @since 3.4.0
136155
*/
137156
public static ResourceLoader get(ResourceLoader resourceLoader, SpringFactoriesLoader springFactoriesLoader) {
157+
return get(resourceLoader, springFactoriesLoader, false);
158+
}
159+
160+
private static ResourceLoader get(ResourceLoader resourceLoader, SpringFactoriesLoader springFactoriesLoader,
161+
boolean preferFileResolution) {
138162
Assert.notNull(resourceLoader, "'resourceLoader' must not be null");
139163
Assert.notNull(springFactoriesLoader, "'springFactoriesLoader' must not be null");
140-
return new ProtocolResolvingResourceLoader(resourceLoader, springFactoriesLoader.load(ProtocolResolver.class));
164+
return new ProtocolResolvingResourceLoader(resourceLoader, springFactoriesLoader.load(ProtocolResolver.class),
165+
preferFileResolution);
141166
}
142167

143168
/**
@@ -185,13 +210,30 @@ public String getPathWithinContext() {
185210
*/
186211
private static class ProtocolResolvingResourceLoader implements ResourceLoader {
187212

213+
private static final String SERVLET_CONTEXT_RESOURCE_CLASS_NAME = "org.springframework.web.context.support.ServletContextResource";
214+
188215
private final ResourceLoader resourceLoader;
189216

190217
private final List<ProtocolResolver> protocolResolvers;
191218

192-
ProtocolResolvingResourceLoader(ResourceLoader resourceLoader, List<ProtocolResolver> protocolResolvers) {
219+
private final boolean preferFileResolution;
220+
221+
private Class<?> servletContextResourceClass;
222+
223+
ProtocolResolvingResourceLoader(ResourceLoader resourceLoader, List<ProtocolResolver> protocolResolvers,
224+
boolean preferFileResolution) {
193225
this.resourceLoader = resourceLoader;
194226
this.protocolResolvers = protocolResolvers;
227+
this.preferFileResolution = preferFileResolution;
228+
this.servletContextResourceClass = resolveServletContextResourceClass(
229+
resourceLoader.getClass().getClassLoader());
230+
}
231+
232+
private static Class<?> resolveServletContextResourceClass(ClassLoader classLoader) {
233+
if (!ClassUtils.isPresent(SERVLET_CONTEXT_RESOURCE_CLASS_NAME, classLoader)) {
234+
return null;
235+
}
236+
return ClassUtils.resolveClassName(SERVLET_CONTEXT_RESOURCE_CLASS_NAME, classLoader);
195237
}
196238

197239
@Override
@@ -204,7 +246,20 @@ public Resource getResource(String location) {
204246
}
205247
}
206248
}
207-
return this.resourceLoader.getResource(location);
249+
Resource resource = this.resourceLoader.getResource(location);
250+
if (this.preferFileResolution
251+
&& (isClassPathResourceByPath(location, resource) || isServletResource(resource))) {
252+
resource = new ApplicationResource(location);
253+
}
254+
return resource;
255+
}
256+
257+
private boolean isClassPathResourceByPath(String location, Resource resource) {
258+
return (resource instanceof ClassPathResource) && !location.startsWith(CLASSPATH_URL_PREFIX);
259+
}
260+
261+
private boolean isServletResource(Resource resource) {
262+
return this.servletContextResourceClass != null && this.servletContextResourceClass.isInstance(resource);
208263
}
209264

210265
@Override

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/io/ApplicationResourceLoaderTests.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,19 @@
2424
import java.util.Enumeration;
2525
import java.util.function.UnaryOperator;
2626

27+
import jakarta.servlet.ServletContext;
2728
import org.junit.jupiter.api.Test;
2829

2930
import org.springframework.core.io.ByteArrayResource;
31+
import org.springframework.core.io.ClassPathResource;
3032
import org.springframework.core.io.DefaultResourceLoader;
33+
import org.springframework.core.io.FileSystemResource;
3134
import org.springframework.core.io.Resource;
3235
import org.springframework.core.io.ResourceLoader;
3336
import org.springframework.core.io.support.SpringFactoriesLoader;
37+
import org.springframework.mock.web.MockServletContext;
38+
import org.springframework.web.context.support.ServletContextResource;
39+
import org.springframework.web.context.support.ServletContextResourceLoader;
3440

3541
import static org.assertj.core.api.Assertions.assertThat;
3642
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -152,6 +158,48 @@ void getResourceWhenPathIsNull() {
152158
.withMessage("Location must not be null");
153159
}
154160

161+
@Test
162+
void getResourceWithPreferFileResolutionWhenFullPathWithClassPathResource() throws Exception {
163+
File file = new File("src/main/resources/a-file");
164+
ResourceLoader loader = ApplicationResourceLoader.get(new DefaultResourceLoader(), true);
165+
Resource resource = loader.getResource(file.getAbsolutePath());
166+
assertThat(resource).isInstanceOf(FileSystemResource.class);
167+
assertThat(resource.getFile().getAbsoluteFile()).isEqualTo(file.getAbsoluteFile());
168+
ResourceLoader regularLoader = ApplicationResourceLoader.get(new DefaultResourceLoader(), false);
169+
assertThat(regularLoader.getResource(file.getAbsolutePath())).isInstanceOf(ClassPathResource.class);
170+
}
171+
172+
@Test
173+
void getResourceWithPreferFileResolutionWhenRelativePathWithClassPathResource() throws Exception {
174+
ResourceLoader loader = ApplicationResourceLoader.get(new DefaultResourceLoader(), true);
175+
Resource resource = loader.getResource("src/main/resources/a-file");
176+
assertThat(resource).isInstanceOf(FileSystemResource.class);
177+
assertThat(resource.getFile().getAbsoluteFile())
178+
.isEqualTo(new File("src/main/resources/a-file").getAbsoluteFile());
179+
ResourceLoader regularLoader = ApplicationResourceLoader.get(new DefaultResourceLoader(), false);
180+
assertThat(regularLoader.getResource("src/main/resources/a-file")).isInstanceOf(ClassPathResource.class);
181+
}
182+
183+
@Test
184+
void getResourceWithPreferFileResolutionWhenExplicitClassPathPrefix() {
185+
ResourceLoader loader = ApplicationResourceLoader.get(new DefaultResourceLoader(), true);
186+
Resource resource = loader.getResource("classpath:a-file");
187+
assertThat(resource).isInstanceOf(ClassPathResource.class);
188+
}
189+
190+
@Test
191+
void getResourceWithPreferFileResolutionWhenPathWithServletContextResource() throws Exception {
192+
ServletContext servletContext = new MockServletContext();
193+
ServletContextResourceLoader servletContextResourceLoader = new ServletContextResourceLoader(servletContext);
194+
ResourceLoader loader = ApplicationResourceLoader.get(servletContextResourceLoader, true);
195+
Resource resource = loader.getResource("src/main/resources/a-file");
196+
assertThat(resource).isInstanceOf(FileSystemResource.class);
197+
assertThat(resource.getFile().getAbsoluteFile())
198+
.isEqualTo(new File("src/main/resources/a-file").getAbsoluteFile());
199+
ResourceLoader regularLoader = ApplicationResourceLoader.get(servletContextResourceLoader, false);
200+
assertThat(regularLoader.getResource("src/main/resources/a-file")).isInstanceOf(ServletContextResource.class);
201+
}
202+
155203
@Test
156204
void getClassLoaderReturnsDelegateClassLoader() {
157205
ClassLoader classLoader = new TestClassLoader(this::useTestProtocolResolversFactories);

0 commit comments

Comments
 (0)