Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix spring authorization server response. Fixes #2123 #2141

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions springdoc-openapi-security/pom.xml
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>
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
@@ -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();
}
@@ -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();
}