Skip to content

Commit

Permalink
fix spring authorization server response. Fixes springdoc#2123
Browse files Browse the repository at this point in the history
  • Loading branch information
uc4w6c committed Mar 16, 2023
1 parent 0d32630 commit 2e3100a
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 720 deletions.
4 changes: 2 additions & 2 deletions springdoc-openapi-security/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<artifactId>springdoc-openapi</artifactId>
<groupId>org.springdoc</groupId>
<version>1.6.15-SNAPSHOT</version>
<version>1.6.16-SNAPSHOT</version>
</parent>
<artifactId>springdoc-openapi-security</artifactId>
<properties>
Expand Down Expand Up @@ -79,4 +79,4 @@
</plugin>
</plugins>
</build>
</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import java.lang.reflect.Field;

import com.nimbusds.jose.jwk.JWKSet;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.headers.Header;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MapSchema;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.Schema;
Expand All @@ -23,6 +24,7 @@
import org.springdoc.core.SpringDocAnnotationsUtils;
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
import org.springdoc.security.oauth2.SpringDocOAuth2AuthorizationServerMetadata;
import org.springdoc.security.oauth2.SpringDocOAuth2Token;
import org.springdoc.security.oauth2.SpringDocOAuth2TokenIntrospection;

import org.springframework.beans.BeansException;
Expand All @@ -31,10 +33,7 @@
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken;
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;
Expand All @@ -49,6 +48,7 @@
import org.springframework.security.web.util.matcher.RequestMatcher;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.http.MediaType.TEXT_HTML_VALUE;

/**
* The type Spring doc security o auth 2 customizer.
Expand Down Expand Up @@ -95,7 +95,10 @@ private void getOAuth2TokenRevocationEndpointFilter(OpenAPI openAPI, SecurityFil
Object oAuth2EndpointFilter =
new SpringDocSecurityOAuth2EndpointUtils(OAuth2TokenRevocationEndpointFilter.class).findEndpoint(securityFilterChain);
if (oAuth2EndpointFilter != null) {
ApiResponses apiResponses = buildApiResponsesWithBadRequest(SpringDocAnnotationsUtils.resolveSchemaFromType(OAuth2TokenRevocationAuthenticationToken.class, openAPI.getComponents(), null), openAPI);
ApiResponses apiResponses = new ApiResponses();
apiResponses.addApiResponse(String.valueOf(HttpStatus.OK.value()), new ApiResponse().description(HttpStatus.OK.getReasonPhrase()));
buildApiResponsesOnInternalServerError(apiResponses);
buildApiResponsesOnBadRequest(apiResponses, openAPI);

Operation operation = buildOperation(apiResponses);
Schema<?> schema = new ObjectSchema()
Expand All @@ -119,15 +122,19 @@ private void getOAuth2TokenIntrospectionEndpointFilter(OpenAPI openAPI, Security
Object oAuth2EndpointFilter =
new SpringDocSecurityOAuth2EndpointUtils(OAuth2TokenIntrospectionEndpointFilter.class).findEndpoint(securityFilterChain);
if (oAuth2EndpointFilter != null) {
ApiResponses apiResponses = buildApiResponsesWithBadRequest(SpringDocAnnotationsUtils.resolveSchemaFromType(SpringDocOAuth2TokenIntrospection.class, openAPI.getComponents(), null), openAPI);
ApiResponses apiResponses = new ApiResponses();
buildApiResponsesOnSuccess(apiResponses, SpringDocAnnotationsUtils.resolveSchemaFromType(SpringDocOAuth2TokenIntrospection.class, openAPI.getComponents(), null));
buildApiResponsesOnInternalServerError(apiResponses);
buildApiResponsesOnBadRequest(apiResponses, openAPI);

Operation operation = buildOperation(apiResponses);
Schema<?> schema = new ObjectSchema()
Schema<?> requestSchema = new ObjectSchema()
.addProperty("token", new StringSchema())
.addProperty(OAuth2ParameterNames.TOKEN_TYPE_HINT, new StringSchema())
.addProperty("additionalParameters", new ObjectSchema().additionalProperties(new StringSchema()));

String mediaType = org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
RequestBody requestBody = new RequestBody().content(new Content().addMediaType(mediaType, new MediaType().schema(schema)));
RequestBody requestBody = new RequestBody().content(new Content().addMediaType(mediaType, new MediaType().schema(requestSchema)));
operation.setRequestBody(requestBody);
buildPath(oAuth2EndpointFilter, "tokenIntrospectionEndpointMatcher", openAPI, operation, HttpMethod.POST);
}
Expand All @@ -143,7 +150,9 @@ private void getOAuth2AuthorizationServerMetadataEndpoint(OpenAPI openAPI, Secur
Object oAuth2EndpointFilter =
new SpringDocSecurityOAuth2EndpointUtils(OAuth2AuthorizationServerMetadataEndpointFilter.class).findEndpoint(securityFilterChain);
if (oAuth2EndpointFilter != null) {
ApiResponses apiResponses = buildApiResponses(SpringDocAnnotationsUtils.resolveSchemaFromType(SpringDocOAuth2AuthorizationServerMetadata.class, openAPI.getComponents(), null));
ApiResponses apiResponses = new ApiResponses();
buildApiResponsesOnSuccess(apiResponses, SpringDocAnnotationsUtils.resolveSchemaFromType(SpringDocOAuth2AuthorizationServerMetadata.class, openAPI.getComponents(), null));
buildApiResponsesOnInternalServerError(apiResponses);
Operation operation = buildOperation(apiResponses);
buildPath(oAuth2EndpointFilter, "requestMatcher", openAPI, operation, HttpMethod.GET);
}
Expand All @@ -159,7 +168,17 @@ private void getNimbusJwkSetEndpoint(OpenAPI openAPI, SecurityFilterChain securi
Object oAuth2EndpointFilter =
new SpringDocSecurityOAuth2EndpointUtils(NimbusJwkSetEndpointFilter.class).findEndpoint(securityFilterChain);
if (oAuth2EndpointFilter != null) {
ApiResponses apiResponses = buildApiResponses(SpringDocAnnotationsUtils.resolveSchemaFromType(JWKSet.class, openAPI.getComponents(), null));
ApiResponses apiResponses = new ApiResponses();
Schema<?> schema = new MapSchema();
schema.addProperty("keys", new ArraySchema().items(new ObjectSchema().additionalProperties(true)));

ApiResponse response = new ApiResponse().description(HttpStatus.OK.getReasonPhrase()).content(new Content().addMediaType(
APPLICATION_JSON_VALUE,
new MediaType().schema(schema)));
apiResponses.addApiResponse(String.valueOf(HttpStatus.OK.value()), response);
buildApiResponsesOnInternalServerError(apiResponses);
buildApiResponsesOnBadRequest(apiResponses, openAPI);

Operation operation = buildOperation(apiResponses);
operation.responses(apiResponses);
buildPath(oAuth2EndpointFilter, "requestMatcher", openAPI, operation, HttpMethod.GET);
Expand All @@ -177,7 +196,10 @@ private void getOAuth2TokenEndpoint(OpenAPI openAPI, SecurityFilterChain securit
new SpringDocSecurityOAuth2EndpointUtils(OAuth2TokenEndpointFilter.class).findEndpoint(securityFilterChain);

if (oAuth2EndpointFilter != null) {
ApiResponses apiResponses = buildApiResponsesWithBadRequest(SpringDocAnnotationsUtils.resolveSchemaFromType(OAuth2AccessTokenResponse.class, openAPI.getComponents(), null), openAPI);
ApiResponses apiResponses = new ApiResponses();
buildApiResponsesOnSuccess(apiResponses, SpringDocAnnotationsUtils.resolveSchemaFromType(SpringDocOAuth2Token.class, openAPI.getComponents(), null));
buildApiResponsesOnInternalServerError(apiResponses);
buildApiResponsesOnBadRequest(apiResponses, openAPI);
buildOAuth2Error(openAPI, apiResponses, HttpStatus.UNAUTHORIZED);
Operation operation = buildOperation(apiResponses);
Schema<?> schema = new ObjectSchema().additionalProperties(new StringSchema());
Expand All @@ -196,7 +218,14 @@ private void getOAuth2AuthorizationEndpoint(OpenAPI openAPI, SecurityFilterChain
Object oAuth2EndpointFilter =
new SpringDocSecurityOAuth2EndpointUtils(OAuth2AuthorizationEndpointFilter.class).findEndpoint(securityFilterChain);
if (oAuth2EndpointFilter != null) {
ApiResponses apiResponses = buildApiResponsesWithBadRequest(SpringDocAnnotationsUtils.resolveSchemaFromType(OAuth2AuthorizationConsentAuthenticationToken.class, openAPI.getComponents(), null), openAPI);
ApiResponses apiResponses = new ApiResponses();

ApiResponse response = new ApiResponse().description(HttpStatus.OK.getReasonPhrase()).content(new Content().addMediaType(
TEXT_HTML_VALUE,
new MediaType()));
apiResponses.addApiResponse(String.valueOf(HttpStatus.OK.value()), response);
buildApiResponsesOnInternalServerError(apiResponses);
buildApiResponsesOnBadRequest(apiResponses, openAPI);
apiResponses.addApiResponse(String.valueOf(HttpStatus.MOVED_TEMPORARILY.value()),
new ApiResponse().description(HttpStatus.MOVED_TEMPORARILY.getReasonPhrase())
.addHeaderObject("Location", new Header().schema(new StringSchema())));
Expand All @@ -221,30 +250,39 @@ private Operation buildOperation(ApiResponses apiResponses) {
}

/**
* Build api responses api responses.
* Build api responses api responses on success.
*
* @param apiResponses the api responses
* @param schema the schema
* @return the api responses
*/
private ApiResponses buildApiResponses(Schema schema) {
ApiResponses apiResponses = new ApiResponses();
private ApiResponses buildApiResponsesOnSuccess(ApiResponses apiResponses, Schema schema) {
ApiResponse response = new ApiResponse().description(HttpStatus.OK.getReasonPhrase()).content(new Content().addMediaType(
APPLICATION_JSON_VALUE,
new MediaType().schema(schema)));
apiResponses.addApiResponse(String.valueOf(HttpStatus.OK.value()), response);
return apiResponses;
}

/**
* Build api responses api responses on internal server error.
*
* @param apiResponses the api responses
* @return the api responses
*/
private ApiResponses buildApiResponsesOnInternalServerError(ApiResponses apiResponses) {
apiResponses.addApiResponse(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), new ApiResponse().description(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()));
return apiResponses;
}

/**
* Build api responses with bad request api responses.
* Build api responses on bad request.
*
* @param schema the schema
* @param apiResponses the api responses
* @param openAPI the open api
* @return the api responses
*/
private ApiResponses buildApiResponsesWithBadRequest(Schema schema, OpenAPI openAPI) {
ApiResponses apiResponses = buildApiResponses(schema);
private ApiResponses buildApiResponsesOnBadRequest(ApiResponses apiResponses, OpenAPI openAPI) {
buildOAuth2Error(openAPI, apiResponses, HttpStatus.BAD_REQUEST);
return apiResponses;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,154 +1,54 @@
package org.springdoc.security.oauth2;

import java.net.URL;
import java.time.Instant;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;

import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimAccessor;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames;

/**
* The type Spring doc o auth 2 authorization server metadata.
*
* @see <a href="https://github.com/spring-projects/spring-authorization-server/blob/main/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadata.java">OAuth2AuthorizationServerMetadata</a>
* @author bnasslahsen
* @author yuta.saito
*/
@Schema(name = "OAuth2AuthorizationServerMetadata")
public class SpringDocOAuth2AuthorizationServerMetadata implements OAuth2AuthorizationServerMetadataClaimAccessor {


@Override
public Map<String, Object> getClaims() {
return null;
}

@Override
public <T> T getClaim(String claim) {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getClaim(claim);
}

@Override
public boolean hasClaim(String claim) {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.hasClaim(claim);
}

@Override
public Boolean containsClaim(String claim) {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.containsClaim(claim);
}

@Override
public String getClaimAsString(String claim) {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getClaimAsString(claim);
}

@Override
public Boolean getClaimAsBoolean(String claim) {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getClaimAsBoolean(claim);
}

@Override
public Instant getClaimAsInstant(String claim) {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getClaimAsInstant(claim);
}

@Override
public URL getClaimAsURL(String claim) {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getClaimAsURL(claim);
}

@Override
public Map<String, Object> getClaimAsMap(String claim) {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getClaimAsMap(claim);
}

@Override
public List<String> getClaimAsStringList(String claim) {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getClaimAsStringList(claim);
}

@Override
public interface SpringDocOAuth2AuthorizationServerMetadata {
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.ISSUER)
public URL getIssuer() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getIssuer();
}
String issuer();

@Override
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT)
public URL getAuthorizationEndpoint() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getAuthorizationEndpoint();
}
String authorizationEndpoint();

@Override
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT)
public URL getTokenEndpoint() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getTokenEndpoint();
}
String tokenEndpoint();

@Override
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED)
public List<String> getTokenEndpointAuthenticationMethods() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getTokenEndpointAuthenticationMethods();
}
List<String> tokenEndpointAuthMethodsSupported();

@Override
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI)
public URL getJwkSetUrl() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getJwkSetUrl();
}

@Override
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED)
public List<String> getScopes() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getScopes();
}
String jwksUri();

@Override
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED)
public List<String> getResponseTypes() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getResponseTypes();
}
List<String> responseTypesSupported();

@Override
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED)
public List<String> getGrantTypes() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getGrantTypes();
}
List<String> grantTypesSupported();

@Override
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT)
public URL getTokenRevocationEndpoint() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getTokenRevocationEndpoint();
}
String revocationEndpoint();

@Override
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED)
public List<String> getTokenRevocationEndpointAuthenticationMethods() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getTokenRevocationEndpointAuthenticationMethods();
}
List<String> revocationEndpointAuthMethodsSupported();

@Override
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT)
public URL getTokenIntrospectionEndpoint() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getTokenIntrospectionEndpoint();
}
String introspectionEndpoint();

@Override
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED)
public List<String> getTokenIntrospectionEndpointAuthenticationMethods() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getTokenIntrospectionEndpointAuthenticationMethods();
}

@Override
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.REGISTRATION_ENDPOINT)
public URL getClientRegistrationEndpoint() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getClientRegistrationEndpoint();
}
List<String> introspectionEndpointAuthMethodsSupported();

@Override
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED)
public List<String> getCodeChallengeMethods() {
return OAuth2AuthorizationServerMetadataClaimAccessor.super.getCodeChallengeMethods();
}
List<String> codeChallengeMethodsSupported();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.springdoc.security.oauth2;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import io.swagger.v3.oas.annotations.media.Schema;

/**
* The type Spring doc o auth 2 token.
*
* @see <a href="https://github.com/spring-projects/spring-security/blob/main/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/DefaultOAuth2AccessTokenResponseMapConverter.java">DefaultOAuth2AccessTokenResponseMapConverter</a>
* @author yuta.saito
*/
@Schema(name = "OAuth2Token")
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public interface SpringDocOAuth2Token {
String getAccessToken();

String getTokenType();

long getExpiresIn();

String getScope();

String getRefreshToken();
}

0 comments on commit 2e3100a

Please sign in to comment.