Skip to content

Commit 3b19cb8

Browse files
committed
feat: support OIDC
1 parent af9c5f1 commit 3b19cb8

17 files changed

+2878
-9
lines changed

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSecurityOAuth2Customizer.java

+103-9
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
import org.springdoc.core.configuration.oauth2.SpringDocOAuth2AuthorizationServerMetadata;
2727
import org.springdoc.core.configuration.oauth2.SpringDocOAuth2Token;
2828
import org.springdoc.core.configuration.oauth2.SpringDocOAuth2TokenIntrospection;
29+
import org.springdoc.core.configuration.oauth2.SpringDocOidcClientRegistrationRequest;
30+
import org.springdoc.core.configuration.oauth2.SpringDocOidcClientRegistrationResponse;
31+
import org.springdoc.core.configuration.oauth2.SpringDocOidcProviderConfiguration;
2932
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
30-
import org.springdoc.core.utils.SpringDocAnnotationsUtils;
3133

3234
import org.springframework.beans.BeansException;
3335
import org.springframework.context.ApplicationContext;
@@ -37,6 +39,9 @@
3739
import org.springframework.security.oauth2.core.AuthorizationGrantType;
3840
import org.springframework.security.oauth2.core.OAuth2Error;
3941
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
42+
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
43+
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
44+
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter;
4045
import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
4146
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
4247
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
@@ -85,6 +90,9 @@ public void customise(OpenAPI openAPI) {
8590
getOAuth2AuthorizationEndpoint(openAPI, filterChain);
8691
getOAuth2TokenIntrospectionEndpointFilter(openAPI, filterChain);
8792
getOAuth2TokenRevocationEndpointFilter(openAPI, filterChain);
93+
getOidcProviderConfigurationEndpoint(openAPI, filterChain);
94+
getOidcUserInfoEndpoint(openAPI, filterChain);
95+
getOidcClientRegistrationEndpoint(openAPI, filterChain);
8896
}
8997
}
9098

@@ -259,6 +267,76 @@ private void getOAuth2AuthorizationEndpoint(OpenAPI openAPI, SecurityFilterChain
259267
}
260268
}
261269

270+
/**
271+
* Gets OpenID Provider endpoint filter
272+
*
273+
* @param openAPI the open api
274+
* @param securityFilterChain the security filter chain
275+
*/
276+
private void getOidcProviderConfigurationEndpoint(OpenAPI openAPI, SecurityFilterChain securityFilterChain) {
277+
Object oAuth2EndpointFilter =
278+
new SpringDocSecurityOAuth2EndpointUtils(OidcProviderConfigurationEndpointFilter.class).findEndpoint(securityFilterChain);
279+
280+
if (oAuth2EndpointFilter != null) {
281+
ApiResponses apiResponses = new ApiResponses();
282+
buildApiResponsesOnSuccess(apiResponses, AnnotationsUtils.resolveSchemaFromType(SpringDocOidcProviderConfiguration.class, openAPI.getComponents(), null));
283+
buildApiResponsesOnInternalServerError(apiResponses);
284+
Operation operation = buildOperation(apiResponses);
285+
buildPath(oAuth2EndpointFilter, "requestMatcher", openAPI, operation, HttpMethod.GET);
286+
}
287+
}
288+
289+
/**
290+
* Gets OpenID UserInfo endpoint filter
291+
*
292+
* @param openAPI the open api
293+
* @param securityFilterChain the security filter chain
294+
*/
295+
private void getOidcUserInfoEndpoint(OpenAPI openAPI, SecurityFilterChain securityFilterChain) {
296+
Object oAuth2EndpointFilter =
297+
new SpringDocSecurityOAuth2EndpointUtils(OidcUserInfoEndpointFilter.class).findEndpoint(securityFilterChain);
298+
299+
if (oAuth2EndpointFilter != null) {
300+
ApiResponses apiResponses = new ApiResponses();
301+
Schema<?> schema = new ObjectSchema().additionalProperties(new StringSchema());
302+
buildApiResponsesOnSuccess(apiResponses, schema);
303+
buildApiResponsesOnInternalServerError(apiResponses);
304+
Operation operation = buildOperation(apiResponses);
305+
buildPath(oAuth2EndpointFilter, "userInfoEndpointMatcher", openAPI, operation, HttpMethod.GET);
306+
}
307+
}
308+
309+
/**
310+
* Gets OpenID Client Registration endpoint filter
311+
*
312+
* @param openAPI the open api
313+
* @param securityFilterChain the security filter chain
314+
*/
315+
private void getOidcClientRegistrationEndpoint(OpenAPI openAPI, SecurityFilterChain securityFilterChain) {
316+
Object oAuth2EndpointFilter =
317+
new SpringDocSecurityOAuth2EndpointUtils(OidcClientRegistrationEndpointFilter.class).findEndpoint(securityFilterChain);
318+
319+
if (oAuth2EndpointFilter != null) {
320+
ApiResponses apiResponses = new ApiResponses();
321+
buildApiResponsesOnCreated(apiResponses, AnnotationsUtils.resolveSchemaFromType(SpringDocOidcClientRegistrationResponse.class, openAPI.getComponents(), null));
322+
buildApiResponsesOnInternalServerError(apiResponses);
323+
buildApiResponsesOnBadRequest(apiResponses, openAPI);
324+
buildOAuth2Error(openAPI, apiResponses, HttpStatus.UNAUTHORIZED);
325+
buildOAuth2Error(openAPI, apiResponses, HttpStatus.FORBIDDEN);
326+
Operation operation = buildOperation(apiResponses);
327+
328+
// OidcClientRegistration
329+
Schema schema = AnnotationsUtils.resolveSchemaFromType(SpringDocOidcClientRegistrationRequest.class, openAPI.getComponents(), null);
330+
331+
String mediaType = org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
332+
RequestBody requestBody = new RequestBody().content(new Content().addMediaType(mediaType, new MediaType().schema(schema)));
333+
operation.setRequestBody(requestBody);
334+
operation.addParametersItem(new HeaderParameter().name("Authorization"));
335+
336+
buildPath(oAuth2EndpointFilter, "clientRegistrationEndpointMatcher", openAPI, operation, HttpMethod.POST);
337+
}
338+
}
339+
262340
/**
263341
* Build operation operation.
264342
*
@@ -287,6 +365,21 @@ private ApiResponses buildApiResponsesOnSuccess(ApiResponses apiResponses, Schem
287365
return apiResponses;
288366
}
289367

368+
/**
369+
* Build api responses api responses on created.
370+
*
371+
* @param apiResponses the api responses
372+
* @param schema the schema
373+
* @return the api responses
374+
*/
375+
private ApiResponses buildApiResponsesOnCreated(ApiResponses apiResponses, Schema schema) {
376+
ApiResponse response = new ApiResponse().description(HttpStatus.CREATED.getReasonPhrase()).content(new Content().addMediaType(
377+
APPLICATION_JSON_VALUE,
378+
new MediaType().schema(schema)));
379+
apiResponses.addApiResponse(String.valueOf(HttpStatus.CREATED.value()), response);
380+
return apiResponses;
381+
}
382+
290383
/**
291384
* Build api responses api responses on internal server error.
292385
*
@@ -338,22 +431,23 @@ private void buildPath(Object oAuth2EndpointFilter, String authorizationEndpoint
338431
Field tokenEndpointMatcherField = FieldUtils.getDeclaredField(oAuth2EndpointFilter.getClass(), authorizationEndpointMatcher, true);
339432
RequestMatcher endpointMatcher = (RequestMatcher) tokenEndpointMatcherField.get(oAuth2EndpointFilter);
340433
String path = null;
341-
if (endpointMatcher instanceof AntPathRequestMatcher)
342-
path = ((AntPathRequestMatcher) endpointMatcher).getPattern();
343-
else if (endpointMatcher instanceof OrRequestMatcher) {
344-
OrRequestMatcher endpointMatchers = (OrRequestMatcher) endpointMatcher;
434+
if (endpointMatcher instanceof AntPathRequestMatcher antPathRequestMatcher)
435+
path = antPathRequestMatcher.getPattern();
436+
else if (endpointMatcher instanceof OrRequestMatcher endpointMatchers) {
345437
Field requestMatchersField = FieldUtils.getDeclaredField(OrRequestMatcher.class, "requestMatchers", true);
346438
Iterable<RequestMatcher> requestMatchers = (Iterable<RequestMatcher>) requestMatchersField.get(endpointMatchers);
347439
for (RequestMatcher requestMatcher : requestMatchers) {
348-
if (requestMatcher instanceof OrRequestMatcher) {
349-
OrRequestMatcher orRequestMatcher = (OrRequestMatcher) requestMatcher;
440+
if (requestMatcher instanceof OrRequestMatcher orRequestMatcher) {
350441
requestMatchersField = FieldUtils.getDeclaredField(OrRequestMatcher.class, "requestMatchers", true);
351442
requestMatchers = (Iterable<RequestMatcher>) requestMatchersField.get(orRequestMatcher);
352443
for (RequestMatcher matcher : requestMatchers) {
353-
if (matcher instanceof AntPathRequestMatcher)
354-
path = ((AntPathRequestMatcher) matcher).getPattern();
444+
if (matcher instanceof AntPathRequestMatcher antPathRequestMatcher)
445+
path = antPathRequestMatcher.getPattern();
355446
}
356447
}
448+
else if (requestMatcher instanceof AntPathRequestMatcher antPathRequestMatcher) {
449+
path = antPathRequestMatcher.getPattern();
450+
}
357451
}
358452
}
359453

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.springdoc.core.configuration.oauth2;
2+
3+
import java.time.Instant;
4+
import java.util.List;
5+
6+
import com.fasterxml.jackson.annotation.JsonProperty;
7+
import io.swagger.v3.oas.annotations.media.Schema;
8+
9+
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames;
10+
11+
/**
12+
* The type Spring doc OpenID Client Registration Request
13+
*
14+
* @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>
15+
* @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>
16+
* @author yuta.saito
17+
*/
18+
@Schema(name = "ClientRegistrationRequest")
19+
public interface SpringDocOidcClientRegistrationRequest {
20+
@JsonProperty(OidcClientMetadataClaimNames.CLIENT_ID)
21+
String clientId();
22+
23+
@JsonProperty(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT)
24+
Instant clientIdIssuedAt();
25+
26+
@JsonProperty(OidcClientMetadataClaimNames.CLIENT_SECRET)
27+
String clientSecret();
28+
29+
@JsonProperty(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT)
30+
Instant CLIENT_SECRET_EXPIRES_AT();
31+
32+
@JsonProperty(OidcClientMetadataClaimNames.CLIENT_NAME)
33+
String clientName();
34+
35+
@JsonProperty(OidcClientMetadataClaimNames.REDIRECT_URIS)
36+
List<String> redirectUris();
37+
38+
@JsonProperty(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD)
39+
String tokenEndpointAuthenticationMethod();
40+
41+
@JsonProperty(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG)
42+
String tokenEndpointAuthenticationSigningAlgorithm();
43+
44+
@JsonProperty(OidcClientMetadataClaimNames.GRANT_TYPES)
45+
List<String> grantTypes();
46+
47+
@JsonProperty(OidcClientMetadataClaimNames.RESPONSE_TYPES)
48+
List<String> responseType();
49+
50+
@JsonProperty(OidcClientMetadataClaimNames.SCOPE)
51+
String scopes();
52+
53+
@JsonProperty(OidcClientMetadataClaimNames.JWKS_URI)
54+
String jwkSetUrl();
55+
56+
@JsonProperty(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG)
57+
String idTokenSignedResponseAlgorithm();
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package org.springdoc.core.configuration.oauth2;
2+
3+
import java.time.Instant;
4+
import java.util.List;
5+
6+
import com.fasterxml.jackson.annotation.JsonProperty;
7+
import io.swagger.v3.oas.annotations.media.Schema;
8+
9+
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames;
10+
11+
/**
12+
* The type Spring doc OpenID Client Registration Request
13+
*
14+
* @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>
15+
* @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>
16+
* @author yuta.saito
17+
*/
18+
@Schema(name = "ClientRegistrationResponse")
19+
public interface SpringDocOidcClientRegistrationResponse {
20+
@JsonProperty(OidcClientMetadataClaimNames.CLIENT_ID)
21+
String clientId();
22+
23+
@JsonProperty(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT)
24+
long clientIdIssuedAt();
25+
26+
@JsonProperty(OidcClientMetadataClaimNames.CLIENT_SECRET)
27+
String clientSecret();
28+
29+
@JsonProperty(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT)
30+
long CLIENT_SECRET_EXPIRES_AT();
31+
32+
@JsonProperty(OidcClientMetadataClaimNames.CLIENT_NAME)
33+
String clientName();
34+
35+
@JsonProperty(OidcClientMetadataClaimNames.REDIRECT_URIS)
36+
List<String> redirectUris();
37+
38+
@JsonProperty(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD)
39+
String tokenEndpointAuthenticationMethod();
40+
41+
@JsonProperty(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG)
42+
String tokenEndpointAuthenticationSigningAlgorithm();
43+
44+
@JsonProperty(OidcClientMetadataClaimNames.GRANT_TYPES)
45+
List<String> grantTypes();
46+
47+
@JsonProperty(OidcClientMetadataClaimNames.RESPONSE_TYPES)
48+
List<String> responseType();
49+
50+
@JsonProperty(OidcClientMetadataClaimNames.SCOPE)
51+
String scopes();
52+
53+
@JsonProperty(OidcClientMetadataClaimNames.JWKS_URI)
54+
String jwkSetUrl();
55+
56+
@JsonProperty(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG)
57+
String idTokenSignedResponseAlgorithm();
58+
59+
@JsonProperty(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN)
60+
String registrationAccessToken();
61+
62+
@JsonProperty(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI)
63+
String registrationClientUrl();
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package org.springdoc.core.configuration.oauth2;
2+
3+
import java.util.List;
4+
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
import io.swagger.v3.oas.annotations.media.Schema;
7+
8+
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames;
9+
import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderMetadataClaimNames;
10+
11+
/**
12+
* The type Spring doc OpenID Provider Configuration
13+
*
14+
* @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>
15+
* @author yuta.saito
16+
*/
17+
@Schema(name = "OidcProviderConfiguration")
18+
public interface SpringDocOidcProviderConfiguration {
19+
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.ISSUER)
20+
String issuer();
21+
22+
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT)
23+
String authorizationEndpoint();
24+
25+
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT)
26+
String tokenEndpoint();
27+
28+
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED)
29+
List<String> tokenEndpointAuthMethodsSupported();
30+
31+
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI)
32+
String jwksUri();
33+
34+
@JsonProperty(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT)
35+
String userInfoEndpoint();
36+
37+
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED)
38+
List<String> responseTypesSupported();
39+
40+
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED)
41+
List<String> grantTypesSupported();
42+
43+
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT)
44+
String revocationEndpoint();
45+
46+
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED)
47+
List<String> revocationEndpointAuthMethodsSupported();
48+
49+
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT)
50+
String introspectionEndpoint();
51+
52+
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED)
53+
List<String> introspectionEndpointAuthMethodsSupported();
54+
55+
@JsonProperty(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED)
56+
String subjectTypesSupported();
57+
58+
@JsonProperty(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED)
59+
String idTokenSigningAlgValuesSupported();
60+
61+
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED)
62+
String scopeSupported();
63+
64+
@JsonProperty(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED)
65+
List<String> codeChallengeMethodsSupported();
66+
}

0 commit comments

Comments
 (0)