Skip to content

Commit acc8461

Browse files
committed
Implement Client Configuration Endpoint
See: https://openid.net/specs/openid-connect-registration-1_0.html#ClientConfigurationEndpoint Generate registration_client_uri and registration_access_token when registering a new client (see: https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration) Closes gh-355
1 parent 8e8979a commit acc8461

16 files changed

+1615
-139
lines changed

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
2525
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
2626
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
27+
import org.springframework.security.web.util.matcher.OrRequestMatcher;
2728
import org.springframework.security.web.util.matcher.RequestMatcher;
2829

2930
/**
@@ -47,13 +48,17 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
4748
@Override
4849
<B extends HttpSecurityBuilder<B>> void init(B builder) {
4950
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
50-
this.requestMatcher = new AntPathRequestMatcher(
51-
providerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.POST.name());
51+
this.requestMatcher = new OrRequestMatcher(
52+
new AntPathRequestMatcher(providerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.POST.name()),
53+
new AntPathRequestMatcher(providerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.GET.name())
54+
);
5255

5356
OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider =
5457
new OidcClientRegistrationAuthenticationProvider(
5558
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
56-
OAuth2ConfigurerUtils.getAuthorizationService(builder));
59+
OAuth2ConfigurerUtils.getAuthorizationService(builder),
60+
OAuth2ConfigurerUtils.getJwtEncoder(builder),
61+
providerSettings);
5762
builder.authenticationProvider(postProcess(oidcClientRegistrationAuthenticationProvider));
5863
}
5964

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientMetadataClaimAccessor.java

+21
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.security.oauth2.core.oidc;
1717

18+
import java.net.URL;
1819
import java.time.Instant;
1920
import java.util.List;
2021

@@ -134,4 +135,24 @@ default String getIdTokenSignedResponseAlgorithm() {
134135
return getClaimAsString(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG);
135136
}
136137

138+
/**
139+
* Returns the Registration Access Token that can be used at the Client Configuration Endpoint.
140+
*
141+
* @return the Registration Access Token that can be used at the Client Configuration Endpoint
142+
* @since 0.2.1
143+
*/
144+
default String getRegistrationAccessToken() {
145+
return getClaimAsString(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN);
146+
}
147+
148+
/**
149+
* Returns the {@code URL} of the OAuth 2.0 Client Configuration Endpoint.
150+
*
151+
* @return the {@code URL} of the OAuth 2.0 Client Configuration Endpoint
152+
* @since 0.2.1
153+
*/
154+
default URL getRegistrationClientUri() {
155+
return getClaimAsURL(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI);
156+
}
157+
137158
}

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientMetadataClaimNames.java

+12
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,16 @@ public interface OidcClientMetadataClaimNames {
8383
*/
8484
String ID_TOKEN_SIGNED_RESPONSE_ALG = "id_token_signed_response_alg";
8585

86+
/**
87+
* {@code registration_access_token} - Registration Access Token that can be used at the Client Configuration Endpoint to perform subsequent operations upon the Client registration
88+
* @since 0.2.1
89+
*/
90+
String REGISTRATION_ACCESS_TOKEN = "registration_access_token";
91+
92+
/**
93+
* {@code registration_client_uri} - the {@code URL} of the OAuth 2.0 Client Configuration Endpoint
94+
* @since 0.2.1
95+
*/
96+
String REGISTRATION_CLIENT_URI = "registration_client_uri";
97+
8698
}

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientRegistration.java

+20
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,26 @@ public Builder idTokenSignedResponseAlgorithm(String idTokenSignedResponseAlgori
251251
return claim(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG, idTokenSignedResponseAlgorithm);
252252
}
253253

254+
/**
255+
* Sets the Registration Access Token that can be used at the Client Configuration Endpoint to perform subsequent operations upon the Client registration, OPTIONAL.
256+
*
257+
* @param registrationAccessToken the Registration Access Token that can be used at the Client Configuration Endpoint to perform subsequent operations upon the Client registration
258+
* @return the {@link Builder} for further configuration
259+
*/
260+
public Builder registrationAccessToken(String registrationAccessToken) {
261+
return claim(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN, registrationAccessToken);
262+
}
263+
264+
/**
265+
* Sets the {@code URL} of the OAuth 2.0 Client Configuration Endpoint, OPTIONAL.
266+
*
267+
* @param registrationClientUri the {@code URL} of the OAuth 2.0 Client Configuration Endpoint
268+
* @return the {@link Builder} for further configuration
269+
*/
270+
public Builder registrationClientUri(String registrationClientUri) {
271+
return claim(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI, registrationClientUri);
272+
}
273+
254274
/**
255275
* Sets the claim.
256276
*

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcAuthenticationProviderUtils.java

+53-1
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,26 @@
1515
*/
1616
package org.springframework.security.oauth2.server.authorization.oidc.authentication;
1717

18+
import org.springframework.lang.Nullable;
1819
import org.springframework.security.authentication.AuthenticationProvider;
1920
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
21+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
22+
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
2023
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
24+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
25+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
26+
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
2127
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
22-
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
28+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
29+
import org.springframework.util.CollectionUtils;
30+
import org.springframework.util.StringUtils;
31+
import org.springframework.web.util.UriComponentsBuilder;
2332

2433
/**
2534
* Utility methods for the OpenID Connect 1.0 {@link AuthenticationProvider}'s.
2635
*
2736
* @author Joe Grandja
37+
* @author Ovidiu Popa
2838
* @since 0.1.1
2939
*/
3040
final class OidcAuthenticationProviderUtils {
@@ -60,4 +70,46 @@ static <T extends AbstractOAuth2Token> OAuth2Authorization invalidate(
6070

6171
return authorizationBuilder.build();
6272
}
73+
74+
static String registrationClientUri(String issuer, String oidcClientRegistrationEndpoint, String clientId){
75+
return UriComponentsBuilder.fromUriString(issuer)
76+
.path(oidcClientRegistrationEndpoint)
77+
.queryParam(OAuth2ParameterNames.CLIENT_ID, clientId).toUriString();
78+
}
79+
80+
static OidcClientRegistration convert(RegisteredClient registeredClient, String registrationClientUri,
81+
@Nullable String registrationAccessToken) {
82+
// @formatter:off
83+
OidcClientRegistration.Builder builder = OidcClientRegistration.builder()
84+
.clientId(registeredClient.getClientId())
85+
.clientIdIssuedAt(registeredClient.getClientIdIssuedAt())
86+
.clientSecret(registeredClient.getClientSecret())
87+
.clientName(registeredClient.getClientName());
88+
89+
builder.redirectUris(redirectUris ->
90+
redirectUris.addAll(registeredClient.getRedirectUris()));
91+
92+
builder.grantTypes(grantTypes ->
93+
registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
94+
grantTypes.add(authorizationGrantType.getValue())));
95+
96+
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
97+
builder.responseType(OAuth2AuthorizationResponseType.CODE.getValue());
98+
}
99+
100+
if (!CollectionUtils.isEmpty(registeredClient.getScopes())) {
101+
builder.scopes(scopes ->
102+
scopes.addAll(registeredClient.getScopes()));
103+
}
104+
105+
builder
106+
.tokenEndpointAuthenticationMethod(registeredClient.getClientAuthenticationMethods().iterator().next().getValue())
107+
.idTokenSignedResponseAlgorithm(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName())
108+
.registrationClientUri(registrationClientUri);
109+
if (StringUtils.hasText(registrationAccessToken)) {
110+
builder.registrationAccessToken(registrationAccessToken);
111+
}
112+
return builder.build();
113+
// @formatter:on
114+
}
63115
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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.server.authorization.oidc.authentication;
17+
18+
import java.io.IOException;
19+
20+
import javax.servlet.http.HttpServletRequest;
21+
22+
import org.springframework.http.converter.HttpMessageConverter;
23+
import org.springframework.http.server.ServletServerHttpRequest;
24+
import org.springframework.security.core.Authentication;
25+
import org.springframework.security.core.context.SecurityContextHolder;
26+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
27+
import org.springframework.security.oauth2.core.OAuth2Error;
28+
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
29+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
30+
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
31+
import org.springframework.security.oauth2.core.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
32+
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
33+
import org.springframework.security.web.authentication.AuthenticationConverter;
34+
import org.springframework.security.web.util.matcher.AndRequestMatcher;
35+
import org.springframework.security.web.util.matcher.RequestMatcher;
36+
import org.springframework.util.StringUtils;
37+
38+
/**
39+
* Attempts to extract an OpenID Connect Dynamic Client Registration 1.0 Request (or OpenID Connect Client Configuration 1.0 Request) from {@link HttpServletRequest}
40+
* for the OIDC Client Registration or Configuration flows and then converts it to
41+
* an {@link OidcClientRegistrationAuthenticationToken} used for authenticating the request.
42+
*
43+
* @author Ovidiu Popa
44+
* @since 0.2.1
45+
* @see AuthenticationConverter
46+
* @see OidcClientRegistrationAuthenticationToken
47+
* @see OidcClientRegistrationEndpointFilter
48+
*/
49+
public class OidcClientRegistrationAuthenticationConverter implements AuthenticationConverter {
50+
51+
private final HttpMessageConverter<OidcClientRegistration> clientRegistrationHttpMessageConverter =
52+
new OidcClientRegistrationHttpMessageConverter();
53+
54+
private static final RequestMatcher OIDC_CLIENT_CONFIGURATION_REQUEST_MATCHER = createOidcClientConfigurationRequestMatcher();
55+
56+
@Override
57+
public Authentication convert(HttpServletRequest request) {
58+
59+
60+
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
61+
62+
if ("POST".equals(request.getMethod())) {
63+
return getOidcClientRegistrationAuthentication(request, principal);
64+
}
65+
if (OIDC_CLIENT_CONFIGURATION_REQUEST_MATCHER.matches(request)) {
66+
return getOidcClientConfigurationAuthentication(request, principal);
67+
}
68+
69+
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
70+
}
71+
72+
private OidcClientRegistrationAuthenticationToken getOidcClientConfigurationAuthentication(HttpServletRequest request, Authentication principal) {
73+
String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID);
74+
String[] clientIdParameters = request.getParameterValues(OAuth2ParameterNames.CLIENT_ID);
75+
if (!StringUtils.hasText(clientId) || clientIdParameters.length != 1) {
76+
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
77+
}
78+
return
79+
OidcClientRegistrationAuthenticationToken.with(principal)
80+
.clientRegistrationRequest(false)
81+
.clientId(clientId)
82+
.build();
83+
}
84+
85+
private Authentication getOidcClientRegistrationAuthentication(HttpServletRequest request, Authentication principal) {
86+
try {
87+
OidcClientRegistration clientRegistration = this.clientRegistrationHttpMessageConverter.read(
88+
OidcClientRegistration.class, new ServletServerHttpRequest(request));
89+
return
90+
OidcClientRegistrationAuthenticationToken.with(principal)
91+
.clientRegistrationRequest(true)
92+
.clientRegistration(clientRegistration)
93+
.build();
94+
} catch (IOException ex) {
95+
OAuth2Error error = new OAuth2Error(
96+
OAuth2ErrorCodes.INVALID_REQUEST,
97+
"OpenID Client Registration Error: " + ex.getMessage(),
98+
"https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError");
99+
throw new OAuth2AuthenticationException(error);
100+
}
101+
}
102+
103+
private static RequestMatcher createOidcClientConfigurationRequestMatcher() {
104+
RequestMatcher clientConfigurationRequestGetMatcher = request -> "GET".equals(request.getMethod());
105+
106+
RequestMatcher clientIdMatcher = request -> {
107+
String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID);
108+
return StringUtils.hasText(clientId);
109+
};
110+
111+
return
112+
new AndRequestMatcher(clientConfigurationRequestGetMatcher, clientIdMatcher);
113+
114+
}
115+
}

0 commit comments

Comments
 (0)