From 02a8b52801456eef458229bc90889735496557fb Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 27 May 2023 21:24:14 +0900 Subject: [PATCH] feat: support OIDC --- .../SpringDocSecurityOAuth2Customizer.java | 112 ++- ...pringDocOidcClientRegistrationRequest.java | 58 ++ ...ringDocOidcClientRegistrationResponse.java | 64 ++ .../SpringDocOidcProviderConfiguration.java | 66 ++ .../api/app11/AuthorizationServerConfig.java | 75 ++ .../springdoc/api/app11/HelloController.java | 17 + .../test/org/springdoc/api/app11/Jwks.java | 29 + .../api/app11/KeyGeneratorUtils.java | 26 + .../api/app11/SpringDocApp11Test.java | 56 ++ .../api/app12/AuthorizationServerConfig.java | 75 ++ .../springdoc/api/app12/HelloController.java | 17 + .../test/org/springdoc/api/app12/Jwks.java | 29 + .../api/app12/KeyGeneratorUtils.java | 26 + .../api/app12/SpringDocApp12Test.java | 56 ++ .../src/test/resources/results/app11.json | 605 ++++++++++++++ .../src/test/resources/results/app12.json | 788 ++++++++++++++++++ 16 files changed, 2090 insertions(+), 9 deletions(-) create mode 100644 springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/oauth2/SpringDocOidcClientRegistrationRequest.java create mode 100644 springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/oauth2/SpringDocOidcClientRegistrationResponse.java create mode 100644 springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/oauth2/SpringDocOidcProviderConfiguration.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/AuthorizationServerConfig.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/HelloController.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/Jwks.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/KeyGeneratorUtils.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/SpringDocApp11Test.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/AuthorizationServerConfig.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/HelloController.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/Jwks.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/KeyGeneratorUtils.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/SpringDocApp12Test.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/resources/results/app11.json create mode 100644 springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/resources/results/app12.json diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSecurityOAuth2Customizer.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSecurityOAuth2Customizer.java index 159e6dea1..0b828cb67 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSecurityOAuth2Customizer.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSecurityOAuth2Customizer.java @@ -26,8 +26,10 @@ import org.springdoc.core.configuration.oauth2.SpringDocOAuth2AuthorizationServerMetadata; import org.springdoc.core.configuration.oauth2.SpringDocOAuth2Token; import org.springdoc.core.configuration.oauth2.SpringDocOAuth2TokenIntrospection; +import org.springdoc.core.configuration.oauth2.SpringDocOidcClientRegistrationRequest; +import org.springdoc.core.configuration.oauth2.SpringDocOidcClientRegistrationResponse; +import org.springdoc.core.configuration.oauth2.SpringDocOidcProviderConfiguration; import org.springdoc.core.customizers.GlobalOpenApiCustomizer; -import org.springdoc.core.utils.SpringDocAnnotationsUtils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; @@ -37,6 +39,9 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter; +import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter; +import org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter; @@ -85,6 +90,9 @@ public void customise(OpenAPI openAPI) { getOAuth2AuthorizationEndpoint(openAPI, filterChain); getOAuth2TokenIntrospectionEndpointFilter(openAPI, filterChain); getOAuth2TokenRevocationEndpointFilter(openAPI, filterChain); + getOidcProviderConfigurationEndpoint(openAPI, filterChain); + getOidcUserInfoEndpoint(openAPI, filterChain); + getOidcClientRegistrationEndpoint(openAPI, filterChain); } } @@ -259,6 +267,76 @@ private void getOAuth2AuthorizationEndpoint(OpenAPI openAPI, SecurityFilterChain } } + /** + * Gets OpenID Provider endpoint filter + * + * @param openAPI the open api + * @param securityFilterChain the security filter chain + */ + private void getOidcProviderConfigurationEndpoint(OpenAPI openAPI, SecurityFilterChain securityFilterChain) { + Object oAuth2EndpointFilter = + new SpringDocSecurityOAuth2EndpointUtils(OidcProviderConfigurationEndpointFilter.class).findEndpoint(securityFilterChain); + + if (oAuth2EndpointFilter != null) { + ApiResponses apiResponses = new ApiResponses(); + buildApiResponsesOnSuccess(apiResponses, AnnotationsUtils.resolveSchemaFromType(SpringDocOidcProviderConfiguration.class, openAPI.getComponents(), null)); + buildApiResponsesOnInternalServerError(apiResponses); + Operation operation = buildOperation(apiResponses); + buildPath(oAuth2EndpointFilter, "requestMatcher", openAPI, operation, HttpMethod.GET); + } + } + + /** + * Gets OpenID UserInfo endpoint filter + * + * @param openAPI the open api + * @param securityFilterChain the security filter chain + */ + private void getOidcUserInfoEndpoint(OpenAPI openAPI, SecurityFilterChain securityFilterChain) { + Object oAuth2EndpointFilter = + new SpringDocSecurityOAuth2EndpointUtils(OidcUserInfoEndpointFilter.class).findEndpoint(securityFilterChain); + + if (oAuth2EndpointFilter != null) { + ApiResponses apiResponses = new ApiResponses(); + Schema schema = new ObjectSchema().additionalProperties(new StringSchema()); + buildApiResponsesOnSuccess(apiResponses, schema); + buildApiResponsesOnInternalServerError(apiResponses); + Operation operation = buildOperation(apiResponses); + buildPath(oAuth2EndpointFilter, "userInfoEndpointMatcher", openAPI, operation, HttpMethod.GET); + } + } + + /** + * Gets OpenID Client Registration endpoint filter + * + * @param openAPI the open api + * @param securityFilterChain the security filter chain + */ + private void getOidcClientRegistrationEndpoint(OpenAPI openAPI, SecurityFilterChain securityFilterChain) { + Object oAuth2EndpointFilter = + new SpringDocSecurityOAuth2EndpointUtils(OidcClientRegistrationEndpointFilter.class).findEndpoint(securityFilterChain); + + if (oAuth2EndpointFilter != null) { + ApiResponses apiResponses = new ApiResponses(); + buildApiResponsesOnCreated(apiResponses, AnnotationsUtils.resolveSchemaFromType(SpringDocOidcClientRegistrationResponse.class, openAPI.getComponents(), null)); + buildApiResponsesOnInternalServerError(apiResponses); + buildApiResponsesOnBadRequest(apiResponses, openAPI); + buildOAuth2Error(openAPI, apiResponses, HttpStatus.UNAUTHORIZED); + buildOAuth2Error(openAPI, apiResponses, HttpStatus.FORBIDDEN); + Operation operation = buildOperation(apiResponses); + + // OidcClientRegistration + Schema schema = AnnotationsUtils.resolveSchemaFromType(SpringDocOidcClientRegistrationRequest.class, openAPI.getComponents(), null); + + String mediaType = APPLICATION_JSON_VALUE; + RequestBody requestBody = new RequestBody().content(new Content().addMediaType(mediaType, new MediaType().schema(schema))); + operation.setRequestBody(requestBody); + operation.addParametersItem(new HeaderParameter().name("Authorization")); + + buildPath(oAuth2EndpointFilter, "clientRegistrationEndpointMatcher", openAPI, operation, HttpMethod.POST); + } + } + /** * Build operation operation. * @@ -287,6 +365,21 @@ private ApiResponses buildApiResponsesOnSuccess(ApiResponses apiResponses, Schem return apiResponses; } + /** + * Build api responses api responses on created. + * + * @param apiResponses the api responses + * @param schema the schema + * @return the api responses + */ + private ApiResponses buildApiResponsesOnCreated(ApiResponses apiResponses, Schema schema) { + ApiResponse response = new ApiResponse().description(HttpStatus.CREATED.getReasonPhrase()).content(new Content().addMediaType( + APPLICATION_JSON_VALUE, + new MediaType().schema(schema))); + apiResponses.addApiResponse(String.valueOf(HttpStatus.CREATED.value()), response); + return apiResponses; + } + /** * Build api responses api responses on internal server error. * @@ -338,22 +431,23 @@ private void buildPath(Object oAuth2EndpointFilter, String authorizationEndpoint Field tokenEndpointMatcherField = FieldUtils.getDeclaredField(oAuth2EndpointFilter.getClass(), authorizationEndpointMatcher, true); RequestMatcher endpointMatcher = (RequestMatcher) tokenEndpointMatcherField.get(oAuth2EndpointFilter); String path = null; - if (endpointMatcher instanceof AntPathRequestMatcher) - path = ((AntPathRequestMatcher) endpointMatcher).getPattern(); - else if (endpointMatcher instanceof OrRequestMatcher) { - OrRequestMatcher endpointMatchers = (OrRequestMatcher) endpointMatcher; + if (endpointMatcher instanceof AntPathRequestMatcher antPathRequestMatcher) + path = antPathRequestMatcher.getPattern(); + else if (endpointMatcher instanceof OrRequestMatcher endpointMatchers) { Field requestMatchersField = FieldUtils.getDeclaredField(OrRequestMatcher.class, "requestMatchers", true); Iterable requestMatchers = (Iterable) requestMatchersField.get(endpointMatchers); for (RequestMatcher requestMatcher : requestMatchers) { - if (requestMatcher instanceof OrRequestMatcher) { - OrRequestMatcher orRequestMatcher = (OrRequestMatcher) requestMatcher; + if (requestMatcher instanceof OrRequestMatcher orRequestMatcher) { requestMatchersField = FieldUtils.getDeclaredField(OrRequestMatcher.class, "requestMatchers", true); requestMatchers = (Iterable) requestMatchersField.get(orRequestMatcher); for (RequestMatcher matcher : requestMatchers) { - if (matcher instanceof AntPathRequestMatcher) - path = ((AntPathRequestMatcher) matcher).getPattern(); + if (matcher instanceof AntPathRequestMatcher antPathRequestMatcher) + path = antPathRequestMatcher.getPattern(); } } + else if (requestMatcher instanceof AntPathRequestMatcher antPathRequestMatcher) { + path = antPathRequestMatcher.getPattern(); + } } } diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/oauth2/SpringDocOidcClientRegistrationRequest.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/oauth2/SpringDocOidcClientRegistrationRequest.java new file mode 100644 index 000000000..984d7539a --- /dev/null +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/oauth2/SpringDocOidcClientRegistrationRequest.java @@ -0,0 +1,58 @@ +package org.springdoc.core.configuration.oauth2; + +import java.time.Instant; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + +import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames; + +/** + * The type Spring doc OpenID Client Registration Request + * + * @see OidcClientRegistration + * @see OidcClientRegistrationHttpMessageConverter + * @author yuta.saito + */ +@Schema(name = "ClientRegistrationRequest") +public interface SpringDocOidcClientRegistrationRequest { + @JsonProperty(OidcClientMetadataClaimNames.CLIENT_ID) + String clientId(); + + @JsonProperty(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT) + Instant clientIdIssuedAt(); + + @JsonProperty(OidcClientMetadataClaimNames.CLIENT_SECRET) + String clientSecret(); + + @JsonProperty(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT) + Instant CLIENT_SECRET_EXPIRES_AT(); + + @JsonProperty(OidcClientMetadataClaimNames.CLIENT_NAME) + String clientName(); + + @JsonProperty(OidcClientMetadataClaimNames.REDIRECT_URIS) + List redirectUris(); + + @JsonProperty(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD) + String tokenEndpointAuthenticationMethod(); + + @JsonProperty(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG) + String tokenEndpointAuthenticationSigningAlgorithm(); + + @JsonProperty(OidcClientMetadataClaimNames.GRANT_TYPES) + List grantTypes(); + + @JsonProperty(OidcClientMetadataClaimNames.RESPONSE_TYPES) + List responseType(); + + @JsonProperty(OidcClientMetadataClaimNames.SCOPE) + String scopes(); + + @JsonProperty(OidcClientMetadataClaimNames.JWKS_URI) + String jwkSetUrl(); + + @JsonProperty(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG) + String idTokenSignedResponseAlgorithm(); +} diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/oauth2/SpringDocOidcClientRegistrationResponse.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/oauth2/SpringDocOidcClientRegistrationResponse.java new file mode 100644 index 000000000..f115b8673 --- /dev/null +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/oauth2/SpringDocOidcClientRegistrationResponse.java @@ -0,0 +1,64 @@ +package org.springdoc.core.configuration.oauth2; + +import java.time.Instant; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + +import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames; + +/** + * The type Spring doc OpenID Client Registration Request + * + * @see OidcClientRegistration + * @see OidcClientRegistrationHttpMessageConverter + * @author yuta.saito + */ +@Schema(name = "ClientRegistrationResponse") +public interface SpringDocOidcClientRegistrationResponse { + @JsonProperty(OidcClientMetadataClaimNames.CLIENT_ID) + String clientId(); + + @JsonProperty(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT) + long clientIdIssuedAt(); + + @JsonProperty(OidcClientMetadataClaimNames.CLIENT_SECRET) + String clientSecret(); + + @JsonProperty(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT) + long CLIENT_SECRET_EXPIRES_AT(); + + @JsonProperty(OidcClientMetadataClaimNames.CLIENT_NAME) + String clientName(); + + @JsonProperty(OidcClientMetadataClaimNames.REDIRECT_URIS) + List redirectUris(); + + @JsonProperty(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD) + String tokenEndpointAuthenticationMethod(); + + @JsonProperty(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG) + String tokenEndpointAuthenticationSigningAlgorithm(); + + @JsonProperty(OidcClientMetadataClaimNames.GRANT_TYPES) + List grantTypes(); + + @JsonProperty(OidcClientMetadataClaimNames.RESPONSE_TYPES) + List responseType(); + + @JsonProperty(OidcClientMetadataClaimNames.SCOPE) + String scopes(); + + @JsonProperty(OidcClientMetadataClaimNames.JWKS_URI) + String jwkSetUrl(); + + @JsonProperty(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG) + String idTokenSignedResponseAlgorithm(); + + @JsonProperty(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN) + String registrationAccessToken(); + + @JsonProperty(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI) + String registrationClientUrl(); +} diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/oauth2/SpringDocOidcProviderConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/oauth2/SpringDocOidcProviderConfiguration.java new file mode 100644 index 000000000..15f7f99c4 --- /dev/null +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/oauth2/SpringDocOidcProviderConfiguration.java @@ -0,0 +1,66 @@ +package org.springdoc.core.configuration.oauth2; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames; +import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderMetadataClaimNames; + +/** + * The type Spring doc OpenID Provider Configuration + * + * @see OidcProviderConfiguration + * @author yuta.saito + */ +@Schema(name = "OidcProviderConfiguration") +public interface SpringDocOidcProviderConfiguration { + @JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.ISSUER) + String issuer(); + + @JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT) + String authorizationEndpoint(); + + @JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT) + String tokenEndpoint(); + + @JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED) + List tokenEndpointAuthMethodsSupported(); + + @JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI) + String jwksUri(); + + @JsonProperty(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT) + String userInfoEndpoint(); + + @JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED) + List responseTypesSupported(); + + @JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED) + List grantTypesSupported(); + + @JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT) + String revocationEndpoint(); + + @JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED) + List revocationEndpointAuthMethodsSupported(); + + @JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT) + String introspectionEndpoint(); + + @JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED) + List introspectionEndpointAuthMethodsSupported(); + + @JsonProperty(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED) + String subjectTypesSupported(); + + @JsonProperty(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED) + String idTokenSigningAlgValuesSupported(); + + @JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED) + String scopeSupported(); + + @JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED) + List codeChallengeMethodsSupported(); +} diff --git a/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/AuthorizationServerConfig.java b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/AuthorizationServerConfig.java new file mode 100644 index 000000000..f9e60d6ac --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/AuthorizationServerConfig.java @@ -0,0 +1,75 @@ +package test.org.springdoc.api.app11; + +import java.util.UUID; + +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.proc.SecurityContext; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; +import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; +import org.springframework.security.web.SecurityFilterChain; + +/* + * @author yuta.saito + */ +@Configuration +public class AuthorizationServerConfig { + @Bean + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); + http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) + .oidc(Customizer.withDefaults()); + return http.build(); + } + + @Bean + public RegisteredClientRepository registeredClientRepository() { + RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) + .clientId("messaging-client") + .clientSecret("{noop}secret") + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) + .scope("message.read") + .scope("message.write") + .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) + .build(); + + return new InMemoryRegisteredClientRepository(registeredClient); + } + + @Bean + public OAuth2AuthorizationService authorizationService() { + return new InMemoryOAuth2AuthorizationService(); + } + + @Bean + public AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().build(); + } + + @Bean + public JWKSource jwkSource() { + RSAKey rsaKey = Jwks.generateRsa(); + JWKSet jwkSet = new JWKSet(rsaKey); + return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); + } + + @Bean + public JwtDecoder jwtDecoder(JWKSource jwkSource) { + return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/HelloController.java b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/HelloController.java new file mode 100644 index 000000000..52548cd1f --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/HelloController.java @@ -0,0 +1,17 @@ +package test.org.springdoc.api.app11; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/* + * @author yuta.saito + */ +@RestController +@RequestMapping("/api") +public class HelloController { + @GetMapping("/hello") + public String hello() { + return "Hello"; + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/Jwks.java b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/Jwks.java new file mode 100644 index 000000000..ab8d9cc79 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/Jwks.java @@ -0,0 +1,29 @@ +package test.org.springdoc.api.app11; + + +import java.security.KeyPair; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.UUID; + +import com.nimbusds.jose.jwk.RSAKey; + + +/** + * @author yuta.saito + */ +public final class Jwks { + private Jwks() { + //final class constructor should be hidden + } + + public static RSAKey generateRsa() { + KeyPair keyPair = KeyGeneratorUtils.generateRsaKey(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + return new RSAKey.Builder(publicKey) + .privateKey(privateKey) + .keyID(UUID.randomUUID().toString()) + .build(); + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/KeyGeneratorUtils.java b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/KeyGeneratorUtils.java new file mode 100644 index 000000000..49ae4597d --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/KeyGeneratorUtils.java @@ -0,0 +1,26 @@ +package test.org.springdoc.api.app11; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; + +/** + * @author yuta.saito + */ +public final class KeyGeneratorUtils { + private KeyGeneratorUtils() { + //final class constructor should be hidden + } + + static KeyPair generateRsaKey() { + KeyPair keyPair; + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + keyPair = keyPairGenerator.generateKeyPair(); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + return keyPair; + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/SpringDocApp11Test.java b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/SpringDocApp11Test.java new file mode 100644 index 000000000..91cf142a9 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app11/SpringDocApp11Test.java @@ -0,0 +1,56 @@ +/* + * + * * Copyright 2019-2023 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.api.app11; + +import java.util.Optional; + +import io.swagger.v3.core.converter.ModelConverter; +import io.swagger.v3.core.converter.ModelConverters; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.springdoc.core.converters.SchemaPropertyDeprecatingConverter; +import test.org.springdoc.api.AbstractSpringDocTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(properties = { "springdoc.show-oauth2-endpoints=true", "springdoc.model-converters.deprecating-converter.enabled=false" }) +public class SpringDocApp11Test extends AbstractSpringDocTest { + + @BeforeAll + public static void init() { + Optional deprecatingConverterOptional = + ModelConverters.getInstance().getConverters() + .stream().filter(modelConverter -> modelConverter instanceof SchemaPropertyDeprecatingConverter).findAny(); + deprecatingConverterOptional.ifPresent(ModelConverters.getInstance()::removeConverter); + } + + @AfterAll + public static void clean() { + Optional deprecatingConverterOptional = + ModelConverters.getInstance().getConverters() + .stream().filter(modelConverter -> modelConverter instanceof SchemaPropertyDeprecatingConverter).findAny(); + deprecatingConverterOptional.ifPresent(ModelConverters.getInstance()::addConverter); + } + + @SpringBootApplication + static class SpringDocTestApp { + } + +} diff --git a/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/AuthorizationServerConfig.java b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/AuthorizationServerConfig.java new file mode 100644 index 000000000..06a888fe6 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/AuthorizationServerConfig.java @@ -0,0 +1,75 @@ +package test.org.springdoc.api.app12; + +import java.util.UUID; + +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.proc.SecurityContext; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; +import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; +import org.springframework.security.web.SecurityFilterChain; + +/* + * @author yuta.saito + */ +@Configuration +public class AuthorizationServerConfig { + @Bean + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); + http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) + .oidc(oidc -> oidc.clientRegistrationEndpoint(Customizer.withDefaults())); + return http.build(); + } + + @Bean + public RegisteredClientRepository registeredClientRepository() { + RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) + .clientId("messaging-client") + .clientSecret("{noop}secret") + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) + .scope("message.read") + .scope("message.write") + .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) + .build(); + + return new InMemoryRegisteredClientRepository(registeredClient); + } + + @Bean + public OAuth2AuthorizationService authorizationService() { + return new InMemoryOAuth2AuthorizationService(); + } + + @Bean + public AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().build(); + } + + @Bean + public JWKSource jwkSource() { + RSAKey rsaKey = Jwks.generateRsa(); + JWKSet jwkSet = new JWKSet(rsaKey); + return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); + } + + @Bean + public JwtDecoder jwtDecoder(JWKSource jwkSource) { + return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/HelloController.java b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/HelloController.java new file mode 100644 index 000000000..74e1ca709 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/HelloController.java @@ -0,0 +1,17 @@ +package test.org.springdoc.api.app12; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/* + * @author yuta.saito + */ +@RestController +@RequestMapping("/api") +public class HelloController { + @GetMapping("/hello") + public String hello() { + return "Hello"; + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/Jwks.java b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/Jwks.java new file mode 100644 index 000000000..2df2f0a1c --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/Jwks.java @@ -0,0 +1,29 @@ +package test.org.springdoc.api.app12; + + +import java.security.KeyPair; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.UUID; + +import com.nimbusds.jose.jwk.RSAKey; + + +/** + * @author yuta.saito + */ +public final class Jwks { + private Jwks() { + //final class constructor should be hidden + } + + public static RSAKey generateRsa() { + KeyPair keyPair = KeyGeneratorUtils.generateRsaKey(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + return new RSAKey.Builder(publicKey) + .privateKey(privateKey) + .keyID(UUID.randomUUID().toString()) + .build(); + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/KeyGeneratorUtils.java b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/KeyGeneratorUtils.java new file mode 100644 index 000000000..46efd9d2e --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/KeyGeneratorUtils.java @@ -0,0 +1,26 @@ +package test.org.springdoc.api.app12; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; + +/** + * @author yuta.saito + */ +public final class KeyGeneratorUtils { + private KeyGeneratorUtils() { + //final class constructor should be hidden + } + + static KeyPair generateRsaKey() { + KeyPair keyPair; + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + keyPair = keyPairGenerator.generateKeyPair(); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + return keyPair; + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/SpringDocApp12Test.java b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/SpringDocApp12Test.java new file mode 100644 index 000000000..4bfb122eb --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/java/test/org/springdoc/api/app12/SpringDocApp12Test.java @@ -0,0 +1,56 @@ +/* + * + * * Copyright 2019-2023 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.api.app12; + +import java.util.Optional; + +import io.swagger.v3.core.converter.ModelConverter; +import io.swagger.v3.core.converter.ModelConverters; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.springdoc.core.converters.SchemaPropertyDeprecatingConverter; +import test.org.springdoc.api.AbstractSpringDocTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(properties = { "springdoc.show-oauth2-endpoints=true", "springdoc.model-converters.deprecating-converter.enabled=false" }) +public class SpringDocApp12Test extends AbstractSpringDocTest { + + @BeforeAll + public static void init() { + Optional deprecatingConverterOptional = + ModelConverters.getInstance().getConverters() + .stream().filter(modelConverter -> modelConverter instanceof SchemaPropertyDeprecatingConverter).findAny(); + deprecatingConverterOptional.ifPresent(ModelConverters.getInstance()::removeConverter); + } + + @AfterAll + public static void clean() { + Optional deprecatingConverterOptional = + ModelConverters.getInstance().getConverters() + .stream().filter(modelConverter -> modelConverter instanceof SchemaPropertyDeprecatingConverter).findAny(); + deprecatingConverterOptional.ifPresent(ModelConverters.getInstance()::addConverter); + } + + @SpringBootApplication + static class SpringDocTestApp { + } + +} diff --git a/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/resources/results/app11.json b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/resources/results/app11.json new file mode 100644 index 000000000..1f8119986 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/resources/results/app11.json @@ -0,0 +1,605 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/api/hello": { + "get": { + "tags": [ + "hello-controller" + ], + "operationId": "hello", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/oauth2/jwks": { + "get": { + "tags": [ + "authorization-server-endpoints" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/.well-known/oauth-authorization-server": { + "get": { + "tags": [ + "authorization-server-endpoints" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2AuthorizationServerMetadata" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/oauth2/token": { + "post": { + "tags": [ + "authorization-server-endpoints" + ], + "parameters": [ + { + "in": "header", + "name": "Authorization" + } + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "properties": { + "grant_type": { + "type": "string", + "enum": [ + "authorization_code", + "refresh_token", + "client_credentials" + ] + }, + "code": { + "type": "string" + }, + "redirect_uri": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string" + }, + "client_assertion_type": { + "type": "string" + }, + "client_assertion": { + "type": "string" + }, + "additionalParameters": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Token" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/oauth2/authorize": { + "post": { + "tags": [ + "authorization-server-endpoints" + ], + "parameters": [ + { + "name": "parameters", + "in": "query", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/html": {} + } + }, + "302": { + "description": "Moved Temporarily", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/oauth2/introspect": { + "post": { + "tags": [ + "authorization-server-endpoints" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "token_type_hint": { + "type": "string" + }, + "additionalParameters": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2TokenIntrospection" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/oauth2/revoke": { + "post": { + "tags": [ + "authorization-server-endpoints" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "token_type_hint": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/.well-known/openid-configuration": { + "get": { + "tags": [ + "authorization-server-endpoints" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OidcProviderConfiguration" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/userinfo": { + "get": { + "tags": [ + "authorization-server-endpoints" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + } + }, + "components": { + "schemas": { + "OAuth2Error": { + "type": "object", + "properties": { + "errorCode": { + "type": "string" + }, + "description": { + "type": "string" + }, + "uri": { + "type": "string" + } + } + }, + "OAuth2AuthorizationServerMetadata": { + "type": "object", + "properties": { + "issuer": { + "type": "string" + }, + "token_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "revocation_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "introspection_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "code_challenge_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "authorization_endpoint": { + "type": "string" + }, + "token_endpoint": { + "type": "string" + }, + "response_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "grant_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "revocation_endpoint": { + "type": "string" + }, + "introspection_endpoint": { + "type": "string" + }, + "jwks_uri": { + "type": "string" + } + } + }, + "OAuth2Token": { + "type": "object", + "properties": { + "scope": { + "type": "string" + }, + "expires_in": { + "type": "integer", + "format": "int64" + }, + "access_token": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "token_type": { + "type": "string" + } + } + }, + "OAuth2TokenIntrospection": { + "type": "object", + "properties": { + "nbf": { + "type": "integer", + "format": "int64" + }, + "scope": { + "type": "string" + }, + "jti": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "iss": { + "type": "string" + }, + "aud": { + "type": "array", + "items": { + "type": "string" + } + }, + "token_type": { + "type": "string" + }, + "exp": { + "type": "integer", + "format": "int64" + }, + "sub": { + "type": "string" + }, + "iat": { + "type": "integer", + "format": "int64" + } + } + }, + "OidcProviderConfiguration": { + "type": "object", + "properties": { + "token_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "revocation_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "introspection_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "code_challenge_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "id_token_signing_alg_values_supported": { + "type": "string" + }, + "authorization_endpoint": { + "type": "string" + }, + "token_endpoint": { + "type": "string" + }, + "userinfo_endpoint": { + "type": "string" + }, + "subject_types_supported": { + "type": "string" + }, + "scopes_supported": { + "type": "string" + }, + "response_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "grant_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "revocation_endpoint": { + "type": "string" + }, + "introspection_endpoint": { + "type": "string" + }, + "issuer": { + "type": "string" + }, + "jwks_uri": { + "type": "string" + } + } + } + } + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/resources/results/app12.json b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/resources/results/app12.json new file mode 100644 index 000000000..8925e7038 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-security-tests/src/test/resources/results/app12.json @@ -0,0 +1,788 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/api/hello": { + "get": { + "tags": [ + "hello-controller" + ], + "operationId": "hello", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/oauth2/jwks": { + "get": { + "tags": [ + "authorization-server-endpoints" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/.well-known/oauth-authorization-server": { + "get": { + "tags": [ + "authorization-server-endpoints" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2AuthorizationServerMetadata" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/oauth2/token": { + "post": { + "tags": [ + "authorization-server-endpoints" + ], + "parameters": [ + { + "in": "header", + "name": "Authorization" + } + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "properties": { + "grant_type": { + "type": "string", + "enum": [ + "authorization_code", + "refresh_token", + "client_credentials" + ] + }, + "code": { + "type": "string" + }, + "redirect_uri": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string" + }, + "client_assertion_type": { + "type": "string" + }, + "client_assertion": { + "type": "string" + }, + "additionalParameters": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Token" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/oauth2/authorize": { + "post": { + "tags": [ + "authorization-server-endpoints" + ], + "parameters": [ + { + "name": "parameters", + "in": "query", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/html": {} + } + }, + "302": { + "description": "Moved Temporarily", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/oauth2/introspect": { + "post": { + "tags": [ + "authorization-server-endpoints" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "token_type_hint": { + "type": "string" + }, + "additionalParameters": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2TokenIntrospection" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/oauth2/revoke": { + "post": { + "tags": [ + "authorization-server-endpoints" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "token_type_hint": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/.well-known/openid-configuration": { + "get": { + "tags": [ + "authorization-server-endpoints" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OidcProviderConfiguration" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/userinfo": { + "get": { + "tags": [ + "authorization-server-endpoints" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/connect/register": { + "post": { + "tags": [ + "authorization-server-endpoints" + ], + "parameters": [ + { + "name": "Authorization", + "in": "header" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientRegistrationRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientRegistrationResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuth2Error" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + } + }, + "components": { + "schemas": { + "OAuth2Error": { + "type": "object", + "properties": { + "errorCode": { + "type": "string" + }, + "description": { + "type": "string" + }, + "uri": { + "type": "string" + } + } + }, + "OAuth2AuthorizationServerMetadata": { + "type": "object", + "properties": { + "issuer": { + "type": "string" + }, + "token_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "revocation_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "introspection_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "code_challenge_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "authorization_endpoint": { + "type": "string" + }, + "token_endpoint": { + "type": "string" + }, + "response_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "grant_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "revocation_endpoint": { + "type": "string" + }, + "introspection_endpoint": { + "type": "string" + }, + "jwks_uri": { + "type": "string" + } + } + }, + "OAuth2Token": { + "type": "object", + "properties": { + "scope": { + "type": "string" + }, + "expires_in": { + "type": "integer", + "format": "int64" + }, + "access_token": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "token_type": { + "type": "string" + } + } + }, + "OAuth2TokenIntrospection": { + "type": "object", + "properties": { + "nbf": { + "type": "integer", + "format": "int64" + }, + "scope": { + "type": "string" + }, + "jti": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "iss": { + "type": "string" + }, + "aud": { + "type": "array", + "items": { + "type": "string" + } + }, + "token_type": { + "type": "string" + }, + "exp": { + "type": "integer", + "format": "int64" + }, + "sub": { + "type": "string" + }, + "iat": { + "type": "integer", + "format": "int64" + } + } + }, + "OidcProviderConfiguration": { + "type": "object", + "properties": { + "token_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "revocation_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "introspection_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "code_challenge_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "id_token_signing_alg_values_supported": { + "type": "string" + }, + "authorization_endpoint": { + "type": "string" + }, + "token_endpoint": { + "type": "string" + }, + "userinfo_endpoint": { + "type": "string" + }, + "subject_types_supported": { + "type": "string" + }, + "scopes_supported": { + "type": "string" + }, + "response_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "grant_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "revocation_endpoint": { + "type": "string" + }, + "introspection_endpoint": { + "type": "string" + }, + "issuer": { + "type": "string" + }, + "jwks_uri": { + "type": "string" + } + } + }, + "ClientRegistrationResponse": { + "type": "object", + "properties": { + "token_endpoint_auth_method": { + "type": "string" + }, + "id_token_signed_response_alg": { + "type": "string" + }, + "response_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "client_secret": { + "type": "string" + }, + "client_secret_expires_at": { + "type": "integer", + "format": "int64" + }, + "registration_access_token": { + "type": "string" + }, + "registration_client_uri": { + "type": "string" + }, + "client_id_issued_at": { + "type": "integer", + "format": "int64" + }, + "redirect_uris": { + "type": "array", + "items": { + "type": "string" + } + }, + "scope": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "token_endpoint_auth_signing_alg": { + "type": "string" + }, + "grant_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "jwks_uri": { + "type": "string" + }, + "client_name": { + "type": "string" + } + } + }, + "ClientRegistrationRequest": { + "type": "object", + "properties": { + "token_endpoint_auth_method": { + "type": "string" + }, + "id_token_signed_response_alg": { + "type": "string" + }, + "response_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "client_secret": { + "type": "string" + }, + "client_secret_expires_at": { + "type": "string", + "format": "date-time" + }, + "client_id_issued_at": { + "type": "string", + "format": "date-time" + }, + "redirect_uris": { + "type": "array", + "items": { + "type": "string" + } + }, + "scope": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "token_endpoint_auth_signing_alg": { + "type": "string" + }, + "grant_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "jwks_uri": { + "type": "string" + }, + "client_name": { + "type": "string" + } + } + } + } + } +}