From d88d66a985893a17a165030a02a84a2b32c0300d Mon Sep 17 00:00:00 2001 From: "predicate@bonzo" Date: Fri, 7 Jan 2022 13:52:31 +0100 Subject: [PATCH 1/3] add support for custom paths --- .../security/SpringDocSecurityConfiguration.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/springdoc-openapi-security/src/main/java/org/springdoc/security/SpringDocSecurityConfiguration.java b/springdoc-openapi-security/src/main/java/org/springdoc/security/SpringDocSecurityConfiguration.java index 7e76496ee..b3d304ef2 100644 --- a/springdoc-openapi-security/src/main/java/org/springdoc/security/SpringDocSecurityConfiguration.java +++ b/springdoc-openapi-security/src/main/java/org/springdoc/security/SpringDocSecurityConfiguration.java @@ -20,6 +20,7 @@ package org.springdoc.security; +import java.lang.reflect.Field; import java.util.Optional; import io.swagger.v3.oas.models.Operation; @@ -50,6 +51,7 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import static org.springdoc.core.Constants.SPRINGDOC_ENABLED; import static org.springdoc.core.Constants.SPRINGDOC_SHOW_LOGIN_ENDPOINT; @@ -131,7 +133,16 @@ OpenApiCustomiser springSecurityLoginEndpointCustomiser(ApplicationContext appli operation.responses(apiResponses); operation.addTagsItem("login-endpoint"); PathItem pathItem = new PathItem().post(operation); - openAPI.getPaths().addPathItem("/login", pathItem); + String loginPath = "/login"; + try { + Field requestMatcherField = usernamePasswordAuthenticationFilter.getClass().getSuperclass().getDeclaredField("requiresAuthenticationRequestMatcher"); + requestMatcherField.setAccessible(true); + AntPathRequestMatcher requestMatcher = (AntPathRequestMatcher) requestMatcherField.get(usernamePasswordAuthenticationFilter); + loginPath = requestMatcher.getPattern(); + requestMatcherField.setAccessible(false); + } catch (NoSuchFieldException | IllegalAccessException ignored) { + } + openAPI.getPaths().addPathItem(loginPath, pathItem); } } }; From c8cace23d49ded303e3825a1ec3599ac1fb459db Mon Sep 17 00:00:00 2001 From: "predicate@bonzo" Date: Fri, 7 Jan 2022 14:53:42 +0100 Subject: [PATCH 2/3] add test --- .../springdoc/api/app8/SpringDocApp8Test.java | 23 ++++++++ .../api/app8/security/WebConfig.java | 17 ++++++ .../src/test/resources/results/app8.json | 53 +++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 springdoc-openapi-security/src/test/java/test/org/springdoc/api/app8/SpringDocApp8Test.java create mode 100644 springdoc-openapi-security/src/test/java/test/org/springdoc/api/app8/security/WebConfig.java create mode 100644 springdoc-openapi-security/src/test/resources/results/app8.json diff --git a/springdoc-openapi-security/src/test/java/test/org/springdoc/api/app8/SpringDocApp8Test.java b/springdoc-openapi-security/src/test/java/test/org/springdoc/api/app8/SpringDocApp8Test.java new file mode 100644 index 000000000..be39c19c8 --- /dev/null +++ b/springdoc-openapi-security/src/test/java/test/org/springdoc/api/app8/SpringDocApp8Test.java @@ -0,0 +1,23 @@ +package test.org.springdoc.api.app8; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.TestPropertySource; +import test.org.springdoc.api.AbstractSpringDocTest; + +@TestPropertySource(properties = "springdoc.show-login-endpoint=true") +public class SpringDocApp8Test extends AbstractSpringDocTest { + + @SpringBootApplication(scanBasePackages = { "test.org.springdoc.api.configuration,test.org.springdoc.api.app8" }) + static class SpringDocTestApp { + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info(new Info().title("Security API").version("v1") + .license(new License().name("Apache 2.0").url("http://springdoc.org"))); + } + } +} diff --git a/springdoc-openapi-security/src/test/java/test/org/springdoc/api/app8/security/WebConfig.java b/springdoc-openapi-security/src/test/java/test/org/springdoc/api/app8/security/WebConfig.java new file mode 100644 index 000000000..a4504dcf6 --- /dev/null +++ b/springdoc-openapi-security/src/test/java/test/org/springdoc/api/app8/security/WebConfig.java @@ -0,0 +1,17 @@ +package test.org.springdoc.api.app8.security; + +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@EnableWebSecurity +@Order(200) +public class WebConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.formLogin() + .loginProcessingUrl("/api/login"); + } +} diff --git a/springdoc-openapi-security/src/test/resources/results/app8.json b/springdoc-openapi-security/src/test/resources/results/app8.json new file mode 100644 index 000000000..c59a6ff1b --- /dev/null +++ b/springdoc-openapi-security/src/test/resources/results/app8.json @@ -0,0 +1,53 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Security API", + "license": { + "name": "Apache 2.0", + "url": "http://springdoc.org" + }, + "version": "v1" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/api/login": { + "post": { + "tags": [ + "login-endpoint" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK" + }, + "403": { + "description": "Forbidden" + } + } + } + } + }, + "components": { + } +} From f63a3e98ebb1d6d2c49fea4f8f6406acf6b7af85 Mon Sep 17 00:00:00 2001 From: "predicate@bonzo" Date: Fri, 7 Jan 2022 16:10:59 +0100 Subject: [PATCH 3/3] some improvements * access AbstractAuthenticationProcessingFilter class directly instead of calling getSuperclass() * catch ClassCastException for matchers that are not instances of AntPathRequestMatcher * ignore authentication filters that produce errors (such as wrong login endpoints) instead of assuming /login path --- .../security/SpringDocSecurityConfiguration.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/springdoc-openapi-security/src/main/java/org/springdoc/security/SpringDocSecurityConfiguration.java b/springdoc-openapi-security/src/main/java/org/springdoc/security/SpringDocSecurityConfiguration.java index b3d304ef2..eb36ab7c3 100644 --- a/springdoc-openapi-security/src/main/java/org/springdoc/security/SpringDocSecurityConfiguration.java +++ b/springdoc-openapi-security/src/main/java/org/springdoc/security/SpringDocSecurityConfiguration.java @@ -49,6 +49,7 @@ import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -133,16 +134,15 @@ OpenApiCustomiser springSecurityLoginEndpointCustomiser(ApplicationContext appli operation.responses(apiResponses); operation.addTagsItem("login-endpoint"); PathItem pathItem = new PathItem().post(operation); - String loginPath = "/login"; try { - Field requestMatcherField = usernamePasswordAuthenticationFilter.getClass().getSuperclass().getDeclaredField("requiresAuthenticationRequestMatcher"); + Field requestMatcherField = AbstractAuthenticationProcessingFilter.class.getDeclaredField("requiresAuthenticationRequestMatcher"); requestMatcherField.setAccessible(true); AntPathRequestMatcher requestMatcher = (AntPathRequestMatcher) requestMatcherField.get(usernamePasswordAuthenticationFilter); - loginPath = requestMatcher.getPattern(); + String loginPath = requestMatcher.getPattern(); requestMatcherField.setAccessible(false); - } catch (NoSuchFieldException | IllegalAccessException ignored) { + openAPI.getPaths().addPathItem(loginPath, pathItem); + } catch (NoSuchFieldException | IllegalAccessException | ClassCastException ignored) { } - openAPI.getPaths().addPathItem(loginPath, pathItem); } } };