Skip to content

feat: support OIDC #2340

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

Merged
merged 1 commit into from
Aug 16, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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<RequestMatcher> requestMatchers = (Iterable<RequestMatcher>) 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<RequestMatcher>) 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();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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 <a href="https://github.com/spring-projects/spring-authorization-server/blob/main/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientRegistration.java">OidcClientRegistration</a>
* @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/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java">OidcClientRegistrationHttpMessageConverter</a>
* @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<String> redirectUris();

@JsonProperty(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD)
String tokenEndpointAuthenticationMethod();

@JsonProperty(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG)
String tokenEndpointAuthenticationSigningAlgorithm();

@JsonProperty(OidcClientMetadataClaimNames.GRANT_TYPES)
List<String> grantTypes();

@JsonProperty(OidcClientMetadataClaimNames.RESPONSE_TYPES)
List<String> responseType();

@JsonProperty(OidcClientMetadataClaimNames.SCOPE)
String scopes();

@JsonProperty(OidcClientMetadataClaimNames.JWKS_URI)
String jwkSetUrl();

@JsonProperty(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG)
String idTokenSignedResponseAlgorithm();
}
Original file line number Diff line number Diff line change
@@ -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 <a href="https://github.com/spring-projects/spring-authorization-server/blob/main/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientRegistration.java">OidcClientRegistration</a>
* @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/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java">OidcClientRegistrationHttpMessageConverter</a>
* @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<String> redirectUris();

@JsonProperty(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD)
String tokenEndpointAuthenticationMethod();

@JsonProperty(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG)
String tokenEndpointAuthenticationSigningAlgorithm();

@JsonProperty(OidcClientMetadataClaimNames.GRANT_TYPES)
List<String> grantTypes();

@JsonProperty(OidcClientMetadataClaimNames.RESPONSE_TYPES)
List<String> 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();
}
Original file line number Diff line number Diff line change
@@ -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 <a href="https://github.com/spring-projects/spring-authorization-server/blob/main/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderConfiguration.java">OidcProviderConfiguration</a>
* @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<String> tokenEndpointAuthMethodsSupported();

@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI)
String jwksUri();

@JsonProperty(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT)
String userInfoEndpoint();

@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED)
List<String> responseTypesSupported();

@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED)
List<String> grantTypesSupported();

@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT)
String revocationEndpoint();

@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED)
List<String> revocationEndpointAuthMethodsSupported();

@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT)
String introspectionEndpoint();

@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED)
List<String> 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<String> codeChallengeMethodsSupported();
}
Loading