Skip to content

Commit 8224a0d

Browse files
ovidiupopa07jgrandja
authored andcommitted
Implement OpenID client registration endpoint
See: https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration Closes gh-57
1 parent 2712a7b commit 8224a0d

25 files changed

+2476
-36
lines changed

oauth2-authorization-server/spring-security-oauth2-authorization-server.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ dependencies {
55
compile 'org.springframework.security:spring-security-web'
66
compile 'org.springframework.security:spring-security-oauth2-core'
77
compile 'org.springframework.security:spring-security-oauth2-jose'
8+
compile 'org.springframework.security:spring-security-oauth2-resource-server'
89
compile springCoreDependency
910
compile 'com.nimbusds:nimbus-jose-jwt'
1011
compile 'com.fasterxml.jackson.core:jackson-databind'
@@ -15,6 +16,7 @@ dependencies {
1516
testCompile 'org.assertj:assertj-core'
1617
testCompile 'org.mockito:mockito-core'
1718
testCompile 'com.jayway.jsonpath:json-path'
19+
testCompile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
1820

1921
provided 'javax.servlet:javax.servlet-api'
2022
}

oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizationServerConfiguration.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@
2121
import org.springframework.core.annotation.Order;
2222
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2323
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
24+
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
2425
import org.springframework.security.web.SecurityFilterChain;
2526
import org.springframework.security.web.util.matcher.RequestMatcher;
2627

2728
/**
2829
* {@link Configuration} for OAuth 2.0 Authorization Server support.
2930
*
3031
* @author Joe Grandja
31-
* @since 0.0.1
3232
* @see OAuth2AuthorizationServerConfigurer
33+
* @since 0.0.1
3334
*/
3435
@Configuration(proxyBeanMethods = false)
3536
public class OAuth2AuthorizationServerConfiguration {
@@ -47,14 +48,16 @@ public static void applyDefaultSecurity(HttpSecurity http) throws Exception {
4748
new OAuth2AuthorizationServerConfigurer<>();
4849
RequestMatcher endpointsMatcher = authorizationServerConfigurer
4950
.getEndpointsMatcher();
50-
5151
http
5252
.requestMatcher(endpointsMatcher)
5353
.authorizeRequests(authorizeRequests ->
54-
authorizeRequests.anyRequest().authenticated()
55-
)
56-
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
54+
authorizeRequests.anyRequest().authenticated()
55+
).csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
5756
.apply(authorizationServerConfigurer);
57+
58+
if (authorizationServerConfigurer.isOidcClientRegistrationEnabled()) {
59+
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
60+
}
5861
}
5962
// @formatter:on
6063
}

oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@
4444
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider;
4545
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider;
4646
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider;
47+
import org.springframework.security.oauth2.server.authorization.authentication.OidcClientRegistrationAuthenticationProvider;
4748
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
4849
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
50+
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
4951
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
5052
import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
5153
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
@@ -69,6 +71,7 @@
6971
* @author Joe Grandja
7072
* @author Daniel Garnier-Moiroux
7173
* @author Gerardo Roza
74+
* @author Ovidiu Popa
7275
* @since 0.0.1
7376
* @see AbstractHttpConfigurer
7477
* @see RegisteredClientRepository
@@ -81,6 +84,7 @@
8184
* @see OidcProviderConfigurationEndpointFilter
8285
* @see OAuth2AuthorizationServerMetadataEndpointFilter
8386
* @see OAuth2ClientAuthenticationFilter
87+
* @see OidcClientRegistrationEndpointFilter
8488
*/
8589
public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>>
8690
extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> {
@@ -92,14 +96,16 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
9296
private RequestMatcher jwkSetEndpointMatcher;
9397
private RequestMatcher oidcProviderConfigurationEndpointMatcher;
9498
private RequestMatcher authorizationServerMetadataEndpointMatcher;
99+
private RequestMatcher oidcClientRegistrationEndpointMatcher;
95100
private final RequestMatcher endpointsMatcher = (request) ->
96101
this.authorizationEndpointMatcher.matches(request) ||
97102
this.tokenEndpointMatcher.matches(request) ||
98103
this.tokenIntrospectionEndpointMatcher.matches(request) ||
99104
this.tokenRevocationEndpointMatcher.matches(request) ||
100105
this.jwkSetEndpointMatcher.matches(request) ||
101106
this.oidcProviderConfigurationEndpointMatcher.matches(request) ||
102-
this.authorizationServerMetadataEndpointMatcher.matches(request);
107+
this.authorizationServerMetadataEndpointMatcher.matches(request) ||
108+
this.oidcClientRegistrationEndpointMatcher.matches(request);
103109

104110
/**
105111
* Sets the repository of registered clients.
@@ -146,6 +152,17 @@ public RequestMatcher getEndpointsMatcher() {
146152
return this.endpointsMatcher;
147153
}
148154

155+
/**
156+
* Returns {@code true} if the OIDC Client Registration endpoint is enabled.
157+
* The default is {@code false}.
158+
*
159+
* @return {@code true} if the OIDC Client Registration endpoint is enabled, {@code false} otherwise
160+
*/
161+
public boolean isOidcClientRegistrationEnabled() {
162+
ProviderSettings providerSettings = getProviderSettings(this.getBuilder());
163+
return providerSettings.isOidClientRegistrationEndpointEnabled();
164+
}
165+
149166
@Override
150167
public void init(B builder) {
151168
ProviderSettings providerSettings = getProviderSettings(builder);
@@ -199,6 +216,11 @@ public void init(B builder) {
199216
getAuthorizationService(builder));
200217
builder.authenticationProvider(postProcess(tokenRevocationAuthenticationProvider));
201218

219+
OidcClientRegistrationAuthenticationProvider clientRegistrationAuthenticationProvider =
220+
new OidcClientRegistrationAuthenticationProvider(
221+
getAuthorizationService(builder));
222+
builder.authenticationProvider(postProcess(clientRegistrationAuthenticationProvider));
223+
202224
ExceptionHandlingConfigurer<B> exceptionHandling = builder.getConfigurer(ExceptionHandlingConfigurer.class);
203225
if (exceptionHandling != null) {
204226
exceptionHandling.defaultAuthenticationEntryPointFor(
@@ -224,6 +246,9 @@ public void configure(B builder) {
224246
builder.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
225247
}
226248

249+
RegisteredClientRepository registeredClientRepository = getRegisteredClientRepository(builder);
250+
OAuth2AuthorizationService authorizationService = getAuthorizationService(builder);
251+
227252
JWKSource<SecurityContext> jwkSource = getJwkSource(builder);
228253
NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(
229254
jwkSource,
@@ -243,8 +268,8 @@ public void configure(B builder) {
243268

244269
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
245270
new OAuth2AuthorizationEndpointFilter(
246-
getRegisteredClientRepository(builder),
247-
getAuthorizationService(builder),
271+
registeredClientRepository,
272+
authorizationService,
248273
providerSettings.authorizationEndpoint());
249274
builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
250275

@@ -265,6 +290,15 @@ public void configure(B builder) {
265290
authenticationManager,
266291
providerSettings.tokenRevocationEndpoint());
267292
builder.addFilterAfter(postProcess(tokenRevocationEndpointFilter), OAuth2TokenIntrospectionEndpointFilter.class);
293+
294+
if (providerSettings.isOidClientRegistrationEndpointEnabled()) {
295+
OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter =
296+
new OidcClientRegistrationEndpointFilter(
297+
registeredClientRepository,
298+
authenticationManager,
299+
providerSettings.oidcClientRegistrationEndpoint());
300+
builder.addFilterAfter(postProcess(oidcClientRegistrationEndpointFilter), OAuth2TokenRevocationEndpointFilter.class);
301+
}
268302
}
269303

270304
private void initEndpointMatchers(ProviderSettings providerSettings) {
@@ -287,6 +321,9 @@ private void initEndpointMatchers(ProviderSettings providerSettings) {
287321
OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI, HttpMethod.GET.name());
288322
this.authorizationServerMetadataEndpointMatcher = new AntPathRequestMatcher(
289323
OAuth2AuthorizationServerMetadataEndpointFilter.DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI, HttpMethod.GET.name());
324+
this.oidcClientRegistrationEndpointMatcher = new AntPathRequestMatcher(
325+
providerSettings.oidcClientRegistrationEndpoint(),
326+
HttpMethod.POST.name());
290327
}
291328

292329
private static void validateProviderSettings(ProviderSettings providerSettings) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2020-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.oauth2.core.oidc;
17+
18+
import org.springframework.security.oauth2.core.ClaimAccessor;
19+
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
20+
21+
import java.time.Instant;
22+
import java.util.List;
23+
24+
/**
25+
* A {@link ClaimAccessor} for the "claims" that can be returned
26+
* in the OpenID Client Registration Response.
27+
*
28+
* @author Ovidiu Popa
29+
* @since 0.1.1
30+
* @see ClaimAccessor
31+
* @see OidcClientMetadataClaimNames
32+
* @see OidcClientRegistration
33+
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata">2. Client Metadata</a>
34+
*/
35+
public interface OidcClientMetadataClaimAccessor extends ClaimAccessor {
36+
37+
/**
38+
* Returns the redirect URI(s) that the client may use in redirect-based flows.
39+
*
40+
* @return the {@code List} of redirect URI(s)
41+
*/
42+
default List<String> getRedirectUris() {
43+
return getClaimAsStringList(OidcClientMetadataClaimNames.REDIRECT_URIS);
44+
}
45+
46+
/**
47+
* Returns the OAuth 2.0 {@code response_type} values that the client may use.
48+
*
49+
* @return the {@code List} of {@code response_type}
50+
*/
51+
default List<String> getResponseTypes() {
52+
return getClaimAsStringList(OidcClientMetadataClaimNames.RESPONSE_TYPES);
53+
}
54+
55+
/**
56+
* Returns the authorization {@code grant_types} that the client may use.
57+
*
58+
* @return the {@code List} of authorization {@code grant_types}
59+
*/
60+
default List<String> getGrantTypes() {
61+
return getClaimAsStringList(OidcClientMetadataClaimNames.GRANT_TYPES);
62+
}
63+
64+
/**
65+
* Returns the {@code client_name}.
66+
*
67+
* @return the {@code client_name}
68+
*/
69+
default String getClientName() {
70+
return getClaimAsString(OidcClientMetadataClaimNames.CLIENT_NAME);
71+
}
72+
73+
/**
74+
* Returns the scope(s) that the client may use.
75+
*
76+
* @return the scope(s)
77+
*/
78+
default String getScope() {
79+
return getClaimAsString(OidcClientMetadataClaimNames.SCOPE);
80+
}
81+
82+
/**
83+
* Returns the {@link ClientAuthenticationMethod authentication method} that the client may use.
84+
*
85+
* @return the {@link ClientAuthenticationMethod authentication method}
86+
*/
87+
default String getTokenEndpointAuthenticationMethod() {
88+
return getClaimAsString(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD);
89+
}
90+
91+
/**
92+
* Returns the {@code client_id}.
93+
*
94+
* @return the {@code client_id}
95+
*/
96+
default String getClientId() {
97+
return getClaimAsString(OidcClientMetadataClaimNames.CLIENT_ID);
98+
}
99+
100+
/**
101+
* Returns the {@code client_id_issued_at} timestamp.
102+
*
103+
* @return the {@code client_id_issued_at} timestamp
104+
*/
105+
default Instant getClientIdIssuedAt() {
106+
return getClaimAsInstant(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT);
107+
}
108+
109+
/**
110+
* Returns the {@code client_secret}.
111+
*
112+
* @return the {@code client_secret}
113+
*/
114+
default String getClientSecret() {
115+
return getClaimAsString(OidcClientMetadataClaimNames.CLIENT_SECRET);
116+
}
117+
118+
/**
119+
* Returns the {@code client_secret_expires_at} timestamp.
120+
*
121+
* @return the {@code client_secret_expires_at} timestamp
122+
*/
123+
default Instant getClientSecretExpiresAt() {
124+
return getClaimAsInstant(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT);
125+
}
126+
127+
128+
129+
130+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2020-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.oauth2.core.oidc;
17+
18+
/**
19+
* The names of the "claims" defined by OpenID Client Registration 1.0 that can be returned
20+
* in the OpenID Client Registration Response.
21+
*
22+
* @author Ovidiu Popa
23+
* @since 0.1.1
24+
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata">2. Client Metadata</a>
25+
*/
26+
public interface OidcClientMetadataClaimNames {
27+
28+
//request
29+
/**
30+
* {@code redirect_uris} - the redirect URI(s) that the client may use in redirect-based flows
31+
*/
32+
String REDIRECT_URIS = "redirect_uris";
33+
34+
/**
35+
* {@code response_types} - the OAuth 2.0 {@code response_type} values that the client may use
36+
*/
37+
String RESPONSE_TYPES = "response_types";
38+
39+
/**
40+
* {@code grant_types} - the OAuth 2.0 authorization {@code grant_types} that the client may use
41+
*/
42+
String GRANT_TYPES = "grant_types";
43+
44+
/**
45+
* {@code client_name} - the {@code client_name}
46+
*/
47+
String CLIENT_NAME = "client_name";
48+
49+
/**
50+
* {@code scope} - the scope(s) that the client may use
51+
*/
52+
String SCOPE = "scope";
53+
54+
/**
55+
* {@code token_endpoint_auth_method} - the {@link org.springframework.security.oauth2.core.ClientAuthenticationMethod authentication method} that the client may use.
56+
*/
57+
String TOKEN_ENDPOINT_AUTH_METHOD = "token_endpoint_auth_method";
58+
59+
//response
60+
/**
61+
* {@code client_id} - the {@code client_id}
62+
*/
63+
String CLIENT_ID = "client_id";
64+
65+
/**
66+
* {@code client_secret} - the {@code client_secret}
67+
*/
68+
String CLIENT_SECRET = "client_secret";
69+
70+
/**
71+
* {@code client_id_issued_at} - the timestamp when the client id was issued
72+
*/
73+
String CLIENT_ID_ISSUED_AT = "client_id_issued_at";
74+
75+
/**
76+
* {@code client_secret_expires_at} - the timestamp when the client secret expires
77+
*/
78+
String CLIENT_SECRET_EXPIRES_AT = "client_secret_expires_at";
79+
}

0 commit comments

Comments
 (0)