diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/Constants.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/Constants.java index c653d407b..3398e5d42 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/Constants.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/Constants.java @@ -201,6 +201,11 @@ public final class Constants { */ public static final String INDEX_PAGE = "/index.html"; + /** + * The constant INDEX_PAGE_PATTERN. + */ + public static final String INDEX_PAGE_PATTERN = "/*index.html"; + /** * The constant SWAGGER_UI_URL. */ diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerConfigurer.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerConfigurer.java index 583dd2ffb..d0c489f0a 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerConfigurer.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerConfigurer.java @@ -9,6 +9,7 @@ import java.util.Arrays; import static org.springdoc.core.utils.Constants.ALL_PATTERN; +import static org.springdoc.core.utils.Constants.INDEX_PAGE_PATTERN; import static org.springdoc.core.utils.Constants.SWAGGER_INITIALIZER_PATTERN; import static org.springdoc.core.utils.Constants.SWAGGER_UI_PREFIX; import static org.springdoc.core.utils.Constants.SWAGGER_UI_WEBJAR_NAME; @@ -54,6 +55,7 @@ protected AbstractSwaggerConfigurer(SwaggerUiConfigProperties swaggerUiConfigPro */ protected SwaggerResourceHandlerConfig[] getSwaggerHandlerConfigs() { String swaggerUiPattern = getUiRootPath() + SWAGGER_UI_PREFIX + ALL_PATTERN; + String swaggerUiIndexPattern = combinePatterns(swaggerUiPattern, INDEX_PAGE_PATTERN); String swaggerUiInitializerPattern = combinePatterns(swaggerUiPattern, SWAGGER_INITIALIZER_PATTERN); String swaggerUiResourceLocation = WEBJARS_RESOURCE_LOCATION + SWAGGER_UI_WEBJAR_NAME + DEFAULT_PATH_SEPARATOR + swaggerUiConfigProperties.getVersion() + DEFAULT_PATH_SEPARATOR; @@ -63,7 +65,7 @@ protected SwaggerResourceHandlerConfig[] getSwaggerHandlerConfigs() { .setPatterns(swaggerUiPattern) .setLocations(swaggerUiResourceLocation), SwaggerResourceHandlerConfig.createUncached() - .setPatterns(swaggerUiInitializerPattern) + .setPatterns(swaggerUiIndexPattern, swaggerUiInitializerPattern) .setLocations(swaggerUiResourceLocation) }; } @@ -77,6 +79,9 @@ protected SwaggerResourceHandlerConfig[] getSwaggerWebjarHandlerConfigs() { if (!springWebProperties.getResources().isAddMappings()) return new SwaggerResourceHandlerConfig[]{}; String swaggerUiWebjarPattern = combinePatterns(getWebjarsPathPattern(), SWAGGER_UI_WEBJAR_NAME_PATTERN) + ALL_PATTERN; + String swaggerUiWebjarIndexPattern = combinePatterns(swaggerUiWebjarPattern, INDEX_PAGE_PATTERN); + String swaggerUiWebjarVersionIndexPattern = combinePatterns(swaggerUiWebjarPattern, + swaggerUiConfigProperties.getVersion() + INDEX_PAGE_PATTERN); String swaggerUiWebjarInitializerPattern = combinePatterns(swaggerUiWebjarPattern, SWAGGER_INITIALIZER_PATTERN); String swaggerUiWebjarVersionInitializerPattern = combinePatterns(swaggerUiWebjarPattern, swaggerUiConfigProperties.getVersion() + SWAGGER_INITIALIZER_PATTERN); @@ -87,7 +92,8 @@ protected SwaggerResourceHandlerConfig[] getSwaggerWebjarHandlerConfigs() { .setPatterns(swaggerUiWebjarPattern) .setLocations(swaggerUiWebjarResourceLocation), SwaggerResourceHandlerConfig.createUncached() - .setPatterns(swaggerUiWebjarInitializerPattern, swaggerUiWebjarVersionInitializerPattern) + .setPatterns(swaggerUiWebjarIndexPattern, swaggerUiWebjarVersionIndexPattern, + swaggerUiWebjarInitializerPattern, swaggerUiWebjarVersionInitializerPattern) .setLocations(swaggerUiWebjarResourceLocation) }; } diff --git a/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app42/SpringDocApp42Test.java b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app42/SpringDocApp42Test.java new file mode 100644 index 000000000..c6d512122 --- /dev/null +++ b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app42/SpringDocApp42Test.java @@ -0,0 +1,102 @@ +/* + * + * * Copyright 2019-2026 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 test.org.springdoc.ui.app42; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.Test; +import org.springdoc.core.properties.SwaggerUiConfigProperties; +import org.springdoc.core.properties.SwaggerUiOAuthProperties; +import org.springdoc.core.providers.ObjectMapperProvider; +import org.springdoc.webmvc.ui.SwaggerIndexPageTransformer; +import org.springdoc.webmvc.ui.SwaggerIndexTransformer; +import org.springdoc.webmvc.ui.SwaggerWelcomeCommon; +import test.org.springdoc.ui.AbstractSpringDocTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.Resource; +import org.springframework.web.servlet.resource.ResourceTransformerChain; +import org.springframework.web.servlet.resource.TransformedResource; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests per-request Swagger UI index transformations. + * + * @author limehee + */ +public class SpringDocApp42Test extends AbstractSpringDocTest { + + @Test + void indexPageTransformerRunsForEveryRequest() throws Exception { + mockMvc.perform(get("/swagger-ui/index.html").requestAttr("cspNonce", "nonce-a")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("nonce=\"nonce-a\""))) + .andExpect(content().string(not(containsString("nonce=\"nonce-b\"")))); + + mockMvc.perform(get("/swagger-ui/index.html").requestAttr("cspNonce", "nonce-b")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("nonce=\"nonce-b\""))) + .andExpect(content().string(not(containsString("nonce=\"nonce-a\"")))); + } + + @SpringBootApplication + static class SpringDocTestApp { + + @Bean + SwaggerIndexTransformer swaggerIndexTransformer(SwaggerUiConfigProperties swaggerUiConfig, + SwaggerUiOAuthProperties swaggerUiOAuthProperties, SwaggerWelcomeCommon swaggerWelcomeCommon, + ObjectMapperProvider objectMapperProvider) { + return new NonceSwaggerIndexTransformer(swaggerUiConfig, swaggerUiOAuthProperties, swaggerWelcomeCommon, + objectMapperProvider); + } + + } + + static class NonceSwaggerIndexTransformer extends SwaggerIndexPageTransformer { + + NonceSwaggerIndexTransformer(SwaggerUiConfigProperties swaggerUiConfig, + SwaggerUiOAuthProperties swaggerUiOAuthProperties, SwaggerWelcomeCommon swaggerWelcomeCommon, + ObjectMapperProvider objectMapperProvider) { + super(swaggerUiConfig, swaggerUiOAuthProperties, swaggerWelcomeCommon, objectMapperProvider); + } + + @Override + public Resource transform(HttpServletRequest request, Resource resource, ResourceTransformerChain transformerChain) + throws IOException { + Resource transformedResource = super.transform(request, resource, transformerChain); + if (request.getRequestURI().endsWith("/index.html")) { + String html = new String(transformedResource.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + String nonce = request.getAttribute("cspNonce").toString(); + html = html.replace("